Bonjour,
Je suis en train de faire un projet Spring Boot en version 2.7.4 qui me permettra de faire un jar qui sera ensuite importé dans plusieurs applications.
Le but étant d'avoir une configuration de sécurité commune pour toutes ces applications plutôt qu'elles fassent chacune leur propre configuration car la base des utilisateurs pour ces applications sera commune et on veut gérer de la même manière la sécurité.
Voici ce que j'ai initialisé (non encore fini):
Authentication filter (pour gérer la connexion sur l'URI '/api/login' et la création du token JWT):
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 @Configuration("personalSecurityConfig") @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class); private static final String[] IGNORING_LIST = { // Swagger ui "/api/v2/api-docs", "/api/swagger-ui.html", "/api/webjars/springfox-swagger-ui/**", "/api/swagger-resources/**", // URLs without authentication needed "/api/no-security" }; @Value("${jwt.secret}") private String jwtSecret; @Autowired private PersoSecurityProperties persoSecurityProperties; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // Do not allow anonymous access to the application (AnonymousAuthenticationFilter will not be set) http.anonymous().disable(); http.addFilter(jwtAuthenticationFilter()); http.addFilter(jwtAuthorizationFilter()); // TODO // The CSRF token must be set in a cookie so that angular can read it http.csrf().csrfTokenRepository(getCsrfTokenRepository()); return http.build(); } @Bean @Order(99) SecurityFilterChain configurePublicEndpoints(HttpSecurity http) throws Exception { List<String> publicResources = getPublicResources(); try { http.requestMatchers(matcher -> matcher.antMatchers(publicResources.toArray(new String[0]))) .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()).csrf().disable() .requestCache().disable().securityContext().disable().sessionManagement().disable(); } catch (Exception ex) { LOGGER.error("Error applying custom security configuration for public endpoints '{}' ", publicResources, ex); } return http.build(); } /** * Permit to indicate to Spring security to not secure the given URI * @return */ @Bean public WebSecurityCustomizer ignoringCustomizer() { return (web) -> web.ignoring().antMatchers(IGNORING_LIST); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception { JwtAuthenticationFilter lJwtAuthenticationFilter = new JwtAuthenticationFilter(authManager()); return lJwtAuthenticationFilter; } @Bean public JwtAuthorizationFilter jwtAuthorizationFilter() throws Exception { JwtAuthorizationFilter lJwtAuthenticationFilter = new JwtAuthorizationFilter(authManager(), jwtSecret); return lJwtAuthenticationFilter; } /** * Bean for personal authentication user detail service. * @return */ @Bean public AuthenticationUserDetailsService<UsernamePasswordAuthenticationToken> personalAuthenticationUserDetailsService() { return new PersonalAuthenticationUserDetailsService(); } @Autowired public void configureAuthenticationProvider(AuthenticationManagerBuilder auth) throws Exception { // TODO } /** * Custom message resource in order not to be poisoned if application is also defining one. * @return */ @Bean("messageResourceSecurityPerso") public MessageSource messageResource() { ResourceBundleMessageSource messageBundleResrc = new ResourceBundleMessageSource(); messageBundleResrc.setBasename("messages"); messageBundleResrc.setDefaultEncoding(StandardCharsets.UTF_8.name()); return messageBundleResrc; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * Permit to have have all URL patters for public resources. * @return URL patters for public resources */ private List<String> getPublicResources() { List<String> defaultPublicPatters = Arrays.asList("/**/*.css", "/**/*.gif", "/**/*.png", "/**/*.jpeg", "/**/*.jpg"); List<String> publicEnpointsProperties = this.persoSecurityProperties.getPublicEndpoints(); StringJoiner joiner = new StringJoiner("', '", "'", "'"); List<String> publicEndpoints = Stream.concat(defaultPublicPatters.stream(), publicEnpointsProperties.stream()) .filter(Objects::nonNull).filter(StringUtils::hasText).collect(Collectors.toList()); publicEndpoints.forEach(joiner::add); LOGGER.info("Public URL patterns: " + joiner); return publicEndpoints; } private CsrfTokenRepository getCsrfTokenRepository() { // The cookie can be read by javascript CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); // Front-end and back-end are on different context paths so set cookie path to '/' tokenRepository.setCookiePath("/"); return tokenRepository; } }
Authorization filter (qui gère la vérification du token pour toutes les requêtes sur des URIs sécurisées):
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationFilter.class); private AuthenticationManager authenticationManager; private String jwtSecret; public JwtAuthenticationFilter(AuthenticationManager authenticationManager, String jwtSecret) { this.authenticationManager = authenticationManager; this.jwtSecret = jwtSecret; setFilterProcessesUrl("/api/login"); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { LOGGER.info("JwtAuthenticationFilter.attemptAuthentication()"); try { // Get credentials from request UserCredentials credentials = new ObjectMapper().readValue(request.getInputStream(), UserCredentials.class); // Create auth object (contains credentials) which will be used by auth manager UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( credentials.getUsername(), credentials.getPassword(), Collections.emptyList()); // Authentication manager authenticate the user, and use // UserDetailsService.loadUserByUsername() method to load the user return this.authenticationManager.authenticate(authToken); } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain, Authentication authentication) { LOGGER.info("JwtAuthenticationFilter.successfulAuthentication()"); UserCredentials user = (UserCredentials) authentication.getPrincipal(); // INFO: username is the email String token = TokenUtils.buildToken(user.getUsername(), this.jwtSecret); response.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); } }
Ce que je souhaite est que le front-end appelle '/api/login' avec le username et le password de l'utilisateur ce qui doit permettre de vérifier son mot de passe et créer le token si c'est bon qui sera mis dans le header de la réponse. Ensuite, pour chaque requête sur une URI sécurisée, on vérifiera le token et on récupèrera les informations utilisateur dont ses droits afin d'initialiser le contexte de sécurité Spring (=liste de GrantedAuthority).
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class JwtAuthorizationFilter extends BasicAuthenticationFilter { private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthorizationFilter.class); private String jwtSecret; public JwtAuthorizationFilter(AuthenticationManager authenticationManager, String jwtSecret) { super(authenticationManager); this.jwtSecret = jwtSecret; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { LOGGER.info("JwtAuthorizationFilter.doFilterInternal()"); UsernamePasswordAuthenticationToken authentication = parseToken(request); if (authentication != null) { SecurityContextHolder.getContext().setAuthentication(authentication); } else { SecurityContextHolder.clearContext(); } filterChain.doFilter(request, response); } private UsernamePasswordAuthenticationToken parseToken(HttpServletRequest request) { String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { String token = authorizationHeader.replace("Bearer ", ""); try { TokenData tokenData = TokenUtils.verifyToken(token, this.jwtSecret); // TODO retrieve roles for email with a service to do a query List<GrantedAuthority> authorities = new ArrayList<>(); return new UsernamePasswordAuthenticationToken(tokenData.getEmail(), null, authorities); } catch (JwtException exception) { LOGGER.warn("Parse token in authorization header '{}' failed with following error: {}", authorizationHeader, exception.getMessage()); } } return null; } }
Est-ce que cette logique et ce que j'ai initialisé vous semble correct?
Egalement, comment puis-je récupérer l'authentication manager dont ont besoin mes filtres car avant on pouvait étendre WebSecurityConfigurerAdapter et surcharger le bean authenticationManagerBean() mais étendre WebSecurityConfigurerAdapter est maintenant déprécié.
Aussi, je ne sais pas comment gérer un ou plusieurs UserDetailsService (avec gestion du UsernamePasswordAuthenticationToken) qui doivent gérer la vérification du mot de passe pour le premier filtre par exemple. Je ne sais pas si j'en ai besoin d'un aussi pour le deuxième filtre.
Idem pour la configuration de l'authentication provider (méthode configureAuthenticationProvider() de ma configuration). Je ne sais pas si j'ai des choses à configurer.
Enfin, je ne sais pas si j'ai besoin d'un token CSRF pour plus de sécurité car il me semblait avoir entendu que ce n'est pas nécessaire quand on a un jeton JWT.
Pouvez-vous m'aider pour tout ca (désolé ca fait beaucoup d'interrogations) svp?
Partager