# Kickoff Java Project — Security Module
Security module (always included). Decodes JWT tokens for endpoints.
Reference: Janus `janus-security/` module.
---
## POM dependencies
```xml
${project.groupId}-common
co.humand.commons.securityauthentication
org.springframeworkspring-context
org.springframework.bootspring-boot-autoconfigure
org.springframework.securityspring-security-crypto
org.apache.httpcomponents.client5httpclient5
org.springframework.bootspring-boot-starter-testtest
```
---
## SecurityConfiguration
```java
@Configuration
@ComponentScan(basePackageClasses = SecurityConfiguration.class)
@EnableConfigurationProperties(SecurityConfigurationProperties.class)
public class SecurityConfiguration {
@Bean
public JwtSecretProvider jwtSecretProvider(SecurityConfigurationProperties securityConfigurationProperties) {
return () -> securityConfigurationProperties.jwt().secret();
}
@Bean
public TextEncryptorFactory textEncryptorFactory(JwtSecretProvider jwtSecretProvider) {
return new TextEncryptorFactoryImpl(jwtSecretProvider);
}
}
```
Named `SecurityConfiguration` (e.g. `JanusSecurityConfiguration`). Uses beans from `co.humand.commons.security.authentication`.
---
## SecurityConfigurationProperties
```java
@ConfigurationProperties("humand..security")
public record SecurityConfigurationProperties(
@NestedConfigurationProperty @Name("jwt") Jwt jwt,
@NestedConfigurationProperty @Name("cors") Cors cors) {
@ConstructorBinding
public SecurityConfigurationProperties(Jwt jwt, Cors cors) {
this.jwt = jwt;
this.cors = cors;
}
public record Jwt(String secret, String jwksRsaUrl) {}
public record Cors(List allowedOrigins, List allowedHeaders) {
private static final Cors PERMIT_ALL = new Cors(List.of("*"), List.of("*"));
public Cors(List allowedOrigins, List allowedHeaders) {
List filteredOrigins =
allowedOrigins.stream().filter(not(String::isBlank)).toList();
List filteredHeaders =
allowedHeaders.stream().filter(not(String::isBlank)).toList();
this.allowedOrigins = filteredOrigins.isEmpty() ? permitAll().allowedOrigins : filteredOrigins;
this.allowedHeaders = filteredHeaders.isEmpty() ? permitAll().allowedHeaders : filteredHeaders;
}
public static Cors permitAll() {
return PERMIT_ALL;
}
}
}
```
Cors record filters empty/blank values and falls back to permit-all when no valid values configured.
---
## JwtScope enum
```java
public enum JwtScope {
APP,
BO,
BA,
INTERNAL;
public static Set parse(String rawScope) {
return Arrays.stream(rawScope.split(" "))
.filter(s -> !s.isBlank())
.map(s -> {
try { return JwtScope.valueOf(s); }
catch (IllegalArgumentException e) { return null; }
})
.filter(s -> s != null)
.collect(Collectors.toUnmodifiableSet());
}
}
```
---
## package-info.java
```java
@NullMarked
package co.humand..security;
import org.jspecify.annotations.NullMarked;
```
Only at the top-level package. Subpackages inherit.
---
## application.yml block
```yaml
humand:
:
security:
jwt:
secret: ${HUMAND__JWT_SECRET:}
jwks-rsa-url: ${HUMAND__JWKS_RSA_URL:}
cors:
allowed-origins: ${HUMAND__CORS_ALLOWED_ORIGINS:}
allowed-headers: ${HUMAND__CORS_ALLOWED_HEADERS:}
```
---
## How webapp uses it
1. Application class: `@Import(SecurityConfiguration.class)` — ensures security beans load even if component scan misses them.
2. `WebSecurityConfiguration` in webapp:
- Injects `SecurityConfigurationProperties` for `JwtDecoder` bean (via `JwtTokenDecoder`)
- Injects `SecurityConfigurationProperties.Cors` for CORS configuration source
- See `modules/base.md` for full WebSecurityConfiguration pattern.