Bonjour à tous,
J'ai créé un template de projet SpringBoot pour exposer une API REST pour une appli angular.
Pour les tests et afin de partager l'API avec d'autres développeurs j'ai mis en place Swagger-ui
Pour sécuriser : j'utilise Spring Security et JWT: tout fonctionne comme je veux, mon seul soucis c'est d'arriver à proposer dans Swagger-ui le service permettant de générer le Token
Voici ma conf :
Swagger
AuthorizationFilter
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 @Configuration public class Swagger2Config { private ApiInfo apiEndPointsInfo() { return new ApiInfoBuilder().title("Application - REST API") .description("API REST - Exposition des services REST de l'application") .contact(new Contact("Toto", "https://www.toto.fr", "toto@toto.fr")) .version(MavenUtils.getVersion()).build(); } @Bean public Docket productApi() { return new Docket(DocumentationType.OAS_30).select() .apis(RequestHandlerSelectors.basePackage("webservices")) .paths(PathSelectors.any()).build().securityContexts(Arrays.asList(this.securityContext())) .securitySchemes(Arrays.asList(this.apiKey())).apiInfo(this.apiEndPointsInfo()); } private ApiKey apiKey() { return new ApiKey(SecurityConstants.HEADER_STRING, "Authorization", "header"); } private SecurityContext securityContext() { return SecurityContext.builder().securityReferences(this.defaultAuth()).build(); } private List<SecurityReference> defaultAuth() { final AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); final AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; authorizationScopes[0] = authorizationScope; return Arrays.asList(new SecurityReference(SecurityConstants.HEADER_STRING, authorizationScopes)); } }
AuthenticationFilter
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 public class JWTAuthorizationFilter extends BasicAuthenticationFilter { Logger logger = LoggerFactory.getLogger(JWTAuthorizationFilter.class); public JWTAuthorizationFilter(final AuthenticationManager authManager) { super(authManager); } @Override protected void doFilterInternal(final HttpServletRequest req, final HttpServletResponse res, final FilterChain chain) throws IOException, ServletException { final String header = req.getHeader(SecurityConstants.HEADER_STRING); if (header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) { chain.doFilter(req, res); return; } final UsernamePasswordAuthenticationToken authentication = this.getAuthentication(req); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(req, res); } // Reads the JWT from the Authorization header, and then uses JWT to // validate the token private UsernamePasswordAuthenticationToken getAuthentication(final HttpServletRequest request) { final String token = request.getHeader(SecurityConstants.HEADER_STRING); if (token != null) { // parse the token. final String user = JWT.require(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes())).build() .verify(token.replace(SecurityConstants.TOKEN_PREFIX, "")).getSubject(); if (user != null) return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>()); return null; } return null; } }
Mes constantes
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 public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class); private final AuthenticationManager authenticationManager; public JWTAuthenticationFilter(final AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; this.setFilterProcessesUrl(SecurityConstants.SIGN_IN_URL); } @Override public Authentication attemptAuthentication(final HttpServletRequest req, final HttpServletResponse res) throws AuthenticationException { try { final ObjectNode node = new ObjectMapper().readValue(req.getInputStream(), ObjectNode.class); final User creds = new User(node.get("username").asText(), node.get("password").asText(), Lists.newArrayList()); return this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(), creds.getPassword(), new ArrayList<>())); } catch (final IOException e) { throw new RuntimeException(e); } } @Override protected void successfulAuthentication(final HttpServletRequest req, final HttpServletResponse res, final FilterChain chain, final Authentication auth) throws IOException { final String token = JWT.create().withSubject(((User) auth.getPrincipal()).getUsername()) .withExpiresAt(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME)) .sign(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes())); final String body = ((User) auth.getPrincipal()).getUsername() + " " + token; res.getWriter().write(body); res.getWriter().flush(); } }
Et mon globalSecurity
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 public class SecurityConstants { public static final String SECRET = "SECRET_KEY"; public static final long EXPIRATION_TIME = 900_000; // 15 mins public static final String TOKEN_PREFIX = "Bearer "; public static final String HEADER_STRING = "Authorization"; public static final String SIGN_IN_URL = "/services/login"; public static final String SIGN_UP_URL = "/services/users"; }
L'idée c'est donc d'avoir mon service /services/login dispo dans mon Swagger-ui pour me générer le token.
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 @Configuration public class GlobalSecurity extends WebSecurityConfigurerAdapter { Logger logger = LoggerFactory.getLogger(GlobalSecurity.class); private final UserDetailsService userDetailsService; public GlobalSecurity(final UserDetailsService userService) { this.userDetailsService = userService; } private static final String[] AUTH_WHITELIST = { // -- Swagger UI v2 "/v2/api-docs", "/swagger-resources", "/swagger-resources/**", "/configuration/ui", "/configuration/security", "/swagger-ui.html", "/webjars/**", // -- Swagger UI v3 (OpenAPI) "/v3/api-docs/**", "/swagger-ui/**", // On autorise l'url qui génére le token SecurityConstants.SIGN_IN_URL }; @Override protected void configure(final HttpSecurity http) throws Exception { http. // whitelist Swagger UI resources authorizeRequests().antMatchers(AUTH_WHITELIST).permitAll() // Ajout de la partie JWT .and() // On autorise les requetes pour créer un user .authorizeRequests().antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll() // Toutes les autres doivent être authentifiées .anyRequest().authenticated().and() // On ajoute les filtres JWT .addFilter(new JWTAuthenticationFilter(this.authenticationManager())) .addFilter(new JWTAuthorizationFilter(this.authenticationManager())) // this disables session creation on Spring Security .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); ; } @Override public void configure(final AuthenticationManagerBuilder auth) throws Exception { this.logger.debug("Message d elog"); auth.userDetailsService(this.userDetailsService); } @Bean CorsConfigurationSource corsConfigurationSource() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues(); source.registerCorsConfiguration("/**", corsConfiguration); return source; } }
PS : je précise que j'utilise un utilisateur défini dans les properties de Spring pour générer le Token (pas de BDD derrière)
Merci bcp pour vos retours !
Partager