# 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.