|
1 | 1 | package fr.insee.genesis.configuration.auth.security; |
2 | 2 |
|
3 | | -import fr.insee.genesis.configuration.Config; |
| 3 | +import lombok.Getter; |
| 4 | +import lombok.RequiredArgsConstructor; |
| 5 | +import lombok.Setter; |
4 | 6 | import lombok.extern.slf4j.Slf4j; |
5 | | -import org.springframework.beans.factory.annotation.Autowired; |
6 | 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| 8 | +import org.springframework.boot.context.properties.ConfigurationProperties; |
7 | 9 | import org.springframework.context.annotation.Bean; |
8 | 10 | import org.springframework.context.annotation.Configuration; |
| 11 | +import org.springframework.core.convert.converter.Converter; |
| 12 | +import org.springframework.http.HttpMethod; |
9 | 13 | import org.springframework.security.config.Customizer; |
| 14 | +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; |
10 | 15 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
11 | 16 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
12 | 17 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; |
13 | 18 | import org.springframework.security.config.http.SessionCreationPolicy; |
| 19 | +import org.springframework.security.core.GrantedAuthority; |
| 20 | +import org.springframework.security.oauth2.jwt.Jwt; |
| 21 | +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; |
14 | 22 | import org.springframework.security.web.SecurityFilterChain; |
| 23 | +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; |
15 | 24 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
16 | 25 |
|
| 26 | +import java.util.Collection; |
| 27 | +import java.util.Collections; |
| 28 | +import java.util.List; |
| 29 | +import java.util.Map; |
| 30 | + |
17 | 31 | @Configuration |
18 | 32 | @EnableWebSecurity |
| 33 | +@EnableMethodSecurity(prePostEnabled = true) |
19 | 34 | @Slf4j |
20 | 35 | @ConditionalOnProperty(name = "fr.insee.genesis.authentication", havingValue = "OIDC") |
| 36 | +@ConfigurationProperties(prefix = "fr.insee.genesis.security") |
| 37 | +@RequiredArgsConstructor |
21 | 38 | public class OIDCSecurityConfig { |
22 | 39 |
|
23 | | - Config config; |
24 | | - @Autowired |
25 | | - public OIDCSecurityConfig(Config config) { |
26 | | - this.config = config; |
27 | | - } |
| 40 | + @Getter |
| 41 | + @Setter |
| 42 | + private String[] whitelistMatchers; |
| 43 | + private static final String ROLE_PREFIX = "ROLE_"; |
| 44 | + private final RoleConfiguration roleConfiguration; |
| 45 | + private final SecurityTokenProperties inseeSecurityTokenProperties; |
28 | 46 |
|
29 | | - @Bean |
30 | | - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
31 | | - http |
32 | | - .csrf(AbstractHttpConfigurer::disable) |
33 | | - .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); |
34 | | - for (var pattern : config.getWhiteList()) { |
35 | | - http.authorizeHttpRequests(authorize -> |
36 | | - authorize |
37 | | - .requestMatchers(AntPathRequestMatcher.antMatcher(pattern)).permitAll() |
38 | | - ); |
39 | | - } |
40 | | - http |
41 | | - .authorizeHttpRequests(configurer -> configurer |
42 | | - .anyRequest().authenticated() |
43 | | - ) |
44 | | - .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); |
45 | | - return http.build(); |
| 47 | + @Bean |
| 48 | + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
| 49 | + http |
| 50 | + .csrf(AbstractHttpConfigurer::disable) |
| 51 | + .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); |
| 52 | + for (var pattern : whitelistMatchers) { |
| 53 | + http.authorizeHttpRequests(authorize -> |
| 54 | + authorize |
| 55 | + .requestMatchers(AntPathRequestMatcher.antMatcher(pattern)).permitAll() |
| 56 | + ); |
46 | 57 | } |
| 58 | + http |
| 59 | + .authorizeHttpRequests(configurer -> configurer |
| 60 | + .requestMatchers(HttpMethod.GET,"/questionnaires/**").hasRole(String.valueOf(ApplicationRole.READER)) |
| 61 | + .requestMatchers(HttpMethod.GET,"/modes/**").hasRole(String.valueOf(ApplicationRole.READER)) |
| 62 | + .requestMatchers(HttpMethod.GET,"/interrogations/**").hasRole(String.valueOf(ApplicationRole.READER)) |
| 63 | + .requestMatchers(HttpMethod.GET,"/campaigns/**").hasRole(String.valueOf(ApplicationRole.READER)) |
| 64 | + .anyRequest().authenticated() |
| 65 | + ) |
| 66 | + .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); |
| 67 | + return http.build(); |
| 68 | + } |
| 69 | + |
| 70 | + @Bean |
| 71 | + JwtAuthenticationConverter jwtAuthenticationConverter() { |
| 72 | + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); |
| 73 | + jwtAuthenticationConverter.setPrincipalClaimName(inseeSecurityTokenProperties.getOidcClaimUsername()); |
| 74 | + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter()); |
| 75 | + return jwtAuthenticationConverter; |
| 76 | + } |
47 | 77 |
|
| 78 | + |
| 79 | + Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter() { |
| 80 | + return new Converter<Jwt, Collection<GrantedAuthority>>() { |
| 81 | + @Override |
| 82 | + @SuppressWarnings({"unchecked"}) |
| 83 | + public Collection<GrantedAuthority> convert(Jwt source) { |
| 84 | + |
| 85 | + String[] claimPath = inseeSecurityTokenProperties.getOidcClaimRole().split("\\."); |
| 86 | + Map<String, Object> claims = source.getClaims(); |
| 87 | + try { |
| 88 | + for (int i = 0; i < claimPath.length - 1; i++) { |
| 89 | + claims = (Map<String, Object>) claims.get(claimPath[i]); |
| 90 | + } |
| 91 | + if (claims != null) { |
| 92 | + List<String> tokenClaims = (List<String>) claims.getOrDefault(claimPath[claimPath.length - 1], List.of()); |
| 93 | + // Collect distinct values from mapping associated with input keys |
| 94 | + List<String> claimedRoles = tokenClaims.stream() |
| 95 | + .filter(roleConfiguration.getRolesByClaim()::containsKey) // Ensure the key exists in the mapping |
| 96 | + .flatMap(key -> roleConfiguration.getRolesByClaim().get(key).stream()) // Get the list of values associated with the key |
| 97 | + .distinct() // Remove duplicates |
| 98 | + .toList(); |
| 99 | + |
| 100 | + return Collections.unmodifiableCollection(claimedRoles.stream().map(s -> new GrantedAuthority() { |
| 101 | + @Override |
| 102 | + public String getAuthority() { |
| 103 | + return ROLE_PREFIX + s; |
| 104 | + } |
| 105 | + |
| 106 | + @Override |
| 107 | + public String toString() { |
| 108 | + return getAuthority(); |
| 109 | + } |
| 110 | + }).toList()); |
| 111 | + } |
| 112 | + } catch (ClassCastException e) { |
| 113 | + // role path not correctly found, assume that no role for this user |
| 114 | + return List.of(); |
| 115 | + } |
| 116 | + return List.of(); |
| 117 | + } |
| 118 | + }; |
| 119 | + } |
48 | 120 | } |
0 commit comments