diff --git a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java index 9e3635041..f43f8ada7 100644 --- a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java +++ b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java @@ -11,19 +11,18 @@ @PreAuthorize("isAuthenticated()") public class GreetingController { - @GetMapping("/greet") - public MessageDto getGreeting(OAuthentication auth) { - return new MessageDto("Hi %s! You are granted with: %s and your email is %s." - .formatted(auth.getName(), auth.getAuthorities(), auth.getClaims().getEmail())); - } + @GetMapping("/greet") + public MessageDto getGreeting(OAuthentication auth) { + return new MessageDto( + "Hi %s! You are granted with: %s and your email is %s.".formatted(auth.getName(), auth.getAuthorities(), auth.getClaims().getEmail())); + } - @GetMapping("/nice") - @PreAuthorize("hasAuthority('NICE')") - public MessageDto getNiceGreeting(OAuthentication auth) { - return new MessageDto("Dear %s! You are granted with: %s." - .formatted(auth.getName(), auth.getAuthorities())); - } + @GetMapping("/nice") + @PreAuthorize("hasAuthority('NICE')") + public MessageDto getNiceGreeting(OAuthentication auth) { + return new MessageDto("Dear %s! You are granted with: %s.".formatted(auth.getName(), auth.getAuthorities())); + } - static record MessageDto(String body) { - } + static record MessageDto(String body) { + } } diff --git a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerMultitenantDynamicApplication.java b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerMultitenantDynamicApplication.java index 2dbe1285c..f5a094fa0 100644 --- a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerMultitenantDynamicApplication.java +++ b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerMultitenantDynamicApplication.java @@ -9,13 +9,19 @@ import io.swagger.v3.oas.annotations.security.OAuthScope; import io.swagger.v3.oas.annotations.security.SecurityScheme; -@SecurityScheme(name = "authorization-code", type = SecuritySchemeType.OAUTH2, flows = @OAuthFlows(authorizationCode = @OAuthFlow(authorizationUrl = "https://localhost:8443/realms/master/protocol/openid-connect/auth", tokenUrl = "https://localhost:8443/realms/master/protocol/openid-connect/token", scopes = { - @OAuthScope(name = "openid"), @OAuthScope(name = "profile") }))) +@SecurityScheme( + name = "authorization-code", + type = SecuritySchemeType.OAUTH2, + flows = @OAuthFlows( + authorizationCode = @OAuthFlow( + authorizationUrl = "https://localhost:8443/realms/master/protocol/openid-connect/auth", + tokenUrl = "https://localhost:8443/realms/master/protocol/openid-connect/token", + scopes = { @OAuthScope(name = "openid"), @OAuthScope(name = "profile") }))) @SpringBootApplication public class ResourceServerMultitenantDynamicApplication { - public static void main(String[] args) { - SpringApplication.run(ResourceServerMultitenantDynamicApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(ResourceServerMultitenantDynamicApplication.class, args); + } } diff --git a/samples/tutorials/resource-server_multitenant_dynamic/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java b/samples/tutorials/resource-server_multitenant_dynamic/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java index 059a92361..7b5f10bd4 100644 --- a/samples/tutorials/resource-server_multitenant_dynamic/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java +++ b/samples/tutorials/resource-server_multitenant_dynamic/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java @@ -19,41 +19,40 @@ @Import(WebSecurityConfig.class) class GreetingControllerTest { - @Autowired - MockMvcSupport api; - - @Test - @OpenId(authorities = { - "AUTHOR" }, claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) - void givenUserIsAuthenticated_whenGreet_thenOk() throws Exception { - api.get("/greet").andExpect(status().isOk()) - .andExpect(jsonPath("$.body").value( - "Hi Tonton Pirate! You are granted with: [AUTHOR] and your email is ch4mp@c4-soft.com.")); - } - - @Test - void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { - api.get("/greet").andExpect(status().isUnauthorized()); - } - - @Test - @OpenId(authorities = { "NICE", - "AUTHOR" }, claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) - void givenUserIsGrantedWithNice_whenGetNice_thenOk() throws Exception { - api.get("/nice").andExpect(status().isOk()) - .andExpect(jsonPath("$.body").value( - "Dear Tonton Pirate! You are granted with: [NICE, AUTHOR].")); - } - - @Test - @OpenId(authorities = { "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate")) - void givenUserIsNotGrantedWithNice_whenGetNice_thenForbidden() throws Exception { - api.get("/nice").andExpect(status().isForbidden()); - } - - @Test - void givenRequestIsAnonymous_whenGetNice_thenUnauthorized() throws Exception { - api.get("/nice").andExpect(status().isUnauthorized()); - } + @Autowired + MockMvcSupport api; + + @Test + @OpenId( + authorities = { "AUTHOR" }, + claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) + void givenUserIsAuthenticated_whenGreet_thenOk() throws Exception { + api.get("/greet").andExpect(status().isOk()) + .andExpect(jsonPath("$.body").value("Hi Tonton Pirate! You are granted with: [AUTHOR] and your email is ch4mp@c4-soft.com.")); + } + + @Test + void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { + api.get("/greet").andExpect(status().isUnauthorized()); + } + + @Test + @OpenId( + authorities = { "NICE", "AUTHOR" }, + claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) + void givenUserIsGrantedWithNice_whenGetNice_thenOk() throws Exception { + api.get("/nice").andExpect(status().isOk()).andExpect(jsonPath("$.body").value("Dear Tonton Pirate! You are granted with: [NICE, AUTHOR].")); + } + + @Test + @OpenId(authorities = { "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate")) + void givenUserIsNotGrantedWithNice_whenGetNice_thenForbidden() throws Exception { + api.get("/nice").andExpect(status().isForbidden()); + } + + @Test + void givenRequestIsAnonymous_whenGetNice_thenUnauthorized() throws Exception { + api.get("/nice").andExpect(status().isUnauthorized()); + } } diff --git a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java index c1c492323..55699ed83 100644 --- a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java +++ b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java @@ -10,14 +10,12 @@ @RequestMapping("/greet") public class GreetingController { - @GetMapping() - @PreAuthorize("hasAuthority('NICE')") - public MessageDto getGreeting(Authentication auth) { - return new MessageDto("Hi %s! You are granted with: %s.".formatted( - auth.getName(), - auth.getAuthorities())); - } + @GetMapping() + @PreAuthorize("hasAuthority('NICE')") + public MessageDto getGreeting(Authentication auth) { + return new MessageDto("Hi %s! You are granted with: %s.".formatted(auth.getName(), auth.getAuthorities())); + } - static record MessageDto(String body) { - } + static record MessageDto(String body) { + } } diff --git a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java index ebaf6792b..c7f9bf896 100644 --- a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java +++ b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java @@ -6,8 +6,8 @@ @SpringBootApplication public class ResourceServerWithOAuthenticationApplication { - public static void main(String[] args) { - SpringApplication.run(ResourceServerWithOAuthenticationApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(ResourceServerWithOAuthenticationApplication.class, args); + } } diff --git a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java index 79426b545..29c10068a 100644 --- a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java +++ b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java @@ -30,41 +30,38 @@ @EnableMethodSecurity public class WebSecurityConfig { - @Bean - @Profile("oauthentication") - // This bean is optional as a default one is provided (building a - // BearerAuthenticationToken) - OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter( - Converter, Collection> authoritiesConverter) { - return (String introspectedToken, - OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> new OAuthentication<>( - new OpenidClaimSet(authenticatedPrincipal.getAttributes()), - authoritiesConverter.convert(authenticatedPrincipal.getAttributes()), - introspectedToken); - } + @Bean + @Profile("oauthentication") + // This bean is optional as a default one is provided (building a + // BearerAuthenticationToken) + OpaqueTokenAuthenticationConverter + introspectionAuthenticationConverter(Converter, Collection> authoritiesConverter) { + return (String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> new OAuthentication<>( + new OpenidClaimSet(authenticatedPrincipal.getAttributes()), + authoritiesConverter.convert(authenticatedPrincipal.getAttributes()), + introspectedToken); + } - @Component - @Profile("auth0 | cognito") - public static class UserEndpointOpaqueTokenIntrospector implements OpaqueTokenIntrospector { - private final URI userinfoUri; - private final RestTemplate restClient = new RestTemplate(); + @Component + @Profile("auth0 | cognito") + public static class UserEndpointOpaqueTokenIntrospector implements OpaqueTokenIntrospector { + private final URI userinfoUri; + private final RestTemplate restClient = new RestTemplate(); - public UserEndpointOpaqueTokenIntrospector(OAuth2ResourceServerProperties oauth2Properties) - throws IOException { - userinfoUri = URI.create(oauth2Properties.getOpaquetoken().getIntrospectionUri()); - } + public UserEndpointOpaqueTokenIntrospector(OAuth2ResourceServerProperties oauth2Properties) throws IOException { + userinfoUri = URI.create(oauth2Properties.getOpaquetoken().getIntrospectionUri()); + } - @Override - @SuppressWarnings("unchecked") - public OAuth2AuthenticatedPrincipal introspect(String token) { - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(token); - final var claims = new OpenidClaimSet(restClient - .exchange(userinfoUri, HttpMethod.GET, new HttpEntity<>(headers), Map.class).getBody()); - // No need to map authorities there, it is done later by - // OpaqueTokenAuthenticationConverter - return new OAuth2IntrospectionAuthenticatedPrincipal(claims, List.of()); - } + @Override + @SuppressWarnings("unchecked") + public OAuth2AuthenticatedPrincipal introspect(String token) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(token); + final var claims = new OpenidClaimSet(restClient.exchange(userinfoUri, HttpMethod.GET, new HttpEntity<>(headers), Map.class).getBody()); + // No need to map authorities there, it is done later by + // OpaqueTokenAuthenticationConverter + return new OAuth2IntrospectionAuthenticatedPrincipal(claims, List.of()); + } - } + } } \ No newline at end of file diff --git a/samples/tutorials/resource-server_with_introspection/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java b/samples/tutorials/resource-server_with_introspection/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java index 405a92314..4c1717740 100644 --- a/samples/tutorials/resource-server_with_introspection/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java +++ b/samples/tutorials/resource-server_with_introspection/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java @@ -19,10 +19,10 @@ @Import(WebSecurityConfig.class) class GreetingControllerTest { - @Autowired - MockMvcSupport mockMvc; + @Autowired + MockMvcSupport mockMvc; - // @formatter:off + // @formatter:off @Test @OpenId( authorities = { "NICE", "AUTHOR" }, @@ -34,15 +34,15 @@ void givenUserIsGrantedWithNice_whenGreet_thenOk() throws Exception { } // @formatter:on - @Test - @OpenId(authorities = "AUTHOR", claims = @OpenIdClaims(preferredUsername = "Tonton Pirate")) - void givenUserIsNotGrantedWithNice_whenGreet_thenForbidden() throws Exception { - mockMvc.get("/greet").andExpect(status().isForbidden()); - } + @Test + @OpenId(authorities = "AUTHOR", claims = @OpenIdClaims(preferredUsername = "Tonton Pirate")) + void givenUserIsNotGrantedWithNice_whenGreet_thenForbidden() throws Exception { + mockMvc.get("/greet").andExpect(status().isForbidden()); + } - @Test - void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { - mockMvc.get("/greet").andExpect(status().isUnauthorized()); - } + @Test + void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { + mockMvc.get("/greet").andExpect(status().isUnauthorized()); + } } diff --git a/samples/tutorials/resource-server_with_introspection/src/test/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplicationTests.java b/samples/tutorials/resource-server_with_introspection/src/test/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplicationTests.java index 2c167e4ae..eb73be32c 100644 --- a/samples/tutorials/resource-server_with_introspection/src/test/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplicationTests.java +++ b/samples/tutorials/resource-server_with_introspection/src/test/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplicationTests.java @@ -20,21 +20,21 @@ @AutoConfigureMockMvc @ImportAutoConfiguration({ AddonsWebmvcTestConf.class }) class ResourceServerWithOAuthenticationApplicationTests { - @Autowired - MockMvcSupport api; + @Autowired + MockMvcSupport api; - @Test - void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { - api.get("/greet").andExpect(status().isUnauthorized()); - } + @Test + void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { + api.get("/greet").andExpect(status().isUnauthorized()); + } - @Test - @OpenId() - void givenUserIsNotGrantedWithNice_whenGreet_thenForbidden() throws Exception { - api.get("/greet").andExpect(status().isForbidden()); - } + @Test + @OpenId() + void givenUserIsNotGrantedWithNice_whenGreet_thenForbidden() throws Exception { + api.get("/greet").andExpect(status().isForbidden()); + } - // @formatter:off + // @formatter:off @Test @OpenId( authorities = { "NICE", "AUTHOR" }, diff --git a/samples/tutorials/resource-server_with_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java b/samples/tutorials/resource-server_with_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java index 9e3635041..f43f8ada7 100644 --- a/samples/tutorials/resource-server_with_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java +++ b/samples/tutorials/resource-server_with_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java @@ -11,19 +11,18 @@ @PreAuthorize("isAuthenticated()") public class GreetingController { - @GetMapping("/greet") - public MessageDto getGreeting(OAuthentication auth) { - return new MessageDto("Hi %s! You are granted with: %s and your email is %s." - .formatted(auth.getName(), auth.getAuthorities(), auth.getClaims().getEmail())); - } + @GetMapping("/greet") + public MessageDto getGreeting(OAuthentication auth) { + return new MessageDto( + "Hi %s! You are granted with: %s and your email is %s.".formatted(auth.getName(), auth.getAuthorities(), auth.getClaims().getEmail())); + } - @GetMapping("/nice") - @PreAuthorize("hasAuthority('NICE')") - public MessageDto getNiceGreeting(OAuthentication auth) { - return new MessageDto("Dear %s! You are granted with: %s." - .formatted(auth.getName(), auth.getAuthorities())); - } + @GetMapping("/nice") + @PreAuthorize("hasAuthority('NICE')") + public MessageDto getNiceGreeting(OAuthentication auth) { + return new MessageDto("Dear %s! You are granted with: %s.".formatted(auth.getName(), auth.getAuthorities())); + } - static record MessageDto(String body) { - } + static record MessageDto(String body) { + } } diff --git a/samples/tutorials/resource-server_with_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java b/samples/tutorials/resource-server_with_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java index b0bc90c4f..4bb0f90b4 100644 --- a/samples/tutorials/resource-server_with_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java +++ b/samples/tutorials/resource-server_with_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java @@ -27,40 +27,43 @@ import io.swagger.v3.oas.annotations.security.OAuthScope; import io.swagger.v3.oas.annotations.security.SecurityScheme; -@SecurityScheme(name = "authorization-code", type = SecuritySchemeType.OAUTH2, flows = @OAuthFlows(authorizationCode = @OAuthFlow(authorizationUrl = "https://localhost:8443/realms/master/protocol/openid-connect/auth", tokenUrl = "https://localhost:8443/realms/master/protocol/openid-connect/token", scopes = { - @OAuthScope(name = "openid"), @OAuthScope(name = "profile") }))) +@SecurityScheme( + name = "authorization-code", + type = SecuritySchemeType.OAUTH2, + flows = @OAuthFlows( + authorizationCode = @OAuthFlow( + authorizationUrl = "https://localhost:8443/realms/master/protocol/openid-connect/auth", + tokenUrl = "https://localhost:8443/realms/master/protocol/openid-connect/token", + scopes = { @OAuthScope(name = "openid"), @OAuthScope(name = "profile") }))) @SpringBootApplication public class ResourceServerWithOAuthenticationApplication { - public static void main(String[] args) { - SpringApplication.run(ResourceServerWithOAuthenticationApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(ResourceServerWithOAuthenticationApplication.class, args); + } - @Configuration - @EnableMethodSecurity - public static class SecurityConfig { - @Bean - OAuth2AuthenticationFactory authenticationFactory( - Converter, Collection> authoritiesConverter, - SpringAddonsSecurityProperties addonsProperties) { - return (bearerString, claims) -> new OAuthentication<>( - new OpenidClaimSet( - claims, - addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)) - .getUsernameClaim()), - authoritiesConverter.convert(claims), - bearerString); - } + @Configuration + @EnableMethodSecurity + public static class SecurityConfig { + @Bean + OAuth2AuthenticationFactory authenticationFactory( + Converter, Collection> authoritiesConverter, + SpringAddonsSecurityProperties addonsProperties) { + return (bearerString, claims) -> new OAuthentication<>( + new OpenidClaimSet(claims, addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)).getUsernameClaim()), + authoritiesConverter.convert(claims), + bearerString); + } - @Bean - ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { - // @formatter:off + @Bean + ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { + // @formatter:off return (AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) -> registry .requestMatchers(HttpMethod.GET, "/actuator/**").hasAuthority("OBSERVABILITY:read") .requestMatchers("/actuator/**").hasAuthority("OBSERVABILITY:write") .anyRequest().authenticated(); // @formatter:on - } - } + } + } } diff --git a/samples/tutorials/resource-server_with_oauthentication/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java b/samples/tutorials/resource-server_with_oauthentication/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java index 116f7eba3..5f4023df7 100644 --- a/samples/tutorials/resource-server_with_oauthentication/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java +++ b/samples/tutorials/resource-server_with_oauthentication/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java @@ -20,41 +20,40 @@ @Import(SecurityConfig.class) class GreetingControllerTest { - @Autowired - MockMvcSupport api; - - @Test - @OpenId(authorities = { - "AUTHOR" }, claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) - void givenUserIsAuthenticated_whenGreet_thenOk() throws Exception { - api.get("/greet").andExpect(status().isOk()) - .andExpect(jsonPath("$.body").value( - "Hi Tonton Pirate! You are granted with: [AUTHOR] and your email is ch4mp@c4-soft.com.")); - } - - @Test - void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { - api.get("/greet").andExpect(status().isUnauthorized()); - } - - @Test - @OpenId(authorities = { "NICE", - "AUTHOR" }, claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) - void givenUserIsGrantedWithNice_whenGetNice_thenOk() throws Exception { - api.get("/nice").andExpect(status().isOk()) - .andExpect(jsonPath("$.body").value( - "Dear Tonton Pirate! You are granted with: [NICE, AUTHOR].")); - } - - @Test - @OpenId(authorities = { "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate")) - void givenUserIsNotGrantedWithNice_whenGetNice_thenForbidden() throws Exception { - api.get("/nice").andExpect(status().isForbidden()); - } - - @Test - void givenRequestIsAnonymous_whenGetNice_thenUnauthorized() throws Exception { - api.get("/nice").andExpect(status().isUnauthorized()); - } + @Autowired + MockMvcSupport api; + + @Test + @OpenId( + authorities = { "AUTHOR" }, + claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) + void givenUserIsAuthenticated_whenGreet_thenOk() throws Exception { + api.get("/greet").andExpect(status().isOk()) + .andExpect(jsonPath("$.body").value("Hi Tonton Pirate! You are granted with: [AUTHOR] and your email is ch4mp@c4-soft.com.")); + } + + @Test + void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { + api.get("/greet").andExpect(status().isUnauthorized()); + } + + @Test + @OpenId( + authorities = { "NICE", "AUTHOR" }, + claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) + void givenUserIsGrantedWithNice_whenGetNice_thenOk() throws Exception { + api.get("/nice").andExpect(status().isOk()).andExpect(jsonPath("$.body").value("Dear Tonton Pirate! You are granted with: [NICE, AUTHOR].")); + } + + @Test + @OpenId(authorities = { "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate")) + void givenUserIsNotGrantedWithNice_whenGetNice_thenForbidden() throws Exception { + api.get("/nice").andExpect(status().isForbidden()); + } + + @Test + void givenRequestIsAnonymous_whenGetNice_thenUnauthorized() throws Exception { + api.get("/nice").andExpect(status().isUnauthorized()); + } } diff --git a/samples/tutorials/resource-server_with_oauthentication/src/test/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplicationTests.java b/samples/tutorials/resource-server_with_oauthentication/src/test/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplicationTests.java index 59761f1d7..a92a7057f 100644 --- a/samples/tutorials/resource-server_with_oauthentication/src/test/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplicationTests.java +++ b/samples/tutorials/resource-server_with_oauthentication/src/test/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplicationTests.java @@ -22,73 +22,72 @@ @AutoConfigureMockMvc @ImportAutoConfiguration({ AddonsWebmvcTestConf.class }) class ResourceServerWithOAuthenticationApplicationTests { - @Autowired - MockMvcSupport api; - - @Test - void givenRequestIsAnonymous_whenGetActuatorHealthLiveness_thenOk() throws Exception { - api.get("/actuator/health/liveness").andExpect(status().isOk()).andExpect(jsonPath("$.status").value("UP")); - } - - @Test - void givenRequestIsAnonymous_whenGetActuatorHealthReadiness_thenOk() throws Exception { - api.get("/actuator/health/readiness").andExpect(status().isOk()); - } - - @Test - void givenRequestIsAnonymous_whenGetActuator_thenUnauthorized() throws Exception { - api.get("/actuator").andExpect(status().isUnauthorized()); - } - - @Test - @OpenId("OBSERVABILITY:read") - void givenUserIsGrantedWithObservabilityRead_whenGetActuator_thenOk() throws Exception { - api.get("/actuator").andExpect(status().isOk()); - } - - @Test - @OpenId("OBSERVABILITY:write") - void givenUserIsGrantedWithObservabilityWrite_whenPostActuatorShutdown_thenOk() throws Exception { - api.post(Map.of("configuredLevel", "debug"), "/actuator/loggers/com.c4soft") - .andExpect(status().is2xxSuccessful()); - } - - @Test - @OpenId("OBSERVABILITY:read") - void givenUserIsNotGrantedWithObservabilityWrite_whenPostActuatorShutdown_thenForbidden() throws Exception { - api.post(Map.of("configuredLevel", "debug"), "/actuator/loggers/com.c4soft").andExpect(status().isForbidden()); - } - - @Test - @OpenId(authorities = "AUTHOR", claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) - void givenUserIsAuthenticated_whenGreet_thenOk() throws Exception { - api.get("/greet").andExpect(status().isOk()) - .andExpect(jsonPath("$.body").value( - "Hi Tonton Pirate! You are granted with: [AUTHOR] and your email is ch4mp@c4-soft.com.")); - } - - @Test - void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { - api.get("/greet").andExpect(status().isUnauthorized()); - } - - @Test - @OpenId(authorities = { "NICE", - "AUTHOR" }, claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) - void givenUserIsGrantedWithNice_whenGetNice_thenOk() throws Exception { - api.get("/nice").andExpect(status().isOk()) - .andExpect(jsonPath("$.body").value( - "Dear Tonton Pirate! You are granted with: [NICE, AUTHOR].")); - } - - @Test - @OpenId(authorities = { "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate")) - void givenUserIsNotGrantedWithNice_whenGetNice_thenForbidden() throws Exception { - api.get("/nice").andExpect(status().isForbidden()); - } - - @Test - void givenRequestIsAnonymous_whenGetNice_thenUnauthorized() throws Exception { - api.get("/nice").andExpect(status().isUnauthorized()); - } + @Autowired + MockMvcSupport api; + + @Test + void givenRequestIsAnonymous_whenGetActuatorHealthLiveness_thenOk() throws Exception { + api.get("/actuator/health/liveness").andExpect(status().isOk()).andExpect(jsonPath("$.status").value("UP")); + } + + @Test + void givenRequestIsAnonymous_whenGetActuatorHealthReadiness_thenOk() throws Exception { + api.get("/actuator/health/readiness").andExpect(status().isOk()); + } + + @Test + void givenRequestIsAnonymous_whenGetActuator_thenUnauthorized() throws Exception { + api.get("/actuator").andExpect(status().isUnauthorized()); + } + + @Test + @OpenId("OBSERVABILITY:read") + void givenUserIsGrantedWithObservabilityRead_whenGetActuator_thenOk() throws Exception { + api.get("/actuator").andExpect(status().isOk()); + } + + @Test + @OpenId("OBSERVABILITY:write") + void givenUserIsGrantedWithObservabilityWrite_whenPostActuatorShutdown_thenOk() throws Exception { + api.post(Map.of("configuredLevel", "debug"), "/actuator/loggers/com.c4soft").andExpect(status().is2xxSuccessful()); + } + + @Test + @OpenId("OBSERVABILITY:read") + void givenUserIsNotGrantedWithObservabilityWrite_whenPostActuatorShutdown_thenForbidden() throws Exception { + api.post(Map.of("configuredLevel", "debug"), "/actuator/loggers/com.c4soft").andExpect(status().isForbidden()); + } + + @Test + @OpenId( + authorities = "AUTHOR", + claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) + void givenUserIsAuthenticated_whenGreet_thenOk() throws Exception { + api.get("/greet").andExpect(status().isOk()) + .andExpect(jsonPath("$.body").value("Hi Tonton Pirate! You are granted with: [AUTHOR] and your email is ch4mp@c4-soft.com.")); + } + + @Test + void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception { + api.get("/greet").andExpect(status().isUnauthorized()); + } + + @Test + @OpenId( + authorities = { "NICE", "AUTHOR" }, + claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com")) + void givenUserIsGrantedWithNice_whenGetNice_thenOk() throws Exception { + api.get("/nice").andExpect(status().isOk()).andExpect(jsonPath("$.body").value("Dear Tonton Pirate! You are granted with: [NICE, AUTHOR].")); + } + + @Test + @OpenId(authorities = { "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate")) + void givenUserIsNotGrantedWithNice_whenGetNice_thenForbidden() throws Exception { + api.get("/nice").andExpect(status().isForbidden()); + } + + @Test + void givenRequestIsAnonymous_whenGetNice_thenUnauthorized() throws Exception { + api.get("/nice").andExpect(status().isUnauthorized()); + } } diff --git a/samples/tutorials/resource-server_with_specialized_oauthentication/README.md b/samples/tutorials/resource-server_with_specialized_oauthentication/README.md index 49db0e8d3..c69a21199 100644 --- a/samples/tutorials/resource-server_with_specialized_oauthentication/README.md +++ b/samples/tutorials/resource-server_with_specialized_oauthentication/README.md @@ -154,7 +154,7 @@ public class WebSecurityConfig { } @Bean - MethodSecurityExpressionHandler methodSecurityExpressionHandler() { + static MethodSecurityExpressionHandler methodSecurityExpressionHandler() { return new C4MethodSecurityExpressionHandler(ProxiesMethodSecurityExpressionRoot::new); } diff --git a/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ProxiesAuthentication.java b/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ProxiesAuthentication.java index 814dbfd6c..a0c62c8bc 100644 --- a/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ProxiesAuthentication.java +++ b/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ProxiesAuthentication.java @@ -14,25 +14,23 @@ @Data @EqualsAndHashCode(callSuper = true) public class ProxiesAuthentication extends OAuthentication { - private static final long serialVersionUID = -6247121748050239792L; - - public ProxiesAuthentication(ProxiesClaimSet claims, Collection authorities, - String tokenString) { - super(claims, authorities, tokenString); - } - - @Override - public String getName() { - return Optional.ofNullable(super.getAttributes().getPreferredUsername()) - .orElse(super.getAttributes().getSubject()); - } - - public boolean hasName(String username) { - return Objects.equals(getName(), username); - } - - public Proxy getProxyFor(String username) { - return getAttributes().getProxyFor(username); - } + private static final long serialVersionUID = -6247121748050239792L; + + public ProxiesAuthentication(ProxiesClaimSet claims, Collection authorities, String tokenString) { + super(claims, authorities, tokenString); + } + + @Override + public String getName() { + return Optional.ofNullable(super.getAttributes().getPreferredUsername()).orElse(super.getAttributes().getSubject()); + } + + public boolean hasName(String username) { + return Objects.equals(getName(), username); + } + + public Proxy getProxyFor(String username) { + return getAttributes().getProxyFor(username); + } } diff --git a/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ProxiesClaimSet.java b/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ProxiesClaimSet.java index 55740f019..26b6c5d32 100644 --- a/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ProxiesClaimSet.java +++ b/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/ProxiesClaimSet.java @@ -38,10 +38,7 @@ public Proxy getProxyFor(String username) { if (proxiesClaim == null) { return Map.of(); } - return proxiesClaim - .entrySet() - .stream() - .map(e -> new Proxy(e.getKey(), claims.getPreferredUsername(), e.getValue())) + return proxiesClaim.entrySet().stream().map(e -> new Proxy(e.getKey(), claims.getPreferredUsername(), e.getValue())) .collect(Collectors.toMap(Proxy::getProxiedUsername, p -> p)); }; } diff --git a/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/SecurityConfig.java b/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/SecurityConfig.java index 95117eca9..4fc975647 100644 --- a/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/SecurityConfig.java +++ b/samples/tutorials/resource-server_with_specialized_oauthentication/src/main/java/com/c4soft/springaddons/tutorials/SecurityConfig.java @@ -20,33 +20,32 @@ @EnableMethodSecurity public class SecurityConfig { - @Bean - OAuth2AuthenticationFactory authenticationFactory( - Converter, Collection> authoritiesConverter) { - return (bearerString, claims) -> { - final var claimSet = new ProxiesClaimSet(claims); - return new ProxiesAuthentication(claimSet, authoritiesConverter.convert(claimSet), bearerString); - }; - } - - @Bean - MethodSecurityExpressionHandler methodSecurityExpressionHandler() { - return new C4MethodSecurityExpressionHandler(ProxiesMethodSecurityExpressionRoot::new); - } - - static final class ProxiesMethodSecurityExpressionRoot extends C4MethodSecurityExpressionRoot { - - public boolean is(String preferredUsername) { - return Objects.equals(preferredUsername, getAuthentication().getName()); - } - - public Proxy onBehalfOf(String proxiedUsername) { - return get(ProxiesAuthentication.class).map(a -> a.getProxyFor(proxiedUsername)) - .orElse(new Proxy(proxiedUsername, getAuthentication().getName(), List.of())); - } - - public boolean isNice() { - return hasAnyAuthority("NICE", "SUPER_COOL"); - } - } + @Bean + OAuth2AuthenticationFactory authenticationFactory(Converter, Collection> authoritiesConverter) { + return (bearerString, claims) -> { + final var claimSet = new ProxiesClaimSet(claims); + return new ProxiesAuthentication(claimSet, authoritiesConverter.convert(claimSet), bearerString); + }; + } + + @Bean + static MethodSecurityExpressionHandler methodSecurityExpressionHandler() { + return new C4MethodSecurityExpressionHandler(ProxiesMethodSecurityExpressionRoot::new); + } + + static final class ProxiesMethodSecurityExpressionRoot extends C4MethodSecurityExpressionRoot { + + public boolean is(String preferredUsername) { + return Objects.equals(preferredUsername, getAuthentication().getName()); + } + + public Proxy onBehalfOf(String proxiedUsername) { + return get(ProxiesAuthentication.class).map(a -> a.getProxyFor(proxiedUsername)) + .orElse(new Proxy(proxiedUsername, getAuthentication().getName(), List.of())); + } + + public boolean isNice() { + return hasAnyAuthority("NICE", "SUPER_COOL"); + } + } } \ No newline at end of file diff --git a/samples/tutorials/resource-server_with_ui/src/main/java/com/c4soft/springaddons/tutorials/WebClientConfig.java b/samples/tutorials/resource-server_with_ui/src/main/java/com/c4soft/springaddons/tutorials/WebClientConfig.java index 676aea572..711f16f64 100644 --- a/samples/tutorials/resource-server_with_ui/src/main/java/com/c4soft/springaddons/tutorials/WebClientConfig.java +++ b/samples/tutorials/resource-server_with_ui/src/main/java/com/c4soft/springaddons/tutorials/WebClientConfig.java @@ -10,19 +10,18 @@ @Configuration public class WebClientConfig { - /** - * By default, WebClient expects reactive OAuth2 configuration. This bridges from ClientRegistrationRepository to - * ReactiveClientRegistrationRepository - * - * @param clientRegistrationRepository - * @param authorizedClientService - * @return - */ - @Bean - WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) { - var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService); - var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); - return WebClient.builder().apply(oauth.oauth2Configuration()).build(); - } + /** + * By default, WebClient expects reactive OAuth2 configuration. This bridges from ClientRegistrationRepository to ReactiveClientRegistrationRepository + * + * @param clientRegistrationRepository + * @param authorizedClientService + * @return + */ + @Bean + WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) { + var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService); + var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); + return WebClient.builder().apply(oauth.oauth2Configuration()).build(); + } } diff --git a/samples/tutorials/servlet-client/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java b/samples/tutorials/servlet-client/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java index bd8757fcb..56b6eb4cf 100644 --- a/samples/tutorials/servlet-client/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java +++ b/samples/tutorials/servlet-client/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java @@ -64,7 +64,7 @@ public class WebSecurityConfig { clientSecurityFilterChain(HttpSecurity http, InMemoryClientRegistrationRepository clientRegistrationRepository, LogoutProperties logoutProperties) throws Exception { http.addFilterBefore(new LoginPageFilter(), DefaultLoginPageGeneratingFilter.class); - http.oauth2Login(withDefaults()); + http.oauth2Login(withDefaults()); http.logout(logout -> { logout.logoutSuccessHandler(new DelegatingOidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository, logoutProperties, "{baseUrl}")); }); diff --git a/samples/webflux-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java b/samples/webflux-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java index 560ac8b9f..6a13b9881 100644 --- a/samples/webflux-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java +++ b/samples/webflux-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java b/samples/webflux-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java index 48b073af8..15a5c48dd 100644 --- a/samples/webflux-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java +++ b/samples/webflux-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java @@ -11,13 +11,13 @@ @Configuration public class SecurityConfig { - @Bean - public ResourceServerAuthorizeExchangeSpecPostProcessor authorizeExchangeSpecPostProcessor() { - // @formatter:off + @Bean + public ResourceServerAuthorizeExchangeSpecPostProcessor authorizeExchangeSpecPostProcessor() { + // @formatter:off return (ServerHttpSecurity.AuthorizeExchangeSpec spec) -> spec .pathMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL") .anyExchange().authenticated(); // @formatter:on - } + } } \ No newline at end of file diff --git a/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java b/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java index 877c25240..60769563a 100644 --- a/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java +++ b/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; @@ -41,14 +40,14 @@ @Import({ SecurityConfig.class }) // Import your web-security configuration class GreetingControllerAnnotatedTest { - // Mock controller injected dependencies - @MockBean - private MessageService messageService; + // Mock controller injected dependencies + @MockBean + private MessageService messageService; - @Autowired - WebTestClientSupport api; + @Autowired + WebTestClientSupport api; - @BeforeEach + @BeforeEach public void setUp() { when(messageService.greet(any())).thenAnswer(invocation -> { final BearerTokenAuthentication auth = invocation.getArgument(0, BearerTokenAuthentication.class); @@ -57,46 +56,48 @@ public void setUp() { when(messageService.getSecret()).thenReturn(Mono.just("Secret message")); } - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get("https://localhost/greet").expectStatus().isUnauthorized(); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("https://localhost/greet").expectBody(String.class) - .isEqualTo("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, name = "Ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("https://localhost/greet").expectBody(String.class) - .isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { - api.get("https://localhost/secured-route").expectStatus().isForbidden(); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { - api.get("https://localhost/secured-route").expectStatus().isOk(); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { - api.get("https://localhost/secured-method").expectStatus().isForbidden(); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { - api.get("https://localhost/secured-method").expectStatus().isOk(); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get("https://localhost/greet").expectStatus().isUnauthorized(); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("https://localhost/greet").expectBody(String.class).isEqualTo("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } + + @Test + @WithMockAuthentication( + authType = BearerTokenAuthentication.class, + principalType = OAuth2AccessToken.class, + name = "Ch4mpy", + authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("https://localhost/greet").expectBody(String.class).isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { + api.get("https://localhost/secured-route").expectStatus().isForbidden(); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { + api.get("https://localhost/secured-route").expectStatus().isOk(); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { + api.get("https://localhost/secured-method").expectStatus().isForbidden(); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { + api.get("https://localhost/secured-method").expectStatus().isOk(); + } } diff --git a/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java b/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java index 4d60bca3e..c5064dec2 100644 --- a/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java +++ b/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; @@ -87,6 +86,7 @@ void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() thr } private OpaqueTokenMutator ch4mpy() { - return mockOpaqueToken().attributes(attributes -> attributes.put(StandardClaimNames.PREFERRED_USERNAME, "Ch4mpy")).authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")); + return mockOpaqueToken().attributes(attributes -> attributes.put(StandardClaimNames.PREFERRED_USERNAME, "Ch4mpy")) + .authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")); } } diff --git a/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java b/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java index b20492ac8..81520a743 100644 --- a/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java +++ b/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java b/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java index 292653327..9f7817046 100644 --- a/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java +++ b/samples/webflux-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; @@ -34,27 +33,27 @@ @AutoConfigureAddonsSecurity class SecretRepoTest { - // auto-wire tested component - @Autowired - SecretRepo secretRepo; - - @Test - void givenRequestIsAnonymous_whenFindSecretByUsername_thenThrows() { - // call tested components methods directly (do not use MockMvc nor - // WebTestClient) - assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy").block()); - } - - @Test - @WithMockBearerTokenAuthentication(attributes = @OpenIdClaims(preferredUsername = "Tonton Pirate")) - void givenUserIsAuthenticatedAsSomeoneElse_whenFindSecretByUsername_thenThrows() { - assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy").block()); - } - - @Test - @WithMockBearerTokenAuthentication(attributes = @OpenIdClaims(preferredUsername = "ch4mpy")) - void givenUserIsAuthenticatedAsSearchedUser_whenFindSecretByUsername_thenThrows() { - assertEquals("Don't ever tell it", secretRepo.findSecretByUsername("ch4mpy").block()); - } + // auto-wire tested component + @Autowired + SecretRepo secretRepo; + + @Test + void givenRequestIsAnonymous_whenFindSecretByUsername_thenThrows() { + // call tested components methods directly (do not use MockMvc nor + // WebTestClient) + assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy").block()); + } + + @Test + @WithMockBearerTokenAuthentication(attributes = @OpenIdClaims(preferredUsername = "Tonton Pirate")) + void givenUserIsAuthenticatedAsSomeoneElse_whenFindSecretByUsername_thenThrows() { + assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy").block()); + } + + @Test + @WithMockBearerTokenAuthentication(attributes = @OpenIdClaims(preferredUsername = "ch4mpy")) + void givenUserIsAuthenticatedAsSearchedUser_whenFindSecretByUsername_thenThrows() { + assertEquals("Don't ever tell it", secretRepo.findSecretByUsername("ch4mpy").block()); + } } diff --git a/samples/webflux-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java b/samples/webflux-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java index 17a2935ab..59378c82c 100644 --- a/samples/webflux-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java +++ b/samples/webflux-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java b/samples/webflux-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java index da0e8e289..a646fa789 100644 --- a/samples/webflux-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java +++ b/samples/webflux-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java @@ -23,25 +23,24 @@ @Configuration public class SecurityConfig { - @Bean - OAuth2AuthenticationFactory authenticationFactory( - Converter, Collection> authoritiesConverter, - SpringAddonsSecurityProperties addonsProperties) { - return (bearerString, claims) -> Mono.just( - new OAuthentication<>(new OpenidClaimSet( - claims, - addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)) - .getUsernameClaim()), - authoritiesConverter.convert(claims), bearerString)); - } - - @Bean - public ResourceServerAuthorizeExchangeSpecPostProcessor authorizeExchangeSpecPostProcessor() { - // @formatter:off + @Bean + OAuth2AuthenticationFactory authenticationFactory( + Converter, Collection> authoritiesConverter, + SpringAddonsSecurityProperties addonsProperties) { + return (bearerString, claims) -> Mono.just( + new OAuthentication<>( + new OpenidClaimSet(claims, addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)).getUsernameClaim()), + authoritiesConverter.convert(claims), + bearerString)); + } + + @Bean + public ResourceServerAuthorizeExchangeSpecPostProcessor authorizeExchangeSpecPostProcessor() { + // @formatter:off return (ServerHttpSecurity.AuthorizeExchangeSpec spec) -> spec .pathMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL") .anyExchange().authenticated(); // @formatter:on - } + } } \ No newline at end of file diff --git a/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java b/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java index 8050b8d52..0f7f031bc 100644 --- a/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java +++ b/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java b/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java index 0dedf31f1..2a114eea2 100644 --- a/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java +++ b/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java b/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java index a2bf6d27a..6db3f629a 100644 --- a/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java +++ b/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java b/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java index 00d0e7cce..b53ec662a 100644 --- a/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java +++ b/samples/webflux-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java b/samples/webflux-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java index f38dbe465..e5e81b254 100644 --- a/samples/webflux-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java +++ b/samples/webflux-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageService.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java b/samples/webflux-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java index 48b073af8..15a5c48dd 100644 --- a/samples/webflux-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java +++ b/samples/webflux-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecurityConfig.java @@ -11,13 +11,13 @@ @Configuration public class SecurityConfig { - @Bean - public ResourceServerAuthorizeExchangeSpecPostProcessor authorizeExchangeSpecPostProcessor() { - // @formatter:off + @Bean + public ResourceServerAuthorizeExchangeSpecPostProcessor authorizeExchangeSpecPostProcessor() { + // @formatter:off return (ServerHttpSecurity.AuthorizeExchangeSpec spec) -> spec .pathMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL") .anyExchange().authenticated(); // @formatter:on - } + } } \ No newline at end of file diff --git a/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java b/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java index 4c51cbfe2..07e292bd6 100644 --- a/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java +++ b/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; @@ -43,14 +42,14 @@ @Import({ SecurityConfig.class }) // Import your web-security configuration class GreetingControllerAnnotatedTest { - // Mock controller injected dependencies - @MockBean - private MessageService messageService; + // Mock controller injected dependencies + @MockBean + private MessageService messageService; - @Autowired - WebTestClientSupport api; + @Autowired + WebTestClientSupport api; - @BeforeEach + @BeforeEach public void setUp() { when(messageService.greet(any())).thenAnswer(invocation -> { final JwtAuthenticationToken auth = invocation.getArgument(0, JwtAuthenticationToken.class); @@ -59,53 +58,50 @@ public void setUp() { when(messageService.getSecret()).thenReturn(Mono.just("Secret message")); } - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get("https://localhost/greet").expectStatus().isUnauthorized(); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("https://localhost/greet").expectBody(String.class) - .isEqualTo("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, name = "Ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsMockedAsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("https://localhost/greet").expectBody(String.class) - .isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } - - @Test - @WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("https://localhost/greet").expectBody(String.class) - .isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { - api.get("https://localhost/secured-route").expectStatus().isForbidden(); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { - api.get("https://localhost/secured-route").expectStatus().isOk(); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { - api.get("https://localhost/secured-method").expectStatus().isForbidden(); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { - api.get("https://localhost/secured-method").expectStatus().isOk(); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get("https://localhost/greet").expectStatus().isUnauthorized(); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("https://localhost/greet").expectBody(String.class).isEqualTo("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, name = "Ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsMockedAsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("https://localhost/greet").expectBody(String.class).isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } + + @Test + @WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("https://localhost/greet").expectBody(String.class).isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { + api.get("https://localhost/secured-route").expectStatus().isForbidden(); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { + api.get("https://localhost/secured-route").expectStatus().isOk(); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { + api.get("https://localhost/secured-method").expectStatus().isForbidden(); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { + api.get("https://localhost/secured-method").expectStatus().isOk(); + } } diff --git a/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java b/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java index 89919d2f8..ff02c6e50 100644 --- a/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java +++ b/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/GreetingControllerFluentApiTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; @@ -38,13 +37,13 @@ @Import({ SecurityConfig.class }) public class GreetingControllerFluentApiTest { - @MockBean - private MessageService messageService; + @MockBean + private MessageService messageService; - @Autowired - WebTestClientSupport api; + @Autowired + WebTestClientSupport api; - @BeforeEach + @BeforeEach public void setUp() { when(messageService.greet(any())).thenAnswer(invocation -> { final JwtAuthenticationToken auth = invocation.getArgument(0, JwtAuthenticationToken.class); @@ -53,40 +52,38 @@ public void setUp() { when(messageService.getSecret()).thenReturn(Mono.just("Secret message")); } - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get("https://localhost/greet").expectStatus().isUnauthorized(); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get("https://localhost/greet").expectStatus().isUnauthorized(); + } - @Test - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.mutateWith(ch4mpy()).get("https://localhost/greet").expectBody(String.class) - .isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } + @Test + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.mutateWith(ch4mpy()).get("https://localhost/greet").expectBody(String.class) + .isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } - @Test - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { - api.mutateWith(mockAuthentication(JwtAuthenticationToken.class)).get("https://localhost/secured-route") - .expectStatus().isForbidden(); - } + @Test + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { + api.mutateWith(mockAuthentication(JwtAuthenticationToken.class)).get("https://localhost/secured-route").expectStatus().isForbidden(); + } - @Test - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { - api.mutateWith(mockAuthentication(JwtAuthenticationToken.class)).get("https://localhost/secured-method") - .expectStatus().isForbidden(); - } + @Test + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { + api.mutateWith(mockAuthentication(JwtAuthenticationToken.class)).get("https://localhost/secured-method").expectStatus().isForbidden(); + } - @Test - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { - api.mutateWith(ch4mpy()).get("https://localhost/secured-route").expectStatus().isOk(); - } + @Test + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { + api.mutateWith(ch4mpy()).get("https://localhost/secured-route").expectStatus().isOk(); + } - @Test - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { - api.mutateWith(ch4mpy()).get("https://localhost/secured-method").expectStatus().isOk(); - } + @Test + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { + api.mutateWith(ch4mpy()).get("https://localhost/secured-method").expectStatus().isOk(); + } - private MockAuthenticationWebTestClientConfigurer ch4mpy() { - return mockAuthentication(JwtAuthenticationToken.class).name("Ch4mpy").authorities("ROLE_AUTHORIZED_PERSONNEL"); - } + private MockAuthenticationWebTestClientConfigurer ch4mpy() { + return mockAuthentication(JwtAuthenticationToken.class).name("Ch4mpy").authorities("ROLE_AUTHORIZED_PERSONNEL"); + } } diff --git a/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java b/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java index d11971444..ae36871f0 100644 --- a/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java +++ b/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/MessageServiceTests.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java b/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java index 0132bb79b..655384012 100644 --- a/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java +++ b/samples/webflux-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webflux_jwtauthenticationtoken/SecretRepoTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_jwtauthenticationtoken; diff --git a/samples/webflux-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/MessageService.java b/samples/webflux-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/MessageService.java index 383957ca8..8873e044f 100644 --- a/samples/webflux-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/MessageService.java +++ b/samples/webflux-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/MessageService.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_oidcauthentication; diff --git a/samples/webflux-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SecurityConfig.java b/samples/webflux-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SecurityConfig.java index 06dd16687..63a34cfdd 100644 --- a/samples/webflux-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SecurityConfig.java +++ b/samples/webflux-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SecurityConfig.java @@ -23,25 +23,24 @@ @Configuration public class SecurityConfig { - @Bean - OAuth2AuthenticationFactory authenticationFactory( - Converter, Collection> authoritiesConverter, - SpringAddonsSecurityProperties addonsProperties) { - return (bearerString, claims) -> Mono.just( - new OAuthentication<>(new OpenidClaimSet( - claims, - addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)) - .getUsernameClaim()), - authoritiesConverter.convert(claims), bearerString)); - } - - @Bean - public ResourceServerAuthorizeExchangeSpecPostProcessor authorizeExchangeSpecPostProcessor() { - // @formatter:off + @Bean + OAuth2AuthenticationFactory authenticationFactory( + Converter, Collection> authoritiesConverter, + SpringAddonsSecurityProperties addonsProperties) { + return (bearerString, claims) -> Mono.just( + new OAuthentication<>( + new OpenidClaimSet(claims, addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)).getUsernameClaim()), + authoritiesConverter.convert(claims), + bearerString)); + } + + @Bean + public ResourceServerAuthorizeExchangeSpecPostProcessor authorizeExchangeSpecPostProcessor() { + // @formatter:off return (ServerHttpSecurity.AuthorizeExchangeSpec spec) -> spec .pathMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL") .anyExchange().authenticated(); // @formatter:on - } + } } \ No newline at end of file diff --git a/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/GreetingControllerAnnotatedTest.java b/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/GreetingControllerAnnotatedTest.java index c9faa1b6a..46c2500b2 100644 --- a/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/GreetingControllerAnnotatedTest.java +++ b/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/GreetingControllerAnnotatedTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_oidcauthentication; @@ -41,14 +40,14 @@ @Import({ SecurityConfig.class }) // Import your web-security configuration class GreetingControllerAnnotatedTest { - // Mock controller injected dependencies - @MockBean - private MessageService messageService; + // Mock controller injected dependencies + @MockBean + private MessageService messageService; - @Autowired - WebTestClientSupport api; + @Autowired + WebTestClientSupport api; - @BeforeEach + @BeforeEach public void setUp() { when(messageService.greet(any())).thenAnswer(invocation -> { final OAuthentication auth = invocation.getArgument(0, OAuthentication.class); @@ -57,45 +56,44 @@ public void setUp() { when(messageService.getSecret()).thenReturn(Mono.just("Secret message")); } - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get("https://localhost/greet").expectStatus().isUnauthorized(); - } - - @Test - @OpenId() - void givenUserIAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("https://localhost/greet").expectBody(String.class).isEqualTo("Hello user! You are granted with []."); - } - - @Test - @OpenId(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("https://localhost/greet").expectBody(String.class) - .isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } - - @Test - @OpenId() - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { - api.get("https://localhost/secured-route").expectStatus().isForbidden(); - } - - @Test - @OpenId("ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { - api.get("https://localhost/secured-route").expectStatus().isOk(); - } - - @Test - @OpenId() - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { - api.get("https://localhost/secured-method").expectStatus().isForbidden(); - } - - @Test - @OpenId("ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { - api.get("https://localhost/secured-method").expectStatus().isOk(); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get("https://localhost/greet").expectStatus().isUnauthorized(); + } + + @Test + @OpenId() + void givenUserIAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("https://localhost/greet").expectBody(String.class).isEqualTo("Hello user! You are granted with []."); + } + + @Test + @OpenId(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("https://localhost/greet").expectBody(String.class).isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } + + @Test + @OpenId() + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { + api.get("https://localhost/secured-route").expectStatus().isForbidden(); + } + + @Test + @OpenId("ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { + api.get("https://localhost/secured-route").expectStatus().isOk(); + } + + @Test + @OpenId() + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { + api.get("https://localhost/secured-method").expectStatus().isForbidden(); + } + + @Test + @OpenId("ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { + api.get("https://localhost/secured-method").expectStatus().isOk(); + } } diff --git a/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/MessageServiceTests.java b/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/MessageServiceTests.java index 519709fc7..768764e2c 100644 --- a/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/MessageServiceTests.java +++ b/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/MessageServiceTests.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_oidcauthentication; diff --git a/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SampleApiIntegrationTests.java b/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SampleApiIntegrationTests.java index 6658e8635..392281b3f 100644 --- a/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SampleApiIntegrationTests.java +++ b/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SampleApiIntegrationTests.java @@ -16,26 +16,25 @@ @AutoConfigureWebTestClient @ImportAutoConfiguration({ AddonsWebfluxTestConf.class }) class SampleApiIntegrationTests { - @Autowired - WebTestClient api; + @Autowired + WebTestClient api; - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get().uri("https://localhost/greet").exchange().expectStatus().isUnauthorized(); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get().uri("https://localhost/greet").exchange().expectStatus().isUnauthorized(); + } - @Test - @OpenId() - void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get().uri("https://localhost/greet").exchange().expectBody(String.class) - .isEqualTo("Hello user! You are granted with []."); - } + @Test + @OpenId() + void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get().uri("https://localhost/greet").exchange().expectBody(String.class).isEqualTo("Hello user! You are granted with []."); + } - @Test - @OpenId(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(preferredUsername = "Ch4mpy")) - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get().uri("https://localhost/greet").exchange().expectBody(String.class) - .isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } + @Test + @OpenId(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(preferredUsername = "Ch4mpy")) + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get().uri("https://localhost/greet").exchange().expectBody(String.class) + .isEqualTo("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } } diff --git a/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SecretRepoTest.java b/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SecretRepoTest.java index 2eb5f2c9f..0b2c68a70 100644 --- a/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SecretRepoTest.java +++ b/samples/webflux-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webflux_oidcauthentication/SecretRepoTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webflux_oidcauthentication; diff --git a/samples/webmvc-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageService.java b/samples/webmvc-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageService.java index 5f83ca720..7b8d70430 100644 --- a/samples/webmvc-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageService.java +++ b/samples/webmvc-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageService.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; diff --git a/samples/webmvc-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecurityConfig.java b/samples/webmvc-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecurityConfig.java index 43d8c1370..3a474b44a 100644 --- a/samples/webmvc-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecurityConfig.java +++ b/samples/webmvc-introspecting-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecurityConfig.java @@ -11,12 +11,12 @@ @Configuration @EnableMethodSecurity public class SecurityConfig { - @Bean - ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { - // @formatter:off + @Bean + ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { + // @formatter:off return (AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) -> registry .requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL") .anyRequest().authenticated(); // @formatter:on - } + } } \ No newline at end of file diff --git a/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java b/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java index 00adb390c..760e6a4d8 100644 --- a/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java +++ b/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; @@ -43,14 +42,14 @@ @Import({ SecurityConfig.class }) // Import your web-security configuration class GreetingControllerAnnotatedTest { - // Mock controller injected dependencies - @MockBean - private MessageService messageService; + // Mock controller injected dependencies + @MockBean + private MessageService messageService; - @Autowired - MockMvcSupport api; + @Autowired + MockMvcSupport api; - @BeforeEach + @BeforeEach public void setUp() { when(messageService.greet(any())).thenAnswer(invocation -> { final BearerTokenAuthentication auth = invocation.getArgument(0, BearerTokenAuthentication.class); @@ -59,58 +58,60 @@ public void setUp() { when(messageService.getSecret()).thenReturn("Secret message"); } - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get("/greet").andExpect(status().isUnauthorized()); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserHasMockedAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("/greet").andExpect(content().string("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } - - @Test - @WithMockBearerTokenAuthentication() - void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("/greet").andExpect(content().string("Hello user! You are granted with [].")); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, name = "Ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsMockedAsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("/greet") - .andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } - - @Test - @WithMockBearerTokenAuthentication(authorities = "ROLE_AUTHORIZED_PERSONNEL", attributes = @OpenIdClaims(sub = "Ch4mpy")) - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("/greet") - .andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { - api.get("/secured-route").andExpect(status().isForbidden()); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { - api.get("/secured-route").andExpect(status().isOk()); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { - api.get("/secured-method").andExpect(status().isForbidden()); - } - - @Test - @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { - api.get("/secured-method").andExpect(status().isOk()); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get("/greet").andExpect(status().isUnauthorized()); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserHasMockedAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } + + @Test + @WithMockBearerTokenAuthentication() + void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello user! You are granted with [].")); + } + + @Test + @WithMockAuthentication( + authType = BearerTokenAuthentication.class, + principalType = OAuth2AccessToken.class, + name = "Ch4mpy", + authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsMockedAsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } + + @Test + @WithMockBearerTokenAuthentication(authorities = "ROLE_AUTHORIZED_PERSONNEL", attributes = @OpenIdClaims(sub = "Ch4mpy")) + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { + api.get("/secured-route").andExpect(status().isForbidden()); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { + api.get("/secured-route").andExpect(status().isOk()); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { + api.get("/secured-method").andExpect(status().isForbidden()); + } + + @Test + @WithMockAuthentication(authType = BearerTokenAuthentication.class, principalType = OAuth2AccessToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { + api.get("/secured-method").andExpect(status().isOk()); + } } diff --git a/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerFluentApiTest.java b/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerFluentApiTest.java index 7948cfc22..f6132ea93 100644 --- a/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerFluentApiTest.java +++ b/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerFluentApiTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; @@ -40,13 +39,13 @@ @Import({ SecurityConfig.class }) class GreetingControllerFluentApiTest { - @MockBean - private MessageService messageService; + @MockBean + private MessageService messageService; - @Autowired - MockMvcSupport api; + @Autowired + MockMvcSupport api; - @BeforeEach + @BeforeEach public void setUp() { when(messageService.greet(any())).thenAnswer(invocation -> { final BearerTokenAuthentication auth = invocation.getArgument(0, BearerTokenAuthentication.class); @@ -55,48 +54,43 @@ public void setUp() { when(messageService.getSecret()).thenReturn("Secret message"); } - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get("/greet").andExpect(status().isUnauthorized()); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get("/greet").andExpect(status().isUnauthorized()); + } - @Test - void givenUserHasMockedAuthentication_whenGetGreet_thenOk() throws Exception { - api.with(mockAuthentication(BearerTokenAuthentication.class, mock(OAuth2AccessToken.class)).name("user")) - .get("/greet") - .andExpect(content().string("Hello user! You are granted with [].")); - } + @Test + void givenUserHasMockedAuthentication_whenGetGreet_thenOk() throws Exception { + api.with(mockAuthentication(BearerTokenAuthentication.class, mock(OAuth2AccessToken.class)).name("user")).get("/greet") + .andExpect(content().string("Hello user! You are granted with [].")); + } - @Test - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.with(ch4mpy()).get("/greet") - .andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } + @Test + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.with(ch4mpy()).get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } - @Test - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { - api.with(mockAuthentication(BearerTokenAuthentication.class, mock(OAuth2AccessToken.class))) - .get("/secured-route").andExpect(status().isForbidden()); - } + @Test + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { + api.with(mockAuthentication(BearerTokenAuthentication.class, mock(OAuth2AccessToken.class))).get("/secured-route").andExpect(status().isForbidden()); + } - @Test - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { - api.with(mockAuthentication(BearerTokenAuthentication.class, mock(OAuth2AccessToken.class))) - .get("/secured-method").andExpect(status().isForbidden()); - } + @Test + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { + api.with(mockAuthentication(BearerTokenAuthentication.class, mock(OAuth2AccessToken.class))).get("/secured-method").andExpect(status().isForbidden()); + } - @Test - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { - api.with(ch4mpy()).get("/secured-route").andExpect(status().isOk()); - } + @Test + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { + api.with(ch4mpy()).get("/secured-route").andExpect(status().isOk()); + } - @Test - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { - api.with(ch4mpy()).get("/secured-method").andExpect(status().isOk()); - } + @Test + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { + api.with(ch4mpy()).get("/secured-method").andExpect(status().isOk()); + } - private MockAuthenticationRequestPostProcessor ch4mpy() { - return mockAuthentication(BearerTokenAuthentication.class, mock(OAuth2AccessToken.class)).name("Ch4mpy") - .authorities("ROLE_AUTHORIZED_PERSONNEL"); - } + private MockAuthenticationRequestPostProcessor ch4mpy() { + return mockAuthentication(BearerTokenAuthentication.class, mock(OAuth2AccessToken.class)).name("Ch4mpy").authorities("ROLE_AUTHORIZED_PERSONNEL"); + } } diff --git a/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageServiceTests.java b/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageServiceTests.java index 291bf467f..262074fbb 100644 --- a/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageServiceTests.java +++ b/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageServiceTests.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; diff --git a/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecretRepoTest.java b/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecretRepoTest.java index 6247415ee..493977ecc 100644 --- a/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecretRepoTest.java +++ b/samples/webmvc-introspecting-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecretRepoTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; diff --git a/samples/webmvc-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageService.java b/samples/webmvc-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageService.java index 879f38504..3020b77c0 100644 --- a/samples/webmvc-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageService.java +++ b/samples/webmvc-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageService.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; diff --git a/samples/webmvc-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecurityConfig.java b/samples/webmvc-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecurityConfig.java index f59b134ac..76d7ac633 100644 --- a/samples/webmvc-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecurityConfig.java +++ b/samples/webmvc-introspecting-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecurityConfig.java @@ -21,23 +21,22 @@ @Configuration @EnableMethodSecurity public class SecurityConfig { - @Bean - OAuth2AuthenticationFactory authenticationFactory( - Converter, Collection> authoritiesConverter, - SpringAddonsSecurityProperties addonsProperties) { - return (bearerString, claims) -> new OAuthentication<>(new OpenidClaimSet( - claims, - addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)) - .getUsernameClaim()), - authoritiesConverter.convert(claims), bearerString); - } + @Bean + OAuth2AuthenticationFactory authenticationFactory( + Converter, Collection> authoritiesConverter, + SpringAddonsSecurityProperties addonsProperties) { + return (bearerString, claims) -> new OAuthentication<>( + new OpenidClaimSet(claims, addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)).getUsernameClaim()), + authoritiesConverter.convert(claims), + bearerString); + } - @Bean - public ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { - // @formatter:off + @Bean + public ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { + // @formatter:off return (AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) -> registry .requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL") .anyRequest().authenticated(); // @formatter:on - } + } } \ No newline at end of file diff --git a/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerAnnotatedTest.java b/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerAnnotatedTest.java index 127c2905e..4e06b9f80 100644 --- a/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerAnnotatedTest.java +++ b/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerAnnotatedTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; @@ -39,13 +38,13 @@ @Import({ SecurityConfig.class }) class GreetingControllerAnnotatedTest { - @MockBean - private MessageService messageService; + @MockBean + private MessageService messageService; - @Autowired - MockMvcSupport api; + @Autowired + MockMvcSupport api; - @BeforeEach + @BeforeEach public void setUp() { when(messageService.greet(any(OAuthentication.class))).thenAnswer(invocation -> { @SuppressWarnings("unchecked") @@ -55,45 +54,44 @@ public void setUp() { when(messageService.getSecret()).thenReturn("Secret message"); } - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get("/greet").andExpect(status().isUnauthorized()); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get("/greet").andExpect(status().isUnauthorized()); + } - @Test - @OpenId() - void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("/greet").andExpect(content().string("Hello user! You are granted with [].")); - } + @Test + @OpenId() + void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello user! You are granted with [].")); + } - @Test - @OpenId(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("/greet") - .andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } + @Test + @OpenId(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } - @Test - @OpenId() - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { - api.get("/secured-route").andExpect(status().isForbidden()); - } + @Test + @OpenId() + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { + api.get("/secured-route").andExpect(status().isForbidden()); + } - @Test - @OpenId("ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { - api.get("/secured-route").andExpect(status().isOk()); - } + @Test + @OpenId("ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { + api.get("/secured-route").andExpect(status().isOk()); + } - @Test - @OpenId() - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { - api.get("/secured-method").andExpect(status().isForbidden()); - } + @Test + @OpenId() + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { + api.get("/secured-method").andExpect(status().isForbidden()); + } - @Test - @OpenId("ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { - api.get("/secured-method").andExpect(status().isOk()); - } + @Test + @OpenId("ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { + api.get("/secured-method").andExpect(status().isOk()); + } } diff --git a/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerFluentApiTest.java b/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerFluentApiTest.java index d41b53733..155ef369f 100644 --- a/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerFluentApiTest.java +++ b/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerFluentApiTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; diff --git a/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageServiceTests.java b/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageServiceTests.java index 8d38482e7..a3eed5d49 100644 --- a/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageServiceTests.java +++ b/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageServiceTests.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; diff --git a/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecretRepoTest.java b/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecretRepoTest.java index 9d7893b13..2a066d873 100644 --- a/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecretRepoTest.java +++ b/samples/webmvc-introspecting-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecretRepoTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; diff --git a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/MessageService.java b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/MessageService.java index b999cd996..77d9ce174 100644 --- a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/MessageService.java +++ b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/MessageService.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken_jpa_authorities; @@ -29,11 +28,10 @@ public String getSecret() { @PreAuthorize("authenticated") public String greet(JwtAuthenticationToken who) { - return String - .format( - "Hello %s! You are granted with %s.", - who.getToken().getClaimAsString(StandardClaimNames.PREFERRED_USERNAME), - who.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList()); + return String.format( + "Hello %s! You are granted with %s.", + who.getToken().getClaimAsString(StandardClaimNames.PREFERRED_USERNAME), + who.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList()); } } \ No newline at end of file diff --git a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/PersistedGrantedAuthoritiesRetriever.java b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/PersistedGrantedAuthoritiesRetriever.java index c929ce6cf..3bb9332c5 100644 --- a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/PersistedGrantedAuthoritiesRetriever.java +++ b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/PersistedGrantedAuthoritiesRetriever.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken_jpa_authorities; diff --git a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/SecurityConfig.java b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/SecurityConfig.java index ce249cf4d..cf3a75512 100644 --- a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/SecurityConfig.java +++ b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/SecurityConfig.java @@ -12,17 +12,17 @@ @Configuration @EnableMethodSecurity public class SecurityConfig { - @Bean - ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { - // @formatter:off + @Bean + ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { + // @formatter:off return (AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) -> registry .requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL") .anyRequest().authenticated(); // @formatter:on - } + } - @Bean - OAuth2AuthoritiesConverter authoritiesConverter(UserAuthorityRepository authoritiesRepo) { - return new PersistedGrantedAuthoritiesRetriever(authoritiesRepo); - } + @Bean + OAuth2AuthoritiesConverter authoritiesConverter(UserAuthorityRepository authoritiesRepo) { + return new PersistedGrantedAuthoritiesRetriever(authoritiesRepo); + } } \ No newline at end of file diff --git a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthority.java b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthority.java index 3591f1d7e..e76cb7b5d 100644 --- a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthority.java +++ b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthority.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken_jpa_authorities; diff --git a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthorityId.java b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthorityId.java index a92606c8b..66be25053 100644 --- a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthorityId.java +++ b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthorityId.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken_jpa_authorities; diff --git a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthorityRepository.java b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthorityRepository.java index ff54287d7..a5dd977fa 100644 --- a/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthorityRepository.java +++ b/samples/webmvc-jwt-default-jpa-authorities/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/UserAuthorityRepository.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken_jpa_authorities; diff --git a/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/GreetingControllerAnnotatedTest.java b/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/GreetingControllerAnnotatedTest.java index 838d99c4b..011122c62 100644 --- a/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/GreetingControllerAnnotatedTest.java +++ b/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/GreetingControllerAnnotatedTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken_jpa_authorities; @@ -42,19 +41,19 @@ @Import({ SecurityConfig.class }) class GreetingControllerAnnotatedTest { - @MockBean - private MessageService messageService; + @MockBean + private MessageService messageService; - @Autowired - MockMvcSupport api; + @Autowired + MockMvcSupport api; - @MockBean - UserAuthorityRepository userAuthorityRepository; + @MockBean + UserAuthorityRepository userAuthorityRepository; - @MockBean(name = "entityManagerFactory") - EntityManagerFactory entityManagerFactory; + @MockBean(name = "entityManagerFactory") + EntityManagerFactory entityManagerFactory; - @BeforeEach + @BeforeEach public void setUp() { when(messageService.greet(any())).thenAnswer(invocation -> { final JwtAuthenticationToken auth = invocation.getArgument(0, JwtAuthenticationToken.class); @@ -63,58 +62,56 @@ public void setUp() { when(messageService.getSecret()).thenReturn("Secret message"); } - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get("/greet").andExpect(status().isUnauthorized()); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserHasMockedAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("/greet").andExpect(content().string("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } - - @Test - @WithMockJwtAuth() - void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("/greet").andExpect(content().string("Hello user! You are granted with [].")); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, name = "Ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsMockedAsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("/greet") - .andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } - - @Test - @WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("/greet") - .andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { - api.get("/secured-route").andExpect(status().isForbidden()); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { - api.get("/secured-route").andExpect(status().isOk()); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { - api.get("/secured-method").andExpect(status().isForbidden()); - } - - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { - api.get("/secured-method").andExpect(status().isOk()); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get("/greet").andExpect(status().isUnauthorized()); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserHasMockedAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } + + @Test + @WithMockJwtAuth() + void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello user! You are granted with [].")); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, name = "Ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsMockedAsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } + + @Test + @WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { + api.get("/secured-route").andExpect(status().isForbidden()); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { + api.get("/secured-route").andExpect(status().isOk()); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { + api.get("/secured-method").andExpect(status().isForbidden()); + } + + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { + api.get("/secured-method").andExpect(status().isOk()); + } } diff --git a/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/GreetingControllerFluentApiTest.java b/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/GreetingControllerFluentApiTest.java index d8bc8659d..161194ef1 100644 --- a/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/GreetingControllerFluentApiTest.java +++ b/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/GreetingControllerFluentApiTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken_jpa_authorities; diff --git a/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/MessageServiceTests.java b/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/MessageServiceTests.java index 2ba3973ad..5a8da9746 100644 --- a/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/MessageServiceTests.java +++ b/samples/webmvc-jwt-default-jpa-authorities/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken_jpa_authorities/MessageServiceTests.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken_jpa_authorities; @@ -40,57 +39,54 @@ @AutoConfigureAddonsSecurity class MessageServiceTests { - @Autowired - private MessageService messageService; + @Autowired + private MessageService messageService; - @Test() - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() { - assertThrows(Exception.class, () -> messageService.getSecret()); - } + @Test() + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() { + assertThrows(Exception.class, () -> messageService.getSecret()); + } - /*--------------*/ - /* @WithMockJwt */ - /*--------------*/ - @Test - @WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(preferredUsername = "ch4mpy")) - void givenUserIsCh4mpy_whenGetGreet_thenReturnGreeting() { - final JwtAuthenticationToken auth = (JwtAuthenticationToken) SecurityContextHolder.getContext() - .getAuthentication(); + /*--------------*/ + /* @WithMockJwt */ + /*--------------*/ + @Test + @WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(preferredUsername = "ch4mpy")) + void givenUserIsCh4mpy_whenGetGreet_thenReturnGreeting() { + final JwtAuthenticationToken auth = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); - assertThat(messageService.greet(auth)) - .isEqualTo("Hello ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } + assertThat(messageService.greet(auth)).isEqualTo("Hello ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } - @Test() - @WithMockJwtAuth() - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetGreet_thenThrows() { - assertThrows(Exception.class, () -> messageService.getSecret()); - } + @Test() + @WithMockJwtAuth() + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetGreet_thenThrows() { + assertThrows(Exception.class, () -> messageService.getSecret()); + } - @Test - @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetGreet_thenReturnsSecret() { - assertThat(messageService.getSecret()).isEqualTo("Secret message"); - } + @Test + @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetGreet_thenReturnsSecret() { + assertThat(messageService.getSecret()).isEqualTo("Secret message"); + } - /*-------------------------*/ - /* @WithMockAuthentication */ - /*-------------------------*/ - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, name = "ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsMockedAsCh4mpy_whenGetGreet_thenReturnGreeting() { - final var token = mock(Jwt.class); - when(token.getClaimAsString(StandardClaimNames.PREFERRED_USERNAME)).thenReturn("ch4mpy"); - final var auth = (JwtAuthenticationToken) TestSecurityContextHolder.getContext().getAuthentication(); - when(auth.getToken()).thenReturn(token); + /*-------------------------*/ + /* @WithMockAuthentication */ + /*-------------------------*/ + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, name = "ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsMockedAsCh4mpy_whenGetGreet_thenReturnGreeting() { + final var token = mock(Jwt.class); + when(token.getClaimAsString(StandardClaimNames.PREFERRED_USERNAME)).thenReturn("ch4mpy"); + final var auth = (JwtAuthenticationToken) TestSecurityContextHolder.getContext().getAuthentication(); + when(auth.getToken()).thenReturn(token); - assertThat(messageService.greet(auth)) - .isEqualTo("Hello ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); - } + assertThat(messageService.greet(auth)).isEqualTo("Hello ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."); + } - @TestConfiguration(proxyBeanMethods = false) - @EnableMethodSecurity - @Import({ MessageService.class }) - static class TestConfig { - } + @TestConfiguration(proxyBeanMethods = false) + @EnableMethodSecurity + @Import({ MessageService.class }) + static class TestConfig { + } } diff --git a/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingController.java b/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingController.java index d9e911257..18b19267e 100644 --- a/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingController.java +++ b/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingController.java @@ -13,27 +13,27 @@ @RestController @RequiredArgsConstructor public class GreetingController { - private final MessageService messageService; - - @GetMapping("/greet") - public ResponseEntity greet(JwtAuthenticationToken auth) { - return ResponseEntity.ok(messageService.greet(auth)); - } - - @GetMapping("/secured-route") - public ResponseEntity securedRoute() { - return ResponseEntity.ok(messageService.getSecret()); - } - - @GetMapping("/secured-method") - @PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')") - public ResponseEntity securedMethod() { - return ResponseEntity.ok(messageService.getSecret()); - } - - @GetMapping("/claims") - @PreAuthorize("isAuthenticated()") - public ResponseEntity> getClaims(JwtAuthenticationToken auth) { - return ResponseEntity.ok(auth.getTokenAttributes()); - } + private final MessageService messageService; + + @GetMapping("/greet") + public ResponseEntity greet(JwtAuthenticationToken auth) { + return ResponseEntity.ok(messageService.greet(auth)); + } + + @GetMapping("/secured-route") + public ResponseEntity securedRoute() { + return ResponseEntity.ok(messageService.getSecret()); + } + + @GetMapping("/secured-method") + @PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')") + public ResponseEntity securedMethod() { + return ResponseEntity.ok(messageService.getSecret()); + } + + @GetMapping("/claims") + @PreAuthorize("isAuthenticated()") + public ResponseEntity> getClaims(JwtAuthenticationToken auth) { + return ResponseEntity.ok(auth.getTokenAttributes()); + } } diff --git a/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageService.java b/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageService.java index 8f5586baa..56ae90df1 100644 --- a/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageService.java +++ b/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageService.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; diff --git a/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/OAuth2SecurityConfig.java b/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/OAuth2SecurityConfig.java index 028c7dff8..5f771fdcf 100644 --- a/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/OAuth2SecurityConfig.java +++ b/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/OAuth2SecurityConfig.java @@ -11,12 +11,12 @@ @Configuration @EnableMethodSecurity public class OAuth2SecurityConfig { - @Bean - ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { - // @formatter:off + @Bean + ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { + // @formatter:off return (AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) -> registry .requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL") .anyRequest().authenticated(); // @formatter:on - } + } } \ No newline at end of file diff --git a/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/WebmvcJwtDefault.java b/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/WebmvcJwtDefault.java index 9a3e8fd8b..b29f2d8bc 100644 --- a/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/WebmvcJwtDefault.java +++ b/samples/webmvc-jwt-default/src/main/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/WebmvcJwtDefault.java @@ -6,7 +6,7 @@ @SpringBootApplication public class WebmvcJwtDefault { - public static void main(String[] args) { - new SpringApplicationBuilder(WebmvcJwtDefault.class).web(WebApplicationType.SERVLET).run(args); - } + public static void main(String[] args) { + new SpringApplicationBuilder(WebmvcJwtDefault.class).web(WebApplicationType.SERVLET).run(args); + } } diff --git a/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java b/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java index 609efdaa2..77203c1ef 100644 --- a/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java +++ b/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerAnnotatedTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; @@ -52,14 +51,14 @@ @Import({ OAuth2SecurityConfig.class }) // Import your web-security configuration class GreetingControllerAnnotatedTest { - // Mock controller injected dependencies - @MockBean - private MessageService messageService; + // Mock controller injected dependencies + @MockBean + private MessageService messageService; - @Autowired - MockMvcSupport api; + @Autowired + MockMvcSupport api; - @BeforeEach + @BeforeEach public void setUp() { when(messageService.greet(any())).thenAnswer(invocation -> { final JwtAuthenticationToken auth = invocation.getArgument(0, JwtAuthenticationToken.class); @@ -68,62 +67,60 @@ public void setUp() { when(messageService.getSecret()).thenReturn("Secret message"); } - @Test - void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { - api.get("/greet").andExpect(status().isUnauthorized()); - } + @Test + void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception { + api.get("/greet").andExpect(status().isUnauthorized()); + } - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserHasMockedAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("/greet").andExpect(content().string("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserHasMockedAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } - @Test - @WithMockJwtAuth() - void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { - api.get("/greet").andExpect(content().string("Hello user! You are granted with [].")); - } + @Test + @WithMockJwtAuth() + void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello user! You are granted with [].")); + } - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, name = "Ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsMockedAsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("/greet") - .andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, name = "Ch4mpy", authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsMockedAsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } - @Test - @WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) - void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { - api.get("/greet") - .andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); - } + @Test + @WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy")) + void givenUserIsCh4mpy_whenGetGreet_thenOk() throws Exception { + api.get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL].")); + } - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { - api.get("/secured-route").andExpect(status().isForbidden()); - } + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception { + api.get("/secured-route").andExpect(status().isForbidden()); + } - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { - api.get("/secured-route").andExpect(status().isOk()); - } + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception { + api.get("/secured-route").andExpect(status().isOk()); + } - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) - void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { - api.get("/secured-method").andExpect(status().isForbidden()); - } + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class) + void givenUserIsNotGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception { + api.get("/secured-method").andExpect(status().isForbidden()); + } - @Test - @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") - void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { - api.get("/secured-method").andExpect(status().isOk()); - } + @Test + @WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL") + void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception { + api.get("/secured-method").andExpect(status().isOk()); + } - // @formatter:off + // @formatter:off static final String obj1 = """ { "prop1_1": { @@ -187,8 +184,9 @@ void givenUserIsGrantedWithAuthorizedPersonnel_whenGetSecuredMethod_thenOk() thr jsonObjectClaims = { @JsonObjectClaim(name = "nested_obj1", value = obj1), @JsonObjectClaim(name = "nested_obj2", value = obj2)}, jsonObjectArrayClaims = @JsonObjectArrayClaim(name = "nested_objArr1", value = { obj3, obj4}))}))) // @formatter:on - void givenUserIsAuthenticated_whenGetClaims_thenOk() throws Exception { - api.get("/claims").andExpect(status().isOk()).andExpect(content().string( - "{\"sub\":\"Ch4mpy\",\"objArr1\":[{\"prop1_1\":{\"nested1_1_1\":\"value1\"},\"prop1_2\":{\"nested1_2_1\":221}},{\"prop2_2\":{\"nested2_2_1\":221},\"prop2_1\":{\"nested2_1_1\":\"value2\"}}],\"strArr1\":[\"a\",\"b\",\"c\"],\"strArr2\":[\"D\",\"E\",\"F\"],\"preferred_username\":\"user\",\"long2\":51,\"int2\":51,\"int1\":42,\"long1\":42,\"url1\":\"https://localhost:8080/greet\",\"url2\":\"https://localhost:4200/home\",\"str1\":\"String 1\",\"str2\":\"String 2\",\"address\":{},\"email_verified\":false,\"obj2\":{\"prop2_1\":{\"nested2_1_1\":{\"nested2_1_1_1\":2111}}},\"obj1\":{\"prop1_1\":{\"nested1_1_1\":\"value1\"},\"prop1_2\":{\"nested1_2_1\":121}},\"phone_number_verified\":false,\"date1\":\"2023-04-04T00:42:00.000+00:00\",\"https://c4-soft.com/spring-addons\":{\"nested_int1\":42,\"nested_int2\":51,\"nested_str2\":\"String 2\",\"nested_str1\":\"String 1\",\"nested_objArr1\":[{\"prop1_1\":{\"nested1_1_1\":\"value1\"},\"prop1_2\":{\"nested1_2_1\":221}},{\"prop2_2\":{\"nested2_2_1\":221},\"prop2_1\":{\"nested2_1_1\":\"value2\"}}],\"nested_strArr1\":[\"a\",\"b\",\"c\"],\"nested_obj2\":{\"prop2_1\":{\"nested2_1_1\":{\"nested2_1_1_1\":2111}}},\"nested_strArr2\":[\"D\",\"E\",\"F\"],\"nested_obj1\":{\"prop1_1\":{\"nested1_1_1\":\"value1\"},\"prop1_2\":{\"nested1_2_1\":121}},\"nested_double2\":5.1,\"nested_double1\":4.2,\"nested_epoch2\":\"2023-04-04T22:42:52.000+00:00\",\"nested_epoch1\":\"2022-12-14T00:40:00.000+00:00\",\"nested_long2\":51,\"nested_long1\":42,\"nested_url1\":\"https://localhost:8080/greet\",\"nested_url2\":\"https://localhost:4200/home\",\"nested_date1\":\"2023-04-04T00:42:00.000+00:00\",\"nested_uri1\":\"https://localhost:8080/greet\",\"nested_uri2\":\"https://localhost:4200/home#greet\"},\"uri2\":\"https://localhost:4200/home#greet\",\"uri1\":\"https://localhost:8080/greet\",\"double2\":5.1,\"double1\":4.2,\"epoch2\":\"2023-04-04T22:42:52.000+00:00\",\"epoch1\":\"2022-12-14T00:40:00.000+00:00\"}")); - } + void givenUserIsAuthenticated_whenGetClaims_thenOk() throws Exception { + api.get("/claims").andExpect(status().isOk()).andExpect( + content().string( + "{\"sub\":\"Ch4mpy\",\"objArr1\":[{\"prop1_1\":{\"nested1_1_1\":\"value1\"},\"prop1_2\":{\"nested1_2_1\":221}},{\"prop2_2\":{\"nested2_2_1\":221},\"prop2_1\":{\"nested2_1_1\":\"value2\"}}],\"strArr1\":[\"a\",\"b\",\"c\"],\"strArr2\":[\"D\",\"E\",\"F\"],\"preferred_username\":\"user\",\"long2\":51,\"int2\":51,\"int1\":42,\"long1\":42,\"url1\":\"https://localhost:8080/greet\",\"url2\":\"https://localhost:4200/home\",\"str1\":\"String 1\",\"str2\":\"String 2\",\"address\":{},\"email_verified\":false,\"obj2\":{\"prop2_1\":{\"nested2_1_1\":{\"nested2_1_1_1\":2111}}},\"obj1\":{\"prop1_1\":{\"nested1_1_1\":\"value1\"},\"prop1_2\":{\"nested1_2_1\":121}},\"phone_number_verified\":false,\"date1\":\"2023-04-04T00:42:00.000+00:00\",\"https://c4-soft.com/spring-addons\":{\"nested_int1\":42,\"nested_int2\":51,\"nested_str2\":\"String 2\",\"nested_str1\":\"String 1\",\"nested_objArr1\":[{\"prop1_1\":{\"nested1_1_1\":\"value1\"},\"prop1_2\":{\"nested1_2_1\":221}},{\"prop2_2\":{\"nested2_2_1\":221},\"prop2_1\":{\"nested2_1_1\":\"value2\"}}],\"nested_strArr1\":[\"a\",\"b\",\"c\"],\"nested_obj2\":{\"prop2_1\":{\"nested2_1_1\":{\"nested2_1_1_1\":2111}}},\"nested_strArr2\":[\"D\",\"E\",\"F\"],\"nested_obj1\":{\"prop1_1\":{\"nested1_1_1\":\"value1\"},\"prop1_2\":{\"nested1_2_1\":121}},\"nested_double2\":5.1,\"nested_double1\":4.2,\"nested_epoch2\":\"2023-04-04T22:42:52.000+00:00\",\"nested_epoch1\":\"2022-12-14T00:40:00.000+00:00\",\"nested_long2\":51,\"nested_long1\":42,\"nested_url1\":\"https://localhost:8080/greet\",\"nested_url2\":\"https://localhost:4200/home\",\"nested_date1\":\"2023-04-04T00:42:00.000+00:00\",\"nested_uri1\":\"https://localhost:8080/greet\",\"nested_uri2\":\"https://localhost:4200/home#greet\"},\"uri2\":\"https://localhost:4200/home#greet\",\"uri1\":\"https://localhost:8080/greet\",\"double2\":5.1,\"double1\":4.2,\"epoch2\":\"2023-04-04T22:42:52.000+00:00\",\"epoch1\":\"2022-12-14T00:40:00.000+00:00\"}")); + } } diff --git a/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerFluentApiTest.java b/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerFluentApiTest.java index 208cb8a59..a3f4e1c29 100644 --- a/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerFluentApiTest.java +++ b/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/GreetingControllerFluentApiTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; diff --git a/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageServiceTests.java b/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageServiceTests.java index e33ded5fb..f7f8e0e6d 100644 --- a/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageServiceTests.java +++ b/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/MessageServiceTests.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; diff --git a/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecretRepoTest.java b/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecretRepoTest.java index 941dffe81..259288192 100644 --- a/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecretRepoTest.java +++ b/samples/webmvc-jwt-default/src/test/java/com/c4_soft/springaddons/samples/webmvc_jwtauthenticationtoken/SecretRepoTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_jwtauthenticationtoken; diff --git a/samples/webmvc-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageService.java b/samples/webmvc-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageService.java index 879f38504..3020b77c0 100644 --- a/samples/webmvc-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageService.java +++ b/samples/webmvc-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageService.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; diff --git a/samples/webmvc-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecurityConfig.java b/samples/webmvc-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecurityConfig.java index f59b134ac..76d7ac633 100644 --- a/samples/webmvc-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecurityConfig.java +++ b/samples/webmvc-jwt-oauthentication/src/main/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecurityConfig.java @@ -21,23 +21,22 @@ @Configuration @EnableMethodSecurity public class SecurityConfig { - @Bean - OAuth2AuthenticationFactory authenticationFactory( - Converter, Collection> authoritiesConverter, - SpringAddonsSecurityProperties addonsProperties) { - return (bearerString, claims) -> new OAuthentication<>(new OpenidClaimSet( - claims, - addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)) - .getUsernameClaim()), - authoritiesConverter.convert(claims), bearerString); - } + @Bean + OAuth2AuthenticationFactory authenticationFactory( + Converter, Collection> authoritiesConverter, + SpringAddonsSecurityProperties addonsProperties) { + return (bearerString, claims) -> new OAuthentication<>( + new OpenidClaimSet(claims, addonsProperties.getIssuerProperties(claims.get(JwtClaimNames.ISS)).getUsernameClaim()), + authoritiesConverter.convert(claims), + bearerString); + } - @Bean - public ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { - // @formatter:off + @Bean + public ResourceServerExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() { + // @formatter:off return (AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) -> registry .requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL") .anyRequest().authenticated(); // @formatter:on - } + } } \ No newline at end of file diff --git a/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerAnnotatedTest.java b/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerAnnotatedTest.java index 1c49dba3a..de1f6306b 100644 --- a/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerAnnotatedTest.java +++ b/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerAnnotatedTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; diff --git a/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerFluentApiTest.java b/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerFluentApiTest.java index abbd19d46..3e309c847 100644 --- a/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerFluentApiTest.java +++ b/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/GreetingControllerFluentApiTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; diff --git a/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageServiceTests.java b/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageServiceTests.java index 77a2a8904..c0922970d 100644 --- a/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageServiceTests.java +++ b/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/MessageServiceTests.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; diff --git a/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecretRepoTest.java b/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecretRepoTest.java index 4b887e463..51a696c70 100644 --- a/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecretRepoTest.java +++ b/samples/webmvc-jwt-oauthentication/src/test/java/com/c4_soft/springaddons/samples/webmvc_oidcauthentication/SecretRepoTest.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.samples.webmvc_oidcauthentication; diff --git a/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/AuthoritiesConverterNotAMockException.java b/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/AuthoritiesConverterNotAMockException.java index 786a022fe..76a5de92c 100644 --- a/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/AuthoritiesConverterNotAMockException.java +++ b/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/AuthoritiesConverterNotAMockException.java @@ -1,17 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test; diff --git a/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/Defaults.java b/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/Defaults.java index 166ab1d10..38482a554 100644 --- a/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/Defaults.java +++ b/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/Defaults.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test; diff --git a/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/OAuthenticationTestingBuilder.java b/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/OAuthenticationTestingBuilder.java index f4d5803ae..c8e31401c 100644 --- a/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/OAuthenticationTestingBuilder.java +++ b/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/OAuthenticationTestingBuilder.java @@ -1,14 +1,13 @@ /* * Copyright 2020 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test; diff --git a/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/OpenidClaimSetBuilder.java b/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/OpenidClaimSetBuilder.java index 7c973f1b4..71682419c 100644 --- a/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/OpenidClaimSetBuilder.java +++ b/spring-addons-oauth2-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/OpenidClaimSetBuilder.java @@ -1,14 +1,13 @@ /* * Copyright 2020 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test; @@ -33,252 +32,252 @@ */ public class OpenidClaimSetBuilder extends ModifiableClaimSet { - private static final long serialVersionUID = 8050195176203128543L; + private static final long serialVersionUID = 8050195176203128543L; - private String usernameClaim = StandardClaimNames.SUB; + private String usernameClaim = StandardClaimNames.SUB; - public OpenidClaimSetBuilder() { - } + public OpenidClaimSetBuilder() { + } - public OpenidClaimSetBuilder(Map ptivateClaims) { - super(ptivateClaims); - } + public OpenidClaimSetBuilder(Map ptivateClaims) { + super(ptivateClaims); + } - public OpenidClaimSet build() { - return new OpenidClaimSet(this, usernameClaim); - } + public OpenidClaimSet build() { + return new OpenidClaimSet(this, usernameClaim); + } - public OpenidClaimSetBuilder usernameClaim(String usernameClaim) { - this.usernameClaim = usernameClaim; - return this; - } + public OpenidClaimSetBuilder usernameClaim(String usernameClaim) { + this.usernameClaim = usernameClaim; + return this; + } - public OpenidClaimSetBuilder acr(String acr) { - return setIfNonEmpty(IdTokenClaimNames.ACR, acr); - } + public OpenidClaimSetBuilder acr(String acr) { + return setIfNonEmpty(IdTokenClaimNames.ACR, acr); + } - public OpenidClaimSetBuilder amr(List amr) { - return setIfNonEmpty(IdTokenClaimNames.AMR, amr); - } + public OpenidClaimSetBuilder amr(List amr) { + return setIfNonEmpty(IdTokenClaimNames.AMR, amr); + } - public OpenidClaimSetBuilder audience(List audience) { - return setIfNonEmpty(IdTokenClaimNames.AUD, audience); - } + public OpenidClaimSetBuilder audience(List audience) { + return setIfNonEmpty(IdTokenClaimNames.AUD, audience); + } - public OpenidClaimSetBuilder authTime(Instant authTime) { - return setIfNonEmpty(IdTokenClaimNames.AUTH_TIME, authTime); - } + public OpenidClaimSetBuilder authTime(Instant authTime) { + return setIfNonEmpty(IdTokenClaimNames.AUTH_TIME, authTime); + } - public OpenidClaimSetBuilder azp(String azp) { - return setIfNonEmpty(IdTokenClaimNames.AZP, azp); - } + public OpenidClaimSetBuilder azp(String azp) { + return setIfNonEmpty(IdTokenClaimNames.AZP, azp); + } - public OpenidClaimSetBuilder expiresAt(Instant expiresAt) { - return setIfNonEmpty(IdTokenClaimNames.EXP, expiresAt); - } + public OpenidClaimSetBuilder expiresAt(Instant expiresAt) { + return setIfNonEmpty(IdTokenClaimNames.EXP, expiresAt); + } - public OpenidClaimSetBuilder issuedAt(Instant issuedAt) { - return setIfNonEmpty(IdTokenClaimNames.IAT, issuedAt); - } + public OpenidClaimSetBuilder issuedAt(Instant issuedAt) { + return setIfNonEmpty(IdTokenClaimNames.IAT, issuedAt); + } - public OpenidClaimSetBuilder jwtId(String jti) { - return setIfNonEmpty(JwtClaimNames.JTI, jti); - } + public OpenidClaimSetBuilder jwtId(String jti) { + return setIfNonEmpty(JwtClaimNames.JTI, jti); + } - public OpenidClaimSetBuilder issuer(URL issuer) { - return setIfNonEmpty(IdTokenClaimNames.ISS, issuer.toString()); - } + public OpenidClaimSetBuilder issuer(URL issuer) { + return setIfNonEmpty(IdTokenClaimNames.ISS, issuer.toString()); + } - public OpenidClaimSetBuilder nonce(String nonce) { - return setIfNonEmpty(IdTokenClaimNames.NONCE, nonce); - } + public OpenidClaimSetBuilder nonce(String nonce) { + return setIfNonEmpty(IdTokenClaimNames.NONCE, nonce); + } - public OpenidClaimSetBuilder notBefore(Instant nbf) { - return setIfNonEmpty(JwtClaimNames.NBF, nbf); - } + public OpenidClaimSetBuilder notBefore(Instant nbf) { + return setIfNonEmpty(JwtClaimNames.NBF, nbf); + } - public OpenidClaimSetBuilder accessTokenHash(String atHash) { - return setIfNonEmpty(IdTokenClaimNames.AT_HASH, atHash); - } + public OpenidClaimSetBuilder accessTokenHash(String atHash) { + return setIfNonEmpty(IdTokenClaimNames.AT_HASH, atHash); + } - public OpenidClaimSetBuilder authorizationCodeHash(String cHash) { - return setIfNonEmpty(IdTokenClaimNames.C_HASH, cHash); - } + public OpenidClaimSetBuilder authorizationCodeHash(String cHash) { + return setIfNonEmpty(IdTokenClaimNames.C_HASH, cHash); + } - public OpenidClaimSetBuilder sessionState(String sessionState) { - return setIfNonEmpty("session_state", sessionState); - } + public OpenidClaimSetBuilder sessionState(String sessionState) { + return setIfNonEmpty("session_state", sessionState); + } - public OpenidClaimSetBuilder subject(String subject) { - return setIfNonEmpty(IdTokenClaimNames.SUB, subject); - } - - public OpenidClaimSetBuilder name(String value) { - return setIfNonEmpty(StandardClaimNames.NAME, value); - } - - public OpenidClaimSetBuilder givenName(String value) { - return setIfNonEmpty(StandardClaimNames.GIVEN_NAME, value); - } - - public OpenidClaimSetBuilder familyName(String value) { - return setIfNonEmpty(StandardClaimNames.FAMILY_NAME, value); - } - - public OpenidClaimSetBuilder middleName(String value) { - return setIfNonEmpty(StandardClaimNames.MIDDLE_NAME, value); - } - - public OpenidClaimSetBuilder nickname(String value) { - return setIfNonEmpty(StandardClaimNames.NICKNAME, value); - } - - public OpenidClaimSetBuilder preferredUsername(String value) { - return setIfNonEmpty(StandardClaimNames.PREFERRED_USERNAME, value); - } - - public OpenidClaimSetBuilder profile(String value) { - return setIfNonEmpty(StandardClaimNames.PROFILE, value); - } - - public OpenidClaimSetBuilder picture(String value) { - return setIfNonEmpty(StandardClaimNames.PICTURE, value); - } - - public OpenidClaimSetBuilder website(String value) { - return setIfNonEmpty(StandardClaimNames.WEBSITE, value); - } - - public OpenidClaimSetBuilder email(String value) { - return setIfNonEmpty(StandardClaimNames.EMAIL, value); - } - - public OpenidClaimSetBuilder emailVerified(Boolean value) { - return setIfNonEmpty(StandardClaimNames.EMAIL_VERIFIED, value); - } - - public OpenidClaimSetBuilder gender(String value) { - return setIfNonEmpty(StandardClaimNames.GENDER, value); - } - - public OpenidClaimSetBuilder birthdate(String value) { - return setIfNonEmpty(StandardClaimNames.BIRTHDATE, value); - } - - public OpenidClaimSetBuilder zoneinfo(String value) { - return setIfNonEmpty(StandardClaimNames.ZONEINFO, value); - } - - public OpenidClaimSetBuilder locale(String value) { - return setIfNonEmpty(StandardClaimNames.LOCALE, value); - } - - public OpenidClaimSetBuilder phoneNumber(String value) { - return setIfNonEmpty(StandardClaimNames.PHONE_NUMBER, value); - } - - public OpenidClaimSetBuilder phoneNumberVerified(Boolean value) { - return setIfNonEmpty(StandardClaimNames.PHONE_NUMBER_VERIFIED, value); - } - - public OpenidClaimSetBuilder address(AddressClaim value) { - if (value == null) { - this.remove("address"); - } else { - this.put("address", value); - } - return this; - } - - public OpenidClaimSetBuilder claims(Map claims) { - this.putAll(claims); - return this; - } - - public OpenidClaimSetBuilder privateClaims(Map claims) { - return this.claims(claims); - } - - public OpenidClaimSetBuilder otherClaims(Map claims) { - return this.claims(claims); - } - - public OpenidClaimSetBuilder updatedAt(Instant value) { - return setIfNonEmpty("", value); - } - - protected OpenidClaimSetBuilder setIfNonEmpty(String claimName, String claimValue) { - if (StringUtils.hasText(claimValue)) { - this.put(claimName, claimValue); - } else { - this.remove(claimName); - } - return this; - } - - protected OpenidClaimSetBuilder setIfNonEmpty(String claimName, Collection claimValue) { - if (claimValue == null || claimValue.isEmpty()) { - this.remove(claimName); - } else if (claimValue.isEmpty()) { - this.setIfNonEmpty(claimName, claimValue.iterator().next()); - } else { - this.put(claimName, claimValue); - } - return this; - } - - protected OpenidClaimSetBuilder setIfNonEmpty(String claimName, Instant claimValue) { - if (claimValue == null) { - this.remove(claimName); - } else { - this.put(claimName, claimValue.getEpochSecond()); - } - return this; - } - - protected OpenidClaimSetBuilder setIfNonEmpty(String claimName, Boolean claimValue) { - if (claimValue == null) { - this.remove(claimName); - } else { - this.put(claimName, claimValue); - } - return this; - } - - public static final class AddressClaim extends ModifiableClaimSet { - private static final long serialVersionUID = 28800769851008900L; - - public AddressClaim formatted(String value) { - return setIfNonEmpty("formatted", value); - } - - public AddressClaim streetAddress(String value) { - return setIfNonEmpty("street_address", value); - } - - public AddressClaim locality(String value) { - return setIfNonEmpty("locality", value); - } - - public AddressClaim region(String value) { - return setIfNonEmpty("region", value); - } - - public AddressClaim postalCode(String value) { - return setIfNonEmpty("postal_code", value); - } - - public AddressClaim country(String value) { - return setIfNonEmpty("country", value); - } - - private AddressClaim setIfNonEmpty(String claimName, String claimValue) { - if (StringUtils.hasText(claimValue)) { - this.put(claimName, claimValue); - } else { - this.remove(claimName); - } - return this; - } - } + public OpenidClaimSetBuilder subject(String subject) { + return setIfNonEmpty(IdTokenClaimNames.SUB, subject); + } + + public OpenidClaimSetBuilder name(String value) { + return setIfNonEmpty(StandardClaimNames.NAME, value); + } + + public OpenidClaimSetBuilder givenName(String value) { + return setIfNonEmpty(StandardClaimNames.GIVEN_NAME, value); + } + + public OpenidClaimSetBuilder familyName(String value) { + return setIfNonEmpty(StandardClaimNames.FAMILY_NAME, value); + } + + public OpenidClaimSetBuilder middleName(String value) { + return setIfNonEmpty(StandardClaimNames.MIDDLE_NAME, value); + } + + public OpenidClaimSetBuilder nickname(String value) { + return setIfNonEmpty(StandardClaimNames.NICKNAME, value); + } + + public OpenidClaimSetBuilder preferredUsername(String value) { + return setIfNonEmpty(StandardClaimNames.PREFERRED_USERNAME, value); + } + + public OpenidClaimSetBuilder profile(String value) { + return setIfNonEmpty(StandardClaimNames.PROFILE, value); + } + + public OpenidClaimSetBuilder picture(String value) { + return setIfNonEmpty(StandardClaimNames.PICTURE, value); + } + + public OpenidClaimSetBuilder website(String value) { + return setIfNonEmpty(StandardClaimNames.WEBSITE, value); + } + + public OpenidClaimSetBuilder email(String value) { + return setIfNonEmpty(StandardClaimNames.EMAIL, value); + } + + public OpenidClaimSetBuilder emailVerified(Boolean value) { + return setIfNonEmpty(StandardClaimNames.EMAIL_VERIFIED, value); + } + + public OpenidClaimSetBuilder gender(String value) { + return setIfNonEmpty(StandardClaimNames.GENDER, value); + } + + public OpenidClaimSetBuilder birthdate(String value) { + return setIfNonEmpty(StandardClaimNames.BIRTHDATE, value); + } + + public OpenidClaimSetBuilder zoneinfo(String value) { + return setIfNonEmpty(StandardClaimNames.ZONEINFO, value); + } + + public OpenidClaimSetBuilder locale(String value) { + return setIfNonEmpty(StandardClaimNames.LOCALE, value); + } + + public OpenidClaimSetBuilder phoneNumber(String value) { + return setIfNonEmpty(StandardClaimNames.PHONE_NUMBER, value); + } + + public OpenidClaimSetBuilder phoneNumberVerified(Boolean value) { + return setIfNonEmpty(StandardClaimNames.PHONE_NUMBER_VERIFIED, value); + } + + public OpenidClaimSetBuilder address(AddressClaim value) { + if (value == null) { + this.remove("address"); + } else { + this.put("address", value); + } + return this; + } + + public OpenidClaimSetBuilder claims(Map claims) { + this.putAll(claims); + return this; + } + + public OpenidClaimSetBuilder privateClaims(Map claims) { + return this.claims(claims); + } + + public OpenidClaimSetBuilder otherClaims(Map claims) { + return this.claims(claims); + } + + public OpenidClaimSetBuilder updatedAt(Instant value) { + return setIfNonEmpty("", value); + } + + protected OpenidClaimSetBuilder setIfNonEmpty(String claimName, String claimValue) { + if (StringUtils.hasText(claimValue)) { + this.put(claimName, claimValue); + } else { + this.remove(claimName); + } + return this; + } + + protected OpenidClaimSetBuilder setIfNonEmpty(String claimName, Collection claimValue) { + if (claimValue == null || claimValue.isEmpty()) { + this.remove(claimName); + } else if (claimValue.isEmpty()) { + this.setIfNonEmpty(claimName, claimValue.iterator().next()); + } else { + this.put(claimName, claimValue); + } + return this; + } + + protected OpenidClaimSetBuilder setIfNonEmpty(String claimName, Instant claimValue) { + if (claimValue == null) { + this.remove(claimName); + } else { + this.put(claimName, claimValue.getEpochSecond()); + } + return this; + } + + protected OpenidClaimSetBuilder setIfNonEmpty(String claimName, Boolean claimValue) { + if (claimValue == null) { + this.remove(claimName); + } else { + this.put(claimName, claimValue); + } + return this; + } + + public static final class AddressClaim extends ModifiableClaimSet { + private static final long serialVersionUID = 28800769851008900L; + + public AddressClaim formatted(String value) { + return setIfNonEmpty("formatted", value); + } + + public AddressClaim streetAddress(String value) { + return setIfNonEmpty("street_address", value); + } + + public AddressClaim locality(String value) { + return setIfNonEmpty("locality", value); + } + + public AddressClaim region(String value) { + return setIfNonEmpty("region", value); + } + + public AddressClaim postalCode(String value) { + return setIfNonEmpty("postal_code", value); + } + + public AddressClaim country(String value) { + return setIfNonEmpty("country", value); + } + + private AddressClaim setIfNonEmpty(String claimName, String claimValue) { + if (StringUtils.hasText(claimValue)) { + this.put(claimName, claimValue); + } else { + this.remove(claimName); + } + return this; + } + } } diff --git a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/AuthenticationBuilder.java b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/AuthenticationBuilder.java index 1d66f72bb..112f06d2a 100644 --- a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/AuthenticationBuilder.java +++ b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/AuthenticationBuilder.java @@ -1,17 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2; @@ -20,12 +16,11 @@ /** * Common interface for test authentication builders * - * @author Jérôme Wacongne <ch4mp#64;c4-soft.com> - * - * @param capture for extending class type + * @author Jérôme Wacongne <ch4mp#64;c4-soft.com> + * @param capture for extending class type */ public interface AuthenticationBuilder { - T build(); + T build(); } diff --git a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/ClaimSet.java b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/ClaimSet.java index 4e8b86e79..8258ec1e7 100644 --- a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/ClaimSet.java +++ b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/ClaimSet.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2; @@ -34,7 +33,7 @@ * @author Jérôme Wacongne <ch4mp#64;c4-soft.com> */ public interface ClaimSet extends Map, Serializable { - + default T getByJsonPath(String jsonPath) { return JsonPath.read(this, jsonPath); } diff --git a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/DelegatingMap.java b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/DelegatingMap.java index f05f6b82b..8439958c7 100644 --- a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/DelegatingMap.java +++ b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/DelegatingMap.java @@ -1,17 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2; @@ -21,16 +17,15 @@ import java.util.Set; /** - * Allows to work around some JDK limitations. - * For instance, {@link java.util.Collections} {@code UnmodifiableMap} can't be extended (private). - * With this, it is possible to extend a Map delegating to an unmodifiable one. + * Allows to work around some JDK limitations. For instance, {@link java.util.Collections} {@code UnmodifiableMap} can't be extended (private). With this, it is + * possible to extend a Map delegating to an unmodifiable one. * * @author Jérôme Wacongne <ch4mp#64;c4-soft.com> */ public class DelegatingMap implements Map { - + private final Map delegate; - + DelegatingMap() { this(new HashMap<>()); } diff --git a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/ModifiableClaimSet.java b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/ModifiableClaimSet.java index 205637fd4..f3a0c7b32 100644 --- a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/ModifiableClaimSet.java +++ b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/ModifiableClaimSet.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2; @@ -16,31 +15,29 @@ import java.util.Map; /** - * Modifiable Map<String, Object> used to assemble claims during test - * setup + * Modifiable Map<String, Object> used to assemble claims during test setup * * @author Jérôme Wacongne <ch4mp#64;c4-soft.com> */ public class ModifiableClaimSet extends HashMap implements ClaimSet { - private static final long serialVersionUID = -1967790894352277253L; + private static final long serialVersionUID = -1967790894352277253L; - /** - * @param properties initial values (copied so that "properties" is not altered - * when claim-set is modified) - */ - public ModifiableClaimSet(Map properties) { - super(properties); - } + /** + * @param properties initial values (copied so that "properties" is not altered when claim-set is modified) + */ + public ModifiableClaimSet(Map properties) { + super(properties); + } - public ModifiableClaimSet() { - } + public ModifiableClaimSet() { + } - public ModifiableClaimSet(int initialCapacity, float loadFactor) { - super(initialCapacity, loadFactor); - } + public ModifiableClaimSet(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + } - public ModifiableClaimSet(int initialCapacity) { - super(initialCapacity); - } + public ModifiableClaimSet(int initialCapacity) { + super(initialCapacity); + } } \ No newline at end of file diff --git a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/OAuthentication.java b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/OAuthentication.java index 13b789547..413b5de46 100644 --- a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/OAuthentication.java +++ b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/OAuthentication.java @@ -1,14 +1,13 @@ /* * Copyright 2020 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2; @@ -27,85 +26,79 @@ import lombok.EqualsAndHashCode; /** - * @author ch4mp - * @param OpenidClaimSet or any specialization. See {@link } + * @author ch4mp + * @param OpenidClaimSet or any specialization. See {@link } */ @Data @EqualsAndHashCode(callSuper = true) -public class OAuthentication & Serializable & Principal> - extends AbstractAuthenticationToken - implements OAuth2AuthenticatedPrincipal { - private static final long serialVersionUID = -2827891205034221389L; +public class OAuthentication< + T extends Map & Serializable & Principal> extends AbstractAuthenticationToken implements OAuth2AuthenticatedPrincipal { + private static final long serialVersionUID = -2827891205034221389L; - /** - * Bearer string to set as Authorization header if we ever need to call a - * downstream service on behalf of the same resource-owner - */ - private final String tokenString; + /** + * Bearer string to set as Authorization header if we ever need to call a downstream service on behalf of the same resource-owner + */ + private final String tokenString; - /** - * Claim-set associated with the access-token (attributes retrieved from the - * token or introspection end-point) - */ - private final T claims; + /** + * Claim-set associated with the access-token (attributes retrieved from the token or introspection end-point) + */ + private final T claims; - /** - * @param claims Claim-set of any-type - * @param authorities Granted authorities associated with this authentication - * instance - * @param tokenString Original encoded Bearer string (in case resource-server - * needs - */ - public OAuthentication(T claims, Collection authorities, String tokenString) { - super(authorities); - super.setAuthenticated(true); - super.setDetails(claims); - this.claims = claims; - this.tokenString = Optional.ofNullable(tokenString) - .map(ts -> ts.toLowerCase().startsWith("bearer ") ? ts.substring(7) : ts).orElse(null); - } + /** + * @param claims Claim-set of any-type + * @param authorities Granted authorities associated with this authentication instance + * @param tokenString Original encoded Bearer string (in case resource-server needs + */ + public OAuthentication(T claims, Collection authorities, String tokenString) { + super(authorities); + super.setAuthenticated(true); + super.setDetails(claims); + this.claims = claims; + this.tokenString = Optional.ofNullable(tokenString).map(ts -> ts.toLowerCase().startsWith("bearer ") ? ts.substring(7) : ts).orElse(null); + } - @Override - public void setDetails(Object details) { - // Do nothing until spring-security 6.1.0 and - // https://github.com/spring-projects/spring-security/issues/11822 fix is - // released - // throw new RuntimeException("OAuthentication details are immutable"); - } + @Override + public void setDetails(Object details) { + // Do nothing until spring-security 6.1.0 and + // https://github.com/spring-projects/spring-security/issues/11822 fix is + // released + // throw new RuntimeException("OAuthentication details are immutable"); + } - @Override - public void setAuthenticated(boolean isAuthenticated) { - throw new RuntimeException("OAuthentication authentication status is immutable"); - } + @Override + public void setAuthenticated(boolean isAuthenticated) { + throw new RuntimeException("OAuthentication authentication status is immutable"); + } - @Override - public String getCredentials() { - return tokenString; - } + @Override + public String getCredentials() { + return tokenString; + } - @Override - public String getName() { - return getPrincipal().getName(); - } + @Override + public String getName() { + return getPrincipal().getName(); + } - @Override - public T getPrincipal() { - return claims; - } + @Override + public T getPrincipal() { + return claims; + } - @Override - public T getAttributes() { - return claims; - } + @Override + public T getAttributes() { + return claims; + } - public T getClaims() { - return claims; - } + public T getClaims() { + return claims; + } - public String getBearerHeader() { - if (!StringUtils.hasText(tokenString)) { - return null; - } - return String.format("Bearer %s", tokenString); - } + public String getBearerHeader() { + if (!StringUtils.hasText(tokenString)) { + return null; + } + return String.format("Bearer %s", tokenString); + } } \ No newline at end of file diff --git a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/UnmodifiableClaimSet.java b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/UnmodifiableClaimSet.java index 3e75ecba5..f84b3f929 100644 --- a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/UnmodifiableClaimSet.java +++ b/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/UnmodifiableClaimSet.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2; diff --git a/spring-addons-web-test/src/main/java/com/c4_soft/springaddons/test/support/web/ByteArrayHttpOutputMessage.java b/spring-addons-web-test/src/main/java/com/c4_soft/springaddons/test/support/web/ByteArrayHttpOutputMessage.java index 5808802af..46dea3f8a 100644 --- a/spring-addons-web-test/src/main/java/com/c4_soft/springaddons/test/support/web/ByteArrayHttpOutputMessage.java +++ b/spring-addons-web-test/src/main/java/com/c4_soft/springaddons/test/support/web/ByteArrayHttpOutputMessage.java @@ -1,17 +1,13 @@ /* * Copyright 2018 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.test.support.web; diff --git a/spring-addons-web-test/src/main/java/com/c4_soft/springaddons/test/support/web/SerializationHelper.java b/spring-addons-web-test/src/main/java/com/c4_soft/springaddons/test/support/web/SerializationHelper.java index 08e7fbd1b..ca3019608 100644 --- a/spring-addons-web-test/src/main/java/com/c4_soft/springaddons/test/support/web/SerializationHelper.java +++ b/spring-addons-web-test/src/main/java/com/c4_soft/springaddons/test/support/web/SerializationHelper.java @@ -1,21 +1,19 @@ /* * Copyright 2018 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.test.support.web; import java.io.IOException; import org.springframework.beans.factory.ObjectFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; @@ -29,15 +27,14 @@ public class SerializationHelper { private final ObjectFactory messageConverters; - @Autowired public SerializationHelper(final ObjectFactory messageConverters) { this.messageConverters = messageConverters; } /** * Serializes objects (request payloads) to any media-type using registered HTTP message converters. Each acceptable converter - * ({@link org.springframework.http.converter.HttpMessageConverter#canWrite(Class, MediaType) converter.canWrite(payload.getClass(), - * mediaType)} return true) is tried until one actually succeeds at serializing. + * ({@link org.springframework.http.converter.HttpMessageConverter#canWrite(Class, MediaType) converter.canWrite(payload.getClass(), mediaType)} return + * true) is tried until one actually succeeds at serializing. * * @param payload type * @param payload request body to serialize @@ -50,14 +47,9 @@ public ByteArrayHttpOutputMessage outputMessage(final T payload, final Media } @SuppressWarnings("unchecked") - final var relevantConverters = - messageConverters - .getObject() - .getConverters() - .stream() - .filter(converter -> converter.canWrite(payload.getClass(), mediaType)) - .map(c -> (HttpMessageConverter) c)// safe to cast as "canWrite"... - .toList(); + final var relevantConverters = messageConverters.getObject().getConverters().stream() + .filter(converter -> converter.canWrite(payload.getClass(), mediaType)).map(c -> (HttpMessageConverter) c)// safe to cast as "canWrite"... + .toList(); final var converted = new ByteArrayHttpOutputMessage(); var isConverted = false; diff --git a/starters/spring-addons-starters-recaptcha/src/main/java/com/c4_soft/springaddons/starter/recaptcha/C4ReCaptchaValidationService.java b/starters/spring-addons-starters-recaptcha/src/main/java/com/c4_soft/springaddons/starter/recaptcha/C4ReCaptchaValidationService.java index 095599755..6131acb89 100644 --- a/starters/spring-addons-starters-recaptcha/src/main/java/com/c4_soft/springaddons/starter/recaptcha/C4ReCaptchaValidationService.java +++ b/starters/spring-addons-starters-recaptcha/src/main/java/com/c4_soft/springaddons/starter/recaptcha/C4ReCaptchaValidationService.java @@ -56,11 +56,10 @@ public Mono checkV3(String response) throws ReCaptchaValidationException log.debug("reCaptcha result : {}", dto); if (!dto.isSuccess()) { throw new ReCaptchaValidationException( - String - .format( - "Failed to validate reCaptcha: %s %s", - response, - dto.getErrorCodes().stream().collect(Collectors.joining("[", ", ", "]")))); + String.format( + "Failed to validate reCaptcha: %s %s", + response, + dto.getErrorCodes().stream().collect(Collectors.joining("[", ", ", "]")))); } if (dto.getScore() < settings.getV3Threshold()) { throw new ReCaptchaValidationException(String.format("Failed to validate reCaptcha: %s. Score is %f", response, dto.getScore())); @@ -70,13 +69,7 @@ public Mono checkV3(String response) throws ReCaptchaValidationException } private Mono response(String response, Class dtoType) { - return webClientBuilder - .get(settings.getSiteverifyUrl()) - .build() - .post() - .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .body(BodyInserters.fromFormData("secret", settings.getSecretKey()).with("response", response)) - .retrieve() - .bodyToMono(dtoType); + return webClientBuilder.get(settings.getSiteverifyUrl()).build().post().contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromFormData("secret", settings.getSecretKey()).with("response", response)).retrieve().bodyToMono(dtoType); } } diff --git a/starters/spring-addons-starters-webclient/src/main/java/com/c4_soft/springaddons/starter/webclient/C4ProxySettings.java b/starters/spring-addons-starters-webclient/src/main/java/com/c4_soft/springaddons/starter/webclient/C4ProxySettings.java index 19a8beabe..a00fe0c08 100644 --- a/starters/spring-addons-starters-webclient/src/main/java/com/c4_soft/springaddons/starter/webclient/C4ProxySettings.java +++ b/starters/spring-addons-starters-webclient/src/main/java/com/c4_soft/springaddons/starter/webclient/C4ProxySettings.java @@ -135,11 +135,7 @@ static String getNonProxyHostsPattern(List noProxy) { if (noProxy == null || noProxy.isEmpty()) { return null; } - return noProxy - .stream() - .map(host -> host.replace(".", "\\.")) - .map(host -> host.replace("-", "\\-")) - .map(host -> host.startsWith("\\.") ? ".*" + host : host) - .collect(Collectors.joining(")|(", "(", ")")); + return noProxy.stream().map(host -> host.replace(".", "\\.")).map(host -> host.replace("-", "\\-")) + .map(host -> host.startsWith("\\.") ? ".*" + host : host).collect(Collectors.joining(")|(", "(", ")")); } } diff --git a/starters/spring-addons-starters-webclient/src/main/java/com/c4_soft/springaddons/starter/webclient/C4WebClientBuilderFactoryService.java b/starters/spring-addons-starters-webclient/src/main/java/com/c4_soft/springaddons/starter/webclient/C4WebClientBuilderFactoryService.java index 58b5d29dd..24fb567ef 100644 --- a/starters/spring-addons-starters-webclient/src/main/java/com/c4_soft/springaddons/starter/webclient/C4WebClientBuilderFactoryService.java +++ b/starters/spring-addons-starters-webclient/src/main/java/com/c4_soft/springaddons/starter/webclient/C4WebClientBuilderFactoryService.java @@ -33,19 +33,11 @@ public WebClient.Builder get(URL baseUrl) { return builder; } log.debug("Building ReactorClientHttpConnector with {}", settings); - final var connector = - new ReactorClientHttpConnector( - HttpClient - .create() - .proxy( - proxy -> proxy - .type(settings.getType()) - .host(settings.getHostname()) - .port(settings.getPort()) - .username(settings.getUsername()) - .password(username -> settings.getPassword()) - .nonProxyHosts(settings.getNoProxy()) - .connectTimeoutMillis(settings.getConnectTimeoutMillis()))); + final var connector = new ReactorClientHttpConnector( + HttpClient.create().proxy( + proxy -> proxy.type(settings.getType()).host(settings.getHostname()).port(settings.getPort()).username(settings.getUsername()) + .password(username -> settings.getPassword()).nonProxyHosts(settings.getNoProxy()) + .connectTimeoutMillis(settings.getConnectTimeoutMillis()))); return builder.clientConnector(connector); } diff --git a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsBackChannelLogoutBeans.java b/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsBackChannelLogoutBeans.java index be2aeeca6..6453dc080 100644 --- a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsBackChannelLogoutBeans.java +++ b/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsBackChannelLogoutBeans.java @@ -40,130 +40,111 @@ /** *

- * This provides with a client side implementation of the OIDC - * Back-Channel - * Logout specification. Keycloak conforms to this OP side of the spec. - * Auth0 - * could some day. + * This provides with a client side implementation of the OIDC Back-Channel Logout + * specification. Keycloak conforms to this OP side of the spec. + * Auth0 could some day. *

*

- * Implementation is made with a security filter-chain intercepting just the - * "/backchannel_logout" route and a controller handling requests to that - * end-point. + * Implementation is made with a security filter-chain intercepting just the "/backchannel_logout" route and a controller handling requests to that end-point. *

*

- * This beans are defined only if - * "com.c4-soft.springaddons.security.client.back-channel-logout-enabled" - * property is true. + * This beans are defined only if "com.c4-soft.springaddons.security.client.back-channel-logout-enabled" property is true. *

* - * * @author Jerome Wacongne ch4mp@c4-soft.com - * */ @ConditionalOnProperty("com.c4-soft.springaddons.security.client.back-channel-logout-enabled") @AutoConfiguration @Import({ SpringAddonsOAuth2ClientProperties.class }) public class SpringAddonsBackChannelLogoutBeans { - /** - * Requests from the OP are anonymous, are not part of a session, and have no - * CSRF token. It contains a logout JWT which serves both to authenticate the - * request and protect against CSRF. - * - * @param http - * @param serverProperties Spring Boot server properties - * @return a security filter-chain dedicated to back-channel logout handling - * @throws Exception - */ - @Order(Ordered.HIGHEST_PRECEDENCE) - @Bean - SecurityWebFilterChain springAddonsBackChannelLogoutClientFilterChain( - ServerHttpSecurity http, - ServerProperties serverProperties) - throws Exception { - http.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/backchannel_logout")); - http.authorizeExchange(exchange -> exchange.anyExchange().permitAll()); - if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { - http.redirectToHttps(withDefaults()); - } - http.cors(cors -> cors.disable()); - http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); - http.csrf(csrf -> csrf.disable()); - return http.build(); - } + /** + * Requests from the OP are anonymous, are not part of a session, and have no CSRF token. It contains a logout JWT which serves both to authenticate the + * request and protect against CSRF. + * + * @param http + * @param serverProperties Spring Boot server properties + * @return a security filter-chain dedicated to back-channel logout handling + * @throws Exception + */ + @Order(Ordered.HIGHEST_PRECEDENCE) + @Bean + SecurityWebFilterChain springAddonsBackChannelLogoutClientFilterChain(ServerHttpSecurity http, ServerProperties serverProperties) throws Exception { + http.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/backchannel_logout")); + http.authorizeExchange(exchange -> exchange.anyExchange().permitAll()); + if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { + http.redirectToHttps(withDefaults()); + } + http.cors(cors -> cors.disable()); + http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); + http.csrf(csrf -> csrf.disable()); + return http.build(); + } - /** - *

- * Handles a POST request containing a JWT logout token provided as - * application/x-www-form-urlencoded as specified in Back-Channel - * Logout specification. - *

- *

- * This end-point will: - *

    - *
  • remove the relevant authorized client (based on issuer URI) for the - * relevant user (based on the subject)
  • - *
  • maybe invalidate user session: only if the removed authorized client was - * the last one the user had
  • - *
- * - * @author Jerome Wacongne ch4mp@c4-soft.com - * - */ - @Component - @RestController - public static class BackChannelLogoutController { - private final SpringAddonsServerOAuth2AuthorizedClientRepository authorizedClientRepository; - private final Map jwtDecoders; + /** + *

+ * Handles a POST request containing a JWT logout token provided as application/x-www-form-urlencoded as specified in + * Back-Channel Logout specification. + *

+ *

+ * This end-point will: + *

    + *
  • remove the relevant authorized client (based on issuer URI) for the relevant user (based on the subject)
  • + *
  • maybe invalidate user session: only if the removed authorized client was the last one the user had
  • + *
+ * + * @author Jerome Wacongne ch4mp@c4-soft.com + */ + @Component + @RestController + public static class BackChannelLogoutController { + private final SpringAddonsServerOAuth2AuthorizedClientRepository authorizedClientRepository; + private final Map jwtDecoders; - public BackChannelLogoutController( - SpringAddonsServerOAuth2AuthorizedClientRepository authorizedClientRepository, - InMemoryReactiveClientRegistrationRepository registrationRepo) { - this.authorizedClientRepository = authorizedClientRepository; - this.jwtDecoders = StreamSupport.stream(registrationRepo.spliterator(), false) - .filter(reg -> AuthorizationGrantType.AUTHORIZATION_CODE.equals(reg.getAuthorizationGrantType())) - .map(ClientRegistration::getProviderDetails) - .collect(Collectors.toMap(provider -> provider.getIssuerUri(), - provider -> NimbusReactiveJwtDecoder.withJwkSetUri(provider.getJwkSetUri()).build())); - } + public BackChannelLogoutController( + SpringAddonsServerOAuth2AuthorizedClientRepository authorizedClientRepository, + InMemoryReactiveClientRegistrationRepository registrationRepo) { + this.authorizedClientRepository = authorizedClientRepository; + this.jwtDecoders = StreamSupport.stream(registrationRepo.spliterator(), false) + .filter(reg -> AuthorizationGrantType.AUTHORIZATION_CODE.equals(reg.getAuthorizationGrantType())) + .map(ClientRegistration::getProviderDetails).collect( + Collectors.toMap( + provider -> provider.getIssuerUri(), + provider -> NimbusReactiveJwtDecoder.withJwkSetUri(provider.getJwkSetUri()).build())); + } - @PostMapping(path = "/backchannel_logout", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public Mono> backChannelLogout(ServerWebExchange serverWebExchange) { - return serverWebExchange.getFormData().map(body -> { - final var tokenString = body.get("logout_token"); - if (tokenString == null || tokenString.size() != 1) { - throw new BadLogoutRequestException(); - } - jwtDecoders.forEach((issuer, decoder) -> { - decoder.decode(tokenString.get(0)).onErrorComplete().subscribe(jwt -> { - final var isLogoutToken = Optional.ofNullable(jwt.getClaims().get("events")) - .map(Object::toString) - .map(evt -> evt.contains("http://schemas.openid.net/event/backchannel-logout")) - .orElse(false); - if (!isLogoutToken) { - throw new BadLogoutRequestException(); - } - final var logoutIss = Optional.ofNullable(jwt.getIssuer()).map(URL::toString).orElse(null); - if (!Objects.equals(issuer, logoutIss)) { - throw new BadLogoutRequestException(); - } - final var logoutSub = jwt.getSubject(); - authorizedClientRepository.removeAuthorizedClients(logoutIss, logoutSub).subscribe(s -> { - s.invalidate(); - }); - }); - }); - return ResponseEntity.ok().build(); - }); - } + @PostMapping(path = "/backchannel_logout", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public Mono> backChannelLogout(ServerWebExchange serverWebExchange) { + return serverWebExchange.getFormData().map(body -> { + final var tokenString = body.get("logout_token"); + if (tokenString == null || tokenString.size() != 1) { + throw new BadLogoutRequestException(); + } + jwtDecoders.forEach((issuer, decoder) -> { + decoder.decode(tokenString.get(0)).onErrorComplete().subscribe(jwt -> { + final var isLogoutToken = Optional.ofNullable(jwt.getClaims().get("events")).map(Object::toString) + .map(evt -> evt.contains("http://schemas.openid.net/event/backchannel-logout")).orElse(false); + if (!isLogoutToken) { + throw new BadLogoutRequestException(); + } + final var logoutIss = Optional.ofNullable(jwt.getIssuer()).map(URL::toString).orElse(null); + if (!Objects.equals(issuer, logoutIss)) { + throw new BadLogoutRequestException(); + } + final var logoutSub = jwt.getSubject(); + authorizedClientRepository.removeAuthorizedClients(logoutIss, logoutSub).subscribe(s -> { + s.invalidate(); + }); + }); + }); + return ResponseEntity.ok().build(); + }); + } - @ResponseStatus(HttpStatus.BAD_REQUEST) - static final class BadLogoutRequestException extends RuntimeException { - } - } + @ResponseStatus(HttpStatus.BAD_REQUEST) + static final class BadLogoutRequestException extends RuntimeException { + private static final long serialVersionUID = -1803794467531166681L; + } + } } diff --git a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsOAuth2ServerLogoutSuccessHandler.java b/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsOAuth2ServerLogoutSuccessHandler.java index 8a91dd6bf..819126009 100644 --- a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsOAuth2ServerLogoutSuccessHandler.java +++ b/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsOAuth2ServerLogoutSuccessHandler.java @@ -22,57 +22,44 @@ /** *

- * Provide with - * RP-Initiated - * Logout for authorization-servers fully compliant with OIDC standard as - * well as those "almost" - * implementing the spec. It is (auto)configured with - * {@link SpringAddonsOAuth2ClientProperties}. + * Provide with RP-Initiated Logout for authorization-servers fully compliant with + * OIDC standard as well as those "almost" implementing the spec. It is (auto)configured with {@link SpringAddonsOAuth2ClientProperties}. *

- * *

- * This implementation is not multi-tenant ready. It will terminate the - * user session on this application as well as on a single authorization-server - * (the one which emitted the access-token with which the logout request is - * made). + * This implementation is not multi-tenant ready. It will terminate the user session on this application as well as on a single authorization-server (the + * one which emitted the access-token with which the logout request is made). *

- * *

- * This bean is auto-configured by {@link SpringAddonsOAuth2ClientBeans} as - * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type + * This bean is auto-configured by {@link SpringAddonsOAuth2ClientBeans} as {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type * {@link ServerLogoutSuccessHandler}. Usage: *

* *
  * SecurityFilterChain uiFilterChain(HttpSecurity http, ServerLogoutSuccessHandler logoutSuccessHandler) {
- *     http.logout().logoutSuccessHandler(logoutSuccessHandler);
+ * 	http.logout().logoutSuccessHandler(logoutSuccessHandler);
  * }
  * 
* * @author Jerome Wacongne ch4mp@c4-soft.com - * - * @see SpringAddonsOAuth2LogoutRequestUriBuilder - * @see SpringAddonsOAuth2ClientProperties - * + * @see SpringAddonsOAuth2LogoutRequestUriBuilder + * @see SpringAddonsOAuth2ClientProperties */ @Data @RequiredArgsConstructor public class SpringAddonsOAuth2ServerLogoutSuccessHandler implements ServerLogoutSuccessHandler { - private final LogoutRequestUriBuilder uriBuilder; - private final ReactiveClientRegistrationRepository clientRegistrationRepo; - private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy(); + private final LogoutRequestUriBuilder uriBuilder; + private final ReactiveClientRegistrationRepository clientRegistrationRepo; + private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy(); - @Override - public Mono onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) { - if (authentication instanceof OAuth2AuthenticationToken oauth) { - return clientRegistrationRepo.findByRegistrationId(oauth.getAuthorizedClientRegistrationId()) - .map(client -> uriBuilder.getLogoutRequestUri(client, - ((OidcUser) oauth.getPrincipal()).getIdToken().getTokenValue())) - .flatMap(logoutUri -> { - return this.redirectStrategy.sendRedirect(exchange.getExchange(), URI.create(logoutUri)); - }); - } - return Mono.empty().then(); - } + @Override + public Mono onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) { + if (authentication instanceof OAuth2AuthenticationToken oauth) { + return clientRegistrationRepo.findByRegistrationId(oauth.getAuthorizedClientRegistrationId()) + .map(client -> uriBuilder.getLogoutRequestUri(client, ((OidcUser) oauth.getPrincipal()).getIdToken().getTokenValue())) + .flatMap(logoutUri -> { + return this.redirectStrategy.sendRedirect(exchange.getExchange(), URI.create(logoutUri)); + }); + } + return Mono.empty().then(); + } } \ No newline at end of file diff --git a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizedClientRepository.java b/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizedClientRepository.java index a41358d36..3f0e74c18 100644 --- a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizedClientRepository.java +++ b/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizedClientRepository.java @@ -29,245 +29,229 @@ /** *

- * Work around the single tenancy nature of {@link OAuth2AuthenticationToken} - * and {@link InMemoryReactiveClientRegistrationRepository}: if a user - * authenticates sequentially on several OP, his OAuth2AuthenticationToken will - * contain an {@link OAuth2User} corresponding only to the last OP he - * authenticated with. To work around this limitation, this repository keeps an - * OAuth2User for each OP (issuer) and resolves the authorization client with - * the right subject for each issuer. + * Work around the single tenancy nature of {@link OAuth2AuthenticationToken} and {@link InMemoryReactiveClientRegistrationRepository}: if a user authenticates + * sequentially on several OP, his OAuth2AuthenticationToken will contain an {@link OAuth2User} corresponding only to the last OP he authenticated with. To work + * around this limitation, this repository keeps an OAuth2User for each OP (issuer) and resolves the authorization client with the right subject for each + * issuer. *

*

- * This repo is also a session listener to keep track of all the (issuer, - * subject) pairs and their associations with sessions (many to many relation). - * This enables it to expose the required API for back-channel logout where a - * request is received to remove an authorized client based on its issuer and - * subject but without a session token. + * This repo is also a session listener to keep track of all the (issuer, subject) pairs and their associations with sessions (many to many relation). This + * enables it to expose the required API for back-channel logout where a request is received to remove an authorized client based on its issuer and subject but + * without a session token. *

* * @author Jerome Wacongne ch4mp@c4-soft.com - * */ -public class SpringAddonsServerOAuth2AuthorizedClientRepository - implements ServerOAuth2AuthorizedClientRepository, WebSessionListener { - private static final String OAUTH2_USERS_KEY = "com.c4-soft.spring-addons.OAuth2.client.oauth2-users"; - private static final String AUTHORIZED_CLIENTS_KEY = "com.c4-soft.spring-addons.OAuth2.client.authorized-clients"; - - private static final Map> sessionsByuserId = new ConcurrentHashMap<>(); - private static final Map> userIdsBySessionId = new ConcurrentHashMap<>(); - - private final ReactiveClientRegistrationRepository clientRegistrationRepository; - - private final SpringAddonsWebSessionStore webSessionStore; - - public SpringAddonsServerOAuth2AuthorizedClientRepository( - ReactiveClientRegistrationRepository clientRegistrationRepository, - SpringAddonsWebSessionStore webSessionStore) { - this.clientRegistrationRepository = clientRegistrationRepository; - this.webSessionStore = webSessionStore; - this.webSessionStore.addWebSessionListener(this); - } - - @Override - public void sessionRemoved(String sessionId) { - final var idsToUpdate = getUserIds(sessionId); - for (var id : idsToUpdate) { - setSessions(id.iss(), id.sub(), new HashSet<>(getSessions(id.iss(), id.sub()).stream() - .filter(s -> !(s.getId().equals(sessionId))).collect(Collectors.toSet()))); - } - userIdsBySessionId.remove(sessionId); - } - - @SuppressWarnings("unchecked") - @Override - public Mono loadAuthorizedClient(String clientRegistrationId, - Authentication auth, ServerWebExchange exchange) { - return clientRegistrationRepository.findByRegistrationId(clientRegistrationId).flatMap(reg -> { - final var issuer = reg.getProviderDetails().getIssuerUri(); - return exchange.getSession().flatMap(session -> { - final var subject = getUserSubject(session, issuer).orElse(auth.getName()); - return loadAuthorizedClient(session, issuer, subject).map(ac -> (T) ac); - }); - }); - } - - public Mono loadAuthorizedClient(WebSession session, String issuer, String subject) { - final var authorizedClients = getAuthorizedClients(session); - final var client = authorizedClients.stream() - .filter(ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) - && Objects.equals(ac.getPrincipalName(), subject)) - .findAny().map(c -> { - return Mono.just(c); - }).orElse(Mono.empty()); - return client; - } - - @Override - public Mono saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication auth, - ServerWebExchange exchange) { - if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) { - return exchange.getSession() - .map(session -> saveAuthorizedClient(session, authorizedClient, (OAuth2User) auth.getPrincipal())) - .then(); - } - return Mono.empty(); - } - - private Mono saveAuthorizedClient(WebSession session, OAuth2AuthorizedClient authorizedClient, - OAuth2User user) { - final var issuer = authorizedClient.getClientRegistration().getProviderDetails().getIssuerUri(); - final var subject = user.getAttributes().get(JWTClaimNames.SUBJECT).toString(); - - final var oauth2Users = getOAuth2Users(session); - if (oauth2Users.containsKey(issuer)) { - removeAuthorizedClient(session, issuer, oauth2Users.get(issuer).getName()); - } - oauth2Users.put(issuer, user); - setOAuth2Users(session, oauth2Users); - - final var authorizedClients = getAuthorizedClients(session); - authorizedClients.add(authorizedClient); - setAuthorizedClients(session, authorizedClients); - - final var sessions = getSessions(issuer, subject); - if (!sessions.contains(session)) { - sessions.add(session); - setSessions(issuer, subject, sessions); - } - - final var userIds = getUserIds(session.getId()); - userIds.add(new UserId(issuer, subject)); - setUserIds(session.getId(), userIds); - - return Mono.empty(); - } - - @Override - public Mono removeAuthorizedClient(String clientRegistrationId, Authentication auth, - ServerWebExchange exchange) { - if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) { - return clientRegistrationRepository.findByRegistrationId(clientRegistrationId).map(reg -> { - final var issuer = reg.getProviderDetails().getIssuerUri(); - return exchange.getSession().map(session -> { - final var subject = getUserSubject(session, issuer).orElse(auth.getName()); - - return removeAuthorizedClient(session, issuer, subject); - }); - }).then(); - } - return Mono.empty(); - } - - public Mono removeAuthorizedClient(WebSession session, String issuer, String subject) { - final var allAuthorizedClients = getAuthorizedClients(session); - final var authorizedClientsToRemove = allAuthorizedClients.stream() - .filter(ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) - && Objects.equals(ac.getPrincipalName(), subject)) - .collect(Collectors.toSet()); - allAuthorizedClients.removeAll(authorizedClientsToRemove); - setAuthorizedClients(session, allAuthorizedClients); - - final var oauth2Users = getOAuth2Users(session); - if (oauth2Users.containsKey(issuer)) { - oauth2Users.remove(issuer); - setOAuth2Users(session, oauth2Users); - } - - final var sessions = getSessions(issuer, subject); - if (sessions.contains(session)) { - sessions.remove(session); - setSessions(issuer, subject, sessions); - } - - final var userIds = getUserIds(session.getId()); - userIds.remove(new UserId(issuer, subject)); - - return Mono.empty(); - } - - /** - * Removes an authorized client and returns a list of sessions to invalidate - * (those for which the current user has no more authorized client after this - * one was removed) - * - * @param issuer OP issuer URI - * @param subject current user subject for this OP - * @return the list of user sessions for which this authorized client was the - * last one - */ - public Flux removeAuthorizedClients(String issuer, String subject) { - final var sessions = getSessions(issuer, subject); - - for (var session : sessions) { - removeAuthorizedClient(session, issuer, subject); - } - - return Flux.fromStream(sessions.stream().filter(s -> { - return getAuthorizedClients(s).stream() - .filter(authorizedClient -> authorizedClient.getClientRegistration().getProviderDetails() - .getIssuerUri().equals(issuer) - && authorizedClient.getPrincipalName().equals(subject)) - .count() < 1; - })); - } - - @SuppressWarnings("unchecked") - private Set getAuthorizedClients(WebSession session) { - final var sessionAuthorizedClients = (Set) session.getAttribute(AUTHORIZED_CLIENTS_KEY); - return sessionAuthorizedClients == null ? new HashSet<>() : sessionAuthorizedClients; - } - - private void setAuthorizedClients(WebSession session, Set sessionAuthorizedClients) { - session.getAttributes().put(AUTHORIZED_CLIENTS_KEY, sessionAuthorizedClients); - } - - public Map getOAuth2UsersBySession(WebSession session) { - if (session == null) { - return null; - } - return Collections.unmodifiableMap(getOAuth2Users(session)); - } - - @SuppressWarnings("unchecked") - private Map getOAuth2Users(WebSession s) { - final var sessionOauth2UsersByIssuer = (Map) s.getAttribute(OAUTH2_USERS_KEY); - return sessionOauth2UsersByIssuer == null ? new ConcurrentHashMap() - : sessionOauth2UsersByIssuer; - } - - private void setOAuth2Users(WebSession s, Map sessionOauth2UsersByIssuer) { - s.getAttributes().put(OAUTH2_USERS_KEY, sessionOauth2UsersByIssuer); - } - - private Set getSessions(String issuer, String subject) { - return sessionsByuserId.getOrDefault(new UserId(issuer, subject), new HashSet<>()); - } - - private void setSessions(String issuer, String subject, Set sessions) { - if (sessions == null || sessions.isEmpty()) { - sessionsByuserId.remove(new UserId(issuer, subject)); - } else { - sessionsByuserId.put(new UserId(issuer, subject), sessions); - } - } - - private Set getUserIds(String sessionId) { - return userIdsBySessionId.getOrDefault(sessionId, new HashSet<>()); - } - - private void setUserIds(String sessionId, Set userIds) { - if (userIds == null || userIds.isEmpty()) { - userIdsBySessionId.remove(sessionId); - } else { - userIdsBySessionId.put(sessionId, userIds); - } - } - - private Optional getUserSubject(WebSession session, String issuer) { - final var oauth2Users = getOAuth2Users(session); - return Optional.ofNullable(oauth2Users.get(issuer)).map(u -> u.getAttribute(JWTClaimNames.SUBJECT)); - } - - private static record UserId(String iss, String sub) { - } +public class SpringAddonsServerOAuth2AuthorizedClientRepository implements ServerOAuth2AuthorizedClientRepository, WebSessionListener { + private static final String OAUTH2_USERS_KEY = "com.c4-soft.spring-addons.OAuth2.client.oauth2-users"; + private static final String AUTHORIZED_CLIENTS_KEY = "com.c4-soft.spring-addons.OAuth2.client.authorized-clients"; + + private static final Map> sessionsByuserId = new ConcurrentHashMap<>(); + private static final Map> userIdsBySessionId = new ConcurrentHashMap<>(); + + private final ReactiveClientRegistrationRepository clientRegistrationRepository; + + private final SpringAddonsWebSessionStore webSessionStore; + + public SpringAddonsServerOAuth2AuthorizedClientRepository( + ReactiveClientRegistrationRepository clientRegistrationRepository, + SpringAddonsWebSessionStore webSessionStore) { + this.clientRegistrationRepository = clientRegistrationRepository; + this.webSessionStore = webSessionStore; + this.webSessionStore.addWebSessionListener(this); + } + + @Override + public void sessionRemoved(String sessionId) { + final var idsToUpdate = getUserIds(sessionId); + for (var id : idsToUpdate) { + setSessions( + id.iss(), + id.sub(), + new HashSet<>(getSessions(id.iss(), id.sub()).stream().filter(s -> !(s.getId().equals(sessionId))).collect(Collectors.toSet()))); + } + userIdsBySessionId.remove(sessionId); + } + + @SuppressWarnings("unchecked") + @Override + public Mono loadAuthorizedClient(String clientRegistrationId, Authentication auth, ServerWebExchange exchange) { + return clientRegistrationRepository.findByRegistrationId(clientRegistrationId).flatMap(reg -> { + final var issuer = reg.getProviderDetails().getIssuerUri(); + return exchange.getSession().flatMap(session -> { + final var subject = getUserSubject(session, issuer).orElse(auth.getName()); + return loadAuthorizedClient(session, issuer, subject).map(ac -> (T) ac); + }); + }); + } + + public Mono loadAuthorizedClient(WebSession session, String issuer, String subject) { + final var authorizedClients = getAuthorizedClients(session); + final var client = authorizedClients.stream().filter( + ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) && Objects.equals(ac.getPrincipalName(), subject)) + .findAny().map(c -> { + return Mono.just(c); + }).orElse(Mono.empty()); + return client; + } + + @Override + public Mono saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication auth, ServerWebExchange exchange) { + if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) { + return exchange.getSession().map(session -> saveAuthorizedClient(session, authorizedClient, (OAuth2User) auth.getPrincipal())).then(); + } + return Mono.empty(); + } + + private Mono saveAuthorizedClient(WebSession session, OAuth2AuthorizedClient authorizedClient, OAuth2User user) { + final var issuer = authorizedClient.getClientRegistration().getProviderDetails().getIssuerUri(); + final var subject = user.getAttributes().get(JWTClaimNames.SUBJECT).toString(); + + final var oauth2Users = getOAuth2Users(session); + if (oauth2Users.containsKey(issuer)) { + removeAuthorizedClient(session, issuer, oauth2Users.get(issuer).getName()); + } + oauth2Users.put(issuer, user); + setOAuth2Users(session, oauth2Users); + + final var authorizedClients = getAuthorizedClients(session); + authorizedClients.add(authorizedClient); + setAuthorizedClients(session, authorizedClients); + + final var sessions = getSessions(issuer, subject); + if (!sessions.contains(session)) { + sessions.add(session); + setSessions(issuer, subject, sessions); + } + + final var userIds = getUserIds(session.getId()); + userIds.add(new UserId(issuer, subject)); + setUserIds(session.getId(), userIds); + + return Mono.empty(); + } + + @Override + public Mono removeAuthorizedClient(String clientRegistrationId, Authentication auth, ServerWebExchange exchange) { + if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) { + return clientRegistrationRepository.findByRegistrationId(clientRegistrationId).map(reg -> { + final var issuer = reg.getProviderDetails().getIssuerUri(); + return exchange.getSession().map(session -> { + final var subject = getUserSubject(session, issuer).orElse(auth.getName()); + + return removeAuthorizedClient(session, issuer, subject); + }); + }).then(); + } + return Mono.empty(); + } + + public Mono removeAuthorizedClient(WebSession session, String issuer, String subject) { + final var allAuthorizedClients = getAuthorizedClients(session); + final var authorizedClientsToRemove = allAuthorizedClients.stream().filter( + ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) && Objects.equals(ac.getPrincipalName(), subject)) + .collect(Collectors.toSet()); + allAuthorizedClients.removeAll(authorizedClientsToRemove); + setAuthorizedClients(session, allAuthorizedClients); + + final var oauth2Users = getOAuth2Users(session); + if (oauth2Users.containsKey(issuer)) { + oauth2Users.remove(issuer); + setOAuth2Users(session, oauth2Users); + } + + final var sessions = getSessions(issuer, subject); + if (sessions.contains(session)) { + sessions.remove(session); + setSessions(issuer, subject, sessions); + } + + final var userIds = getUserIds(session.getId()); + userIds.remove(new UserId(issuer, subject)); + + return Mono.empty(); + } + + /** + * Removes an authorized client and returns a list of sessions to invalidate (those for which the current user has no more authorized client after this one + * was removed) + * + * @param issuer OP issuer URI + * @param subject current user subject for this OP + * @return the list of user sessions for which this authorized client was the last one + */ + public Flux removeAuthorizedClients(String issuer, String subject) { + final var sessions = getSessions(issuer, subject); + + for (var session : sessions) { + removeAuthorizedClient(session, issuer, subject); + } + + return Flux.fromStream(sessions.stream().filter(s -> { + return getAuthorizedClients(s).stream() + .filter( + authorizedClient -> authorizedClient.getClientRegistration().getProviderDetails().getIssuerUri().equals(issuer) + && authorizedClient.getPrincipalName().equals(subject)) + .count() < 1; + })); + } + + @SuppressWarnings("unchecked") + private Set getAuthorizedClients(WebSession session) { + final var sessionAuthorizedClients = (Set) session.getAttribute(AUTHORIZED_CLIENTS_KEY); + return sessionAuthorizedClients == null ? new HashSet<>() : sessionAuthorizedClients; + } + + private void setAuthorizedClients(WebSession session, Set sessionAuthorizedClients) { + session.getAttributes().put(AUTHORIZED_CLIENTS_KEY, sessionAuthorizedClients); + } + + public Map getOAuth2UsersBySession(WebSession session) { + if (session == null) { + return null; + } + return Collections.unmodifiableMap(getOAuth2Users(session)); + } + + @SuppressWarnings("unchecked") + private Map getOAuth2Users(WebSession s) { + final var sessionOauth2UsersByIssuer = (Map) s.getAttribute(OAUTH2_USERS_KEY); + return sessionOauth2UsersByIssuer == null ? new ConcurrentHashMap() : sessionOauth2UsersByIssuer; + } + + private void setOAuth2Users(WebSession s, Map sessionOauth2UsersByIssuer) { + s.getAttributes().put(OAUTH2_USERS_KEY, sessionOauth2UsersByIssuer); + } + + private Set getSessions(String issuer, String subject) { + return sessionsByuserId.getOrDefault(new UserId(issuer, subject), new HashSet<>()); + } + + private void setSessions(String issuer, String subject, Set sessions) { + if (sessions == null || sessions.isEmpty()) { + sessionsByuserId.remove(new UserId(issuer, subject)); + } else { + sessionsByuserId.put(new UserId(issuer, subject), sessions); + } + } + + private Set getUserIds(String sessionId) { + return userIdsBySessionId.getOrDefault(sessionId, new HashSet<>()); + } + + private void setUserIds(String sessionId, Set userIds) { + if (userIds == null || userIds.isEmpty()) { + userIdsBySessionId.remove(sessionId); + } else { + userIdsBySessionId.put(sessionId, userIds); + } + } + + private Optional getUserSubject(WebSession session, String issuer) { + final var oauth2Users = getOAuth2Users(session); + return Optional.ofNullable(oauth2Users.get(issuer)).map(u -> u.getAttribute(JWTClaimNames.SUBJECT)); + } + + private static record UserId(String iss, String sub) { + } } diff --git a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ClientAuthorizeExchangeSpecPostProcessor.java b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ClientAuthorizeExchangeSpecPostProcessor.java index 195afe400..11fddb3b7 100644 --- a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ClientAuthorizeExchangeSpecPostProcessor.java +++ b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ClientAuthorizeExchangeSpecPostProcessor.java @@ -3,5 +3,5 @@ import org.springframework.security.config.web.server.ServerHttpSecurity; public interface ClientAuthorizeExchangeSpecPostProcessor { - ServerHttpSecurity.AuthorizeExchangeSpec authorizeHttpRequests(ServerHttpSecurity.AuthorizeExchangeSpec spec); + ServerHttpSecurity.AuthorizeExchangeSpec authorizeHttpRequests(ServerHttpSecurity.AuthorizeExchangeSpec spec); } \ No newline at end of file diff --git a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ClientHttpSecurityPostProcessor.java b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ClientHttpSecurityPostProcessor.java index f3be27eb8..0a811e8dc 100644 --- a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ClientHttpSecurityPostProcessor.java +++ b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ClientHttpSecurityPostProcessor.java @@ -3,5 +3,5 @@ import org.springframework.security.config.web.server.ServerHttpSecurity; public interface ClientHttpSecurityPostProcessor { - ServerHttpSecurity process(ServerHttpSecurity serverHttpSecurity); + ServerHttpSecurity process(ServerHttpSecurity serverHttpSecurity); } \ No newline at end of file diff --git a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ReactiveConfigurationSupport.java b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ReactiveConfigurationSupport.java index c0eaa9e63..ec704e49b 100644 --- a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ReactiveConfigurationSupport.java +++ b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ReactiveConfigurationSupport.java @@ -19,116 +19,110 @@ public class ReactiveConfigurationSupport { - public static ServerHttpSecurity configureResourceServer( - ServerHttpSecurity http, - ServerProperties serverProperties, - SpringAddonsSecurityProperties addonsResourceServerProperties, - ServerAccessDeniedHandler accessDeniedHandler, - ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor, - ResourceServerHttpSecurityPostProcessor httpPostProcessor) { - - ReactiveConfigurationSupport.configureCors(http, addonsResourceServerProperties.getCors()); - ReactiveConfigurationSupport.configureState(http, addonsResourceServerProperties.isStatlessSessions(), - addonsResourceServerProperties.getCsrf()); - ReactiveConfigurationSupport.configureAccess(http, addonsResourceServerProperties.getPermitAll()); - - http.exceptionHandling(handling -> handling.accessDeniedHandler(accessDeniedHandler)); - - if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { - http.redirectToHttps(withDefaults()); - } - - http.authorizeExchange(registry -> authorizePostProcessor.authorizeHttpRequests(registry)); - httpPostProcessor.process(http); - - return http; - } - - public static ServerHttpSecurity configureClient( - ServerHttpSecurity http, - ServerProperties serverProperties, - SpringAddonsOAuth2ClientProperties addonsClientProperties, - ClientAuthorizeExchangeSpecPostProcessor authorizePostProcessor, - ClientHttpSecurityPostProcessor httpPostProcessor) { - - ReactiveConfigurationSupport.configureCors(http, addonsClientProperties.getCors()); - ReactiveConfigurationSupport.configureState(http, false, addonsClientProperties.getCsrf()); - ReactiveConfigurationSupport.configureAccess(http, addonsClientProperties.getPermitAll()); - - if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { - http.redirectToHttps(withDefaults()); - } - - http.authorizeExchange(registry -> authorizePostProcessor.authorizeHttpRequests(registry)); - httpPostProcessor.process(http); - - return http; - } - - public static ServerHttpSecurity configureAccess(ServerHttpSecurity http, String[] permitAll) { - if (permitAll.length > 0) { - http.anonymous(withDefaults()); - http.authorizeExchange(authorizeExchange -> authorizeExchange.pathMatchers(permitAll).permitAll()); - } - return http; - } - - public static ServerHttpSecurity configureCors(ServerHttpSecurity http, CorsProperties[] corsProperties) { - if (corsProperties.length == 0) { - http.cors(cors -> cors.disable()); - } else { - final var source = new UrlBasedCorsConfigurationSource(); - for (final var corsProps : corsProperties) { - final var configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); - configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); - configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); - configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); - source.registerCorsConfiguration(corsProps.getPath(), configuration); - } - http.cors(cors -> cors.configurationSource(source)); - } - return http; - } - - public static ServerHttpSecurity configureState( - ServerHttpSecurity http, - boolean isStatless, - SpringAddonsSecurityProperties.Csrf csrfEnum) { - - if (isStatless) { - http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); - } - - http.csrf(csrf -> { - var delegate = new XorServerCsrfTokenRequestAttributeHandler(); - switch (csrfEnum) { - case DISABLE: - csrf.disable(); - break; - case DEFAULT: - if (isStatless) { - csrf.disable(); - } else { - withDefaults(); - } - break; - case SESSION: - withDefaults(); - break; - case COOKIE_HTTP_ONLY: - // https://docs.spring.io/spring-security/reference/5.8/migration/reactive.html#_i_am_using_angularjs_or_another_javascript_framework - csrf.csrfTokenRepository(new CookieServerCsrfTokenRepository()) - .csrfTokenRequestHandler(delegate::handle); - break; - case COOKIE_ACCESSIBLE_FROM_JS: - // https://docs.spring.io/spring-security/reference/5.8/migration/reactive.html#_i_am_using_angularjs_or_another_javascript_framework - csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) - .csrfTokenRequestHandler(delegate::handle); - break; - } - }); - - return http; - } + public static ServerHttpSecurity configureResourceServer( + ServerHttpSecurity http, + ServerProperties serverProperties, + SpringAddonsSecurityProperties addonsResourceServerProperties, + ServerAccessDeniedHandler accessDeniedHandler, + ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor, + ResourceServerHttpSecurityPostProcessor httpPostProcessor) { + + ReactiveConfigurationSupport.configureCors(http, addonsResourceServerProperties.getCors()); + ReactiveConfigurationSupport.configureState(http, addonsResourceServerProperties.isStatlessSessions(), addonsResourceServerProperties.getCsrf()); + ReactiveConfigurationSupport.configureAccess(http, addonsResourceServerProperties.getPermitAll()); + + http.exceptionHandling(handling -> handling.accessDeniedHandler(accessDeniedHandler)); + + if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { + http.redirectToHttps(withDefaults()); + } + + http.authorizeExchange(registry -> authorizePostProcessor.authorizeHttpRequests(registry)); + httpPostProcessor.process(http); + + return http; + } + + public static ServerHttpSecurity configureClient( + ServerHttpSecurity http, + ServerProperties serverProperties, + SpringAddonsOAuth2ClientProperties addonsClientProperties, + ClientAuthorizeExchangeSpecPostProcessor authorizePostProcessor, + ClientHttpSecurityPostProcessor httpPostProcessor) { + + ReactiveConfigurationSupport.configureCors(http, addonsClientProperties.getCors()); + ReactiveConfigurationSupport.configureState(http, false, addonsClientProperties.getCsrf()); + ReactiveConfigurationSupport.configureAccess(http, addonsClientProperties.getPermitAll()); + + if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { + http.redirectToHttps(withDefaults()); + } + + http.authorizeExchange(registry -> authorizePostProcessor.authorizeHttpRequests(registry)); + httpPostProcessor.process(http); + + return http; + } + + public static ServerHttpSecurity configureAccess(ServerHttpSecurity http, String[] permitAll) { + if (permitAll.length > 0) { + http.anonymous(withDefaults()); + http.authorizeExchange(authorizeExchange -> authorizeExchange.pathMatchers(permitAll).permitAll()); + } + return http; + } + + public static ServerHttpSecurity configureCors(ServerHttpSecurity http, CorsProperties[] corsProperties) { + if (corsProperties.length == 0) { + http.cors(cors -> cors.disable()); + } else { + final var source = new UrlBasedCorsConfigurationSource(); + for (final var corsProps : corsProperties) { + final var configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); + configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); + configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); + configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); + source.registerCorsConfiguration(corsProps.getPath(), configuration); + } + http.cors(cors -> cors.configurationSource(source)); + } + return http; + } + + public static ServerHttpSecurity configureState(ServerHttpSecurity http, boolean isStatless, SpringAddonsSecurityProperties.Csrf csrfEnum) { + + if (isStatless) { + http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); + } + + http.csrf(csrf -> { + var delegate = new XorServerCsrfTokenRequestAttributeHandler(); + switch (csrfEnum) { + case DISABLE: + csrf.disable(); + break; + case DEFAULT: + if (isStatless) { + csrf.disable(); + } else { + withDefaults(); + } + break; + case SESSION: + withDefaults(); + break; + case COOKIE_HTTP_ONLY: + // https://docs.spring.io/spring-security/reference/5.8/migration/reactive.html#_i_am_using_angularjs_or_another_javascript_framework + csrf.csrfTokenRepository(new CookieServerCsrfTokenRepository()).csrfTokenRequestHandler(delegate::handle); + break; + case COOKIE_ACCESSIBLE_FROM_JS: + // https://docs.spring.io/spring-security/reference/5.8/migration/reactive.html#_i_am_using_angularjs_or_another_javascript_framework + csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(delegate::handle); + break; + } + }); + + return http; + } } diff --git a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ResourceServerAuthorizeExchangeSpecPostProcessor.java b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ResourceServerAuthorizeExchangeSpecPostProcessor.java index 0d12813a1..fafa419db 100644 --- a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ResourceServerAuthorizeExchangeSpecPostProcessor.java +++ b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ResourceServerAuthorizeExchangeSpecPostProcessor.java @@ -8,7 +8,6 @@ * Customize access-control for routes which where not listed in {@link SpringAddonsSecurityProperties#permitAll} * * @author ch4mp - * */ public interface ResourceServerAuthorizeExchangeSpecPostProcessor { ServerHttpSecurity.AuthorizeExchangeSpec authorizeHttpRequests(ServerHttpSecurity.AuthorizeExchangeSpec spec); diff --git a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ResourceServerHttpSecurityPostProcessor.java b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ResourceServerHttpSecurityPostProcessor.java index 4edb2a2b1..f32f61b24 100644 --- a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ResourceServerHttpSecurityPostProcessor.java +++ b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ResourceServerHttpSecurityPostProcessor.java @@ -3,12 +3,11 @@ import org.springframework.security.config.web.server.ServerHttpSecurity; /** - * Process {@link ServerHttpSecurity} of default security filter-chain after it was processed by spring-addons. - * This enables to override anything that was auto-configured (or add to it). + * Process {@link ServerHttpSecurity} of default security filter-chain after it was processed by spring-addons. This enables to override anything that was + * auto-configured (or add to it). * * @author ch4mp - * */ public interface ResourceServerHttpSecurityPostProcessor { - ServerHttpSecurity process(ServerHttpSecurity serverHttpSecurity); + ServerHttpSecurity process(ServerHttpSecurity serverHttpSecurity); } diff --git a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ServerHttpRequestSupport.java b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ServerHttpRequestSupport.java index 2d7946a02..044c0e078 100644 --- a/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ServerHttpRequestSupport.java +++ b/webflux/spring-addons-webflux-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/ServerHttpRequestSupport.java @@ -17,84 +17,76 @@ * Support class to statically access current request. *

*

- * It is mainly intended at parsing additional headers when authorizing - * requests. + * It is mainly intended at parsing additional headers when authorizing requests. *

* * @author ch4mp */ public class ServerHttpRequestSupport { - /** - * @return the request in current context - */ - public static Mono getRequest() { - return Mono.deferContextual(Mono::just) - .map(contextView -> contextView.get(ServerWebExchange.class).getRequest()); - } + /** + * @return the request in current context + */ + public static Mono getRequest() { + return Mono.deferContextual(Mono::just).map(contextView -> contextView.get(ServerWebExchange.class).getRequest()); + } - public static Mono getSession() { - return Mono.deferContextual(Mono::just) - .flatMap(contextView -> contextView.get(ServerWebExchange.class).getSession()); - } + public static Mono getSession() { + return Mono.deferContextual(Mono::just).flatMap(contextView -> contextView.get(ServerWebExchange.class).getSession()); + } - /** - * @param headerName name of the header to retrieve - * @return the unique value for the given header in current request - * @throws MissingHeaderException if no non-empty value is found for that - * header - * @throws MultiValuedHeaderException more than one non-empty value is found for - * that header - */ - public static Mono getUniqueHeader(String headerName) - throws MissingHeaderException, MultiValuedHeaderException { - try { - return getNonEmptyHeaderValues(headerName).single(); - } catch (NoSuchElementException e) { - throw new MissingHeaderException(headerName); - } catch (IndexOutOfBoundsException e) { - throw new MultiValuedHeaderException(headerName); - } + /** + * @param headerName name of the header to retrieve + * @return the unique value for the given header in current request + * @throws MissingHeaderException if no non-empty value is found for that header + * @throws MultiValuedHeaderException more than one non-empty value is found for that header + */ + public static Mono getUniqueHeader(String headerName) throws MissingHeaderException, MultiValuedHeaderException { + try { + return getNonEmptyHeaderValues(headerName).single(); + } catch (NoSuchElementException e) { + throw new MissingHeaderException(headerName); + } catch (IndexOutOfBoundsException e) { + throw new MultiValuedHeaderException(headerName); + } - } + } - /** - * @param headerName the name of the header to retrieve - * @return a stream of non empty values for a given header from the request in - * current context - */ - public static Flux getNonEmptyHeaderValues(String headerName) { - return getRequest().flatMapMany(req -> Flux - .fromStream(req.getHeaders().getOrEmpty(headerName).stream().filter(StringUtils::hasLength))); - } + /** + * @param headerName the name of the header to retrieve + * @return a stream of non empty values for a given header from the request in current context + */ + public static Flux getNonEmptyHeaderValues(String headerName) { + return getRequest().flatMapMany(req -> Flux.fromStream(req.getHeaders().getOrEmpty(headerName).stream().filter(StringUtils::hasLength))); + } - @ResponseStatus(code = HttpStatus.UNAUTHORIZED) - public static class MissingHeaderException extends RuntimeException { - private static final long serialVersionUID = -4894061353773464761L; + @ResponseStatus(code = HttpStatus.UNAUTHORIZED) + public static class MissingHeaderException extends RuntimeException { + private static final long serialVersionUID = -4894061353773464761L; - public MissingHeaderException(String headerName) { - super(headerName + " is missing"); - assert (StringUtils.hasText(headerName)); - } - } + public MissingHeaderException(String headerName) { + super(headerName + " is missing"); + assert (StringUtils.hasText(headerName)); + } + } - @ResponseStatus(code = HttpStatus.UNAUTHORIZED) - public static class MultiValuedHeaderException extends RuntimeException { - private static final long serialVersionUID = 1654993007508549674L; + @ResponseStatus(code = HttpStatus.UNAUTHORIZED) + public static class MultiValuedHeaderException extends RuntimeException { + private static final long serialVersionUID = 1654993007508549674L; - public MultiValuedHeaderException(String headerName) { - super(headerName + " is not unique"); - assert (StringUtils.hasText(headerName)); - } - } + public MultiValuedHeaderException(String headerName) { + super(headerName + " is not unique"); + assert (StringUtils.hasText(headerName)); + } + } - @ResponseStatus(code = HttpStatus.UNAUTHORIZED) - public static class InvalidHeaderException extends RuntimeException { - private static final long serialVersionUID = -6233252290377524340L; + @ResponseStatus(code = HttpStatus.UNAUTHORIZED) + public static class InvalidHeaderException extends RuntimeException { + private static final long serialVersionUID = -6233252290377524340L; - public InvalidHeaderException(String headerName) { - super(headerName + " is not valid"); - assert (StringUtils.hasText(headerName)); - } - } + public InvalidHeaderException(String headerName) { + super(headerName + " is not valid"); + assert (StringUtils.hasText(headerName)); + } + } } \ No newline at end of file diff --git a/webflux/spring-addons-webflux-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java b/webflux/spring-addons-webflux-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java index cae61f717..9f5fb0677 100644 --- a/webflux/spring-addons-webflux-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java +++ b/webflux/spring-addons-webflux-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java @@ -19,17 +19,16 @@ @Import({ SpringAddonsSecurityProperties.class }) public class AddonsSecurityBeans { - /** - * Retrieves granted authorities from the introspected token attributes - * according to the configuration set for issuer (iss attribute) - * - * @param securityProperties - * @return - */ - @ConditionalOnMissingBean - @Bean - OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { - log.debug("Building default CorsConfigurationSource with: {}", addonsProperties); - return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); - } + /** + * Retrieves granted authorities from the introspected token attributes according to the configuration set for issuer (iss attribute) + * + * @param securityProperties + * @return + */ + @ConditionalOnMissingBean + @Bean + OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { + log.debug("Building default CorsConfigurationSource with: {}", addonsProperties); + return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); + } } \ No newline at end of file diff --git a/webflux/spring-addons-webflux-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java b/webflux/spring-addons-webflux-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java index 0a9d8c629..7bf3655a8 100644 --- a/webflux/spring-addons-webflux-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java +++ b/webflux/spring-addons-webflux-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java @@ -13,8 +13,8 @@ /** *

- * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or - * repositories (web context is not desired in that case). + * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or repositories (web context + * is not desired in that case). *

* See {@link AutoConfigureAddonsSecurity} * diff --git a/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java b/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java index a7ecf787a..9556e9d39 100644 --- a/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java +++ b/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java @@ -19,17 +19,16 @@ @Import({ SpringAddonsSecurityProperties.class }) public class AddonsSecurityBeans { - /** - * Retrieves granted authorities from the Jwt (from its private claims or with - * the help of an external service) - * - * @param securityProperties - * @return - */ - @ConditionalOnMissingBean - @Bean - OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { - log.debug("Building default CorsConfigurationSource with: {}", addonsProperties); - return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); - } + /** + * Retrieves granted authorities from the Jwt (from its private claims or with the help of an external service) + * + * @param securityProperties + * @return + */ + @ConditionalOnMissingBean + @Bean + OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { + log.debug("Building default CorsConfigurationSource with: {}", addonsProperties); + return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); + } } \ No newline at end of file diff --git a/webflux/spring-addons-webflux-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/jwt/AutoConfigureAddonsWebSecurity.java b/webflux/spring-addons-webflux-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/jwt/AutoConfigureAddonsWebSecurity.java index f827859ad..c89efa072 100644 --- a/webflux/spring-addons-webflux-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/jwt/AutoConfigureAddonsWebSecurity.java +++ b/webflux/spring-addons-webflux-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/jwt/AutoConfigureAddonsWebSecurity.java @@ -13,8 +13,8 @@ /** *

- * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or - * repositories (web context is not desired in that case). + * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or repositories (web context + * is not desired in that case). *

* See {@link AutoConfigureAddonsSecurity} * diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AddonsWebfluxTestConf.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AddonsWebfluxTestConf.java index e2674902a..15b2b627b 100644 --- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AddonsWebfluxTestConf.java +++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AddonsWebfluxTestConf.java @@ -48,148 +48,146 @@ @Import({ WebTestClientProperties.class }) public class AddonsWebfluxTestConf { - @MockBean - ReactiveJwtDecoder jwtDecoder; - - @MockBean - ReactiveAuthenticationManagerResolver jwtIssuerReactiveAuthenticationManagerResolver; - - @MockBean - ReactiveOpaqueTokenIntrospector introspector; - - @ConditionalOnMissingBean - @Bean - InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { - final var clientRegistrationRepository = mock(InMemoryReactiveClientRegistrationRepository.class); - when(clientRegistrationRepository.iterator()).thenReturn(new ArrayList().iterator()); - when(clientRegistrationRepository.spliterator()).thenReturn(new ArrayList().spliterator()); - return clientRegistrationRepository; - } - - @MockBean - ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService; - - @Bean - HttpSecurity httpSecurity() { - return mock(HttpSecurity.class); - } - - @Bean - @Scope("prototype") - WebTestClientSupport webTestClientSupport( - WebTestClientProperties webTestClientProperties, - WebTestClient webTestClient, - SpringAddonsSecurityProperties addonsProperties) { - return new WebTestClientSupport(webTestClientProperties, webTestClient, addonsProperties); - } - - @ConditionalOnMissingBean - @Bean - OAuth2AuthoritiesConverter authoritiesConverter() { - return mock(OAuth2AuthoritiesConverter.class); - } - - @ConditionalOnMissingBean - @Bean - ServerAccessDeniedHandler serverAccessDeniedHandler() { - return (var exchange, var ex) -> exchange.getPrincipal().flatMap(principal -> { - var response = exchange.getResponse(); - response.setStatusCode( - principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED : HttpStatus.FORBIDDEN); - response.getHeaders().setContentType(MediaType.TEXT_PLAIN); - var dataBufferFactory = response.bufferFactory(); - var buffer = dataBufferFactory.wrap(ex.getMessage().getBytes(Charset.defaultCharset())); - return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer)); - }); - } - - @ConditionalOnMissingBean - @Bean - SecurityWebFilterChain springAddonsResourceServerSecurityFilterChain( - ServerHttpSecurity http, - ServerAccessDeniedHandler accessDeniedHandler, - SpringAddonsSecurityProperties addonsProperties, - ServerProperties serverProperties, - ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor, - ResourceServerHttpSecurityPostProcessor httpPostProcessor, - CorsConfigurationSource corsConfigurationSource) - throws Exception { - - if (addonsProperties.getPermitAll().length > 0) { - http.anonymous(); - } - - if (addonsProperties.getCors().length > 0) { - http.cors().configurationSource(corsConfigurationSource); - } else { - http.cors().disable(); - } - - switch (addonsProperties.getCsrf()) { - case DISABLE: - http.csrf().disable(); - break; - case DEFAULT: - if (addonsProperties.isStatlessSessions()) { - http.csrf().disable(); - } else { - http.csrf(); - } - break; - case SESSION: - break; - case COOKIE_HTTP_ONLY: - http.csrf().csrfTokenRepository(new CookieServerCsrfTokenRepository()); - break; - case COOKIE_ACCESSIBLE_FROM_JS: - http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) - .csrfTokenRequestHandler(new XorServerCsrfTokenRequestAttributeHandler()::handle); - break; - } - - if (addonsProperties.isStatlessSessions()) { - http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); - } - - if (!addonsProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) { - http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); - } - - if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { - http.redirectToHttps(); - } - - authorizePostProcessor.authorizeHttpRequests( - http.authorizeExchange().pathMatchers(addonsProperties.getPermitAll()).permitAll()); - - return httpPostProcessor.process(http).build(); - } - - @ConditionalOnMissingBean - @Bean - ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor() { - return (ServerHttpSecurity.AuthorizeExchangeSpec spec) -> spec.anyExchange().authenticated(); - } - - @ConditionalOnMissingBean - @Bean - ResourceServerHttpSecurityPostProcessor httpPostProcessor() { - return serverHttpSecurity -> serverHttpSecurity; - } - - @ConditionalOnMissingBean - @Bean - CorsConfigurationSource corsConfigurationSource(SpringAddonsSecurityProperties addonsProperties) { - final var source = new UrlBasedCorsConfigurationSource(); - for (final var corsProps : addonsProperties.getCors()) { - final var configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); - configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); - configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); - configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); - source.registerCorsConfiguration(corsProps.getPath(), configuration); - } - return source; - } + @MockBean + ReactiveJwtDecoder jwtDecoder; + + @MockBean + ReactiveAuthenticationManagerResolver jwtIssuerReactiveAuthenticationManagerResolver; + + @MockBean + ReactiveOpaqueTokenIntrospector introspector; + + @ConditionalOnMissingBean + @Bean + InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { + final var clientRegistrationRepository = mock(InMemoryReactiveClientRegistrationRepository.class); + when(clientRegistrationRepository.iterator()).thenReturn(new ArrayList().iterator()); + when(clientRegistrationRepository.spliterator()).thenReturn(new ArrayList().spliterator()); + return clientRegistrationRepository; + } + + @MockBean + ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService; + + @Bean + HttpSecurity httpSecurity() { + return mock(HttpSecurity.class); + } + + @Bean + @Scope("prototype") + WebTestClientSupport webTestClientSupport( + WebTestClientProperties webTestClientProperties, + WebTestClient webTestClient, + SpringAddonsSecurityProperties addonsProperties) { + return new WebTestClientSupport(webTestClientProperties, webTestClient, addonsProperties); + } + + @ConditionalOnMissingBean + @Bean + OAuth2AuthoritiesConverter authoritiesConverter() { + return mock(OAuth2AuthoritiesConverter.class); + } + + @ConditionalOnMissingBean + @Bean + ServerAccessDeniedHandler serverAccessDeniedHandler() { + return (var exchange, var ex) -> exchange.getPrincipal().flatMap(principal -> { + var response = exchange.getResponse(); + response.setStatusCode(principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED : HttpStatus.FORBIDDEN); + response.getHeaders().setContentType(MediaType.TEXT_PLAIN); + var dataBufferFactory = response.bufferFactory(); + var buffer = dataBufferFactory.wrap(ex.getMessage().getBytes(Charset.defaultCharset())); + return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer)); + }); + } + + @ConditionalOnMissingBean + @Bean + SecurityWebFilterChain springAddonsResourceServerSecurityFilterChain( + ServerHttpSecurity http, + ServerAccessDeniedHandler accessDeniedHandler, + SpringAddonsSecurityProperties addonsProperties, + ServerProperties serverProperties, + ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor, + ResourceServerHttpSecurityPostProcessor httpPostProcessor, + CorsConfigurationSource corsConfigurationSource) + throws Exception { + + if (addonsProperties.getPermitAll().length > 0) { + http.anonymous(); + } + + if (addonsProperties.getCors().length > 0) { + http.cors().configurationSource(corsConfigurationSource); + } else { + http.cors().disable(); + } + + switch (addonsProperties.getCsrf()) { + case DISABLE: + http.csrf().disable(); + break; + case DEFAULT: + if (addonsProperties.isStatlessSessions()) { + http.csrf().disable(); + } else { + http.csrf(); + } + break; + case SESSION: + break; + case COOKIE_HTTP_ONLY: + http.csrf().csrfTokenRepository(new CookieServerCsrfTokenRepository()); + break; + case COOKIE_ACCESSIBLE_FROM_JS: + http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) + .csrfTokenRequestHandler(new XorServerCsrfTokenRequestAttributeHandler()::handle); + break; + } + + if (addonsProperties.isStatlessSessions()) { + http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); + } + + if (!addonsProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) { + http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); + } + + if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { + http.redirectToHttps(); + } + + authorizePostProcessor.authorizeHttpRequests(http.authorizeExchange().pathMatchers(addonsProperties.getPermitAll()).permitAll()); + + return httpPostProcessor.process(http).build(); + } + + @ConditionalOnMissingBean + @Bean + ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor() { + return (ServerHttpSecurity.AuthorizeExchangeSpec spec) -> spec.anyExchange().authenticated(); + } + + @ConditionalOnMissingBean + @Bean + ResourceServerHttpSecurityPostProcessor httpPostProcessor() { + return serverHttpSecurity -> serverHttpSecurity; + } + + @ConditionalOnMissingBean + @Bean + CorsConfigurationSource corsConfigurationSource(SpringAddonsSecurityProperties addonsProperties) { + final var source = new UrlBasedCorsConfigurationSource(); + for (final var corsProps : addonsProperties.getCors()) { + final var configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); + configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); + configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); + configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); + source.registerCorsConfiguration(corsProps.getPath(), configuration); + } + return source; + } } \ No newline at end of file diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AuthenticationConfigurer.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AuthenticationConfigurer.java index a81d234a5..c630b198b 100644 --- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AuthenticationConfigurer.java +++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AuthenticationConfigurer.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.webflux; diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/MockAuthenticationWebTestClientConfigurer.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/MockAuthenticationWebTestClientConfigurer.java index bf15d5089..65a1b26b7 100644 --- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/MockAuthenticationWebTestClientConfigurer.java +++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/MockAuthenticationWebTestClientConfigurer.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.webflux; diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/OidcIdAuthenticationTokenWebTestClientConfigurer.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/OidcIdAuthenticationTokenWebTestClientConfigurer.java index 629f23354..ae579faff 100644 --- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/OidcIdAuthenticationTokenWebTestClientConfigurer.java +++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/OidcIdAuthenticationTokenWebTestClientConfigurer.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.webflux; diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/WebTestClientSupport.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/WebTestClientSupport.java index f422b9d8a..7ead9a807 100644 --- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/WebTestClientSupport.java +++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/WebTestClientSupport.java @@ -1,14 +1,13 @@ /* * Copyright 2020 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.webflux; @@ -26,99 +25,88 @@ /** * You may configure in your test properties: *
    - *
  • {@code com.c4-soft.springaddons.test.web.default-charset} defaulted to - * utf-8 - *
  • {@code com.c4-soft.springaddons.test.web.default-media-type} defaulted to - * application+json + *
  • {@code com.c4-soft.springaddons.test.web.default-charset} defaulted to utf-8 + *
  • {@code com.c4-soft.springaddons.test.web.default-media-type} defaulted to application+json *
* * @author Jérôme Wacongne <ch4mp@c4-soft.com> */ public class WebTestClientSupport { - private MediaType mediaType; - - private Charset charset; - - private WebTestClient delegate; - - public WebTestClientSupport( - WebTestClientProperties webTestClientProperties, - WebTestClient webTestClient, - SpringAddonsSecurityProperties addonsProperties) { - this.mediaType = MediaType.valueOf(webTestClientProperties.getDefaultMediaType()); - this.charset = Charset.forName(webTestClientProperties.getDefaultCharset()); - this.delegate = webTestClient; - this.setCsrf(!addonsProperties.getCsrf().equals(Csrf.DISABLE)); - } - - /** - * @param mediaType override configured default media-type - * @return - */ - public WebTestClientSupport setMediaType(MediaType mediaType) { - this.mediaType = mediaType; - return this; - } - - /** - * @param charset override configured default charset - * @return - */ - public WebTestClientSupport setCharset(Charset charset) { - this.charset = charset; - return this; - } - - public ResponseSpec get(MediaType accept, String uriTemplate, Object... uriVars) { - return delegate.get().uri(uriTemplate, uriVars).accept(accept).exchange(); - } - - public ResponseSpec get(String uriTemplate, Object... uriVars) { - return get(new MediaType(mediaType, charset), uriTemplate, uriVars); - } - - public ResponseSpec post(T payload, MediaType contentType, Charset charset, MediaType accept, - String uriTemplate, Object... uriVars) { - return delegate.post().uri(uriTemplate, uriVars).accept(accept).contentType(new MediaType(contentType, charset)) - .bodyValue(payload).exchange(); - } - - public ResponseSpec post(T payload, String uriTemplate, Object... uriVars) { - return post(payload, mediaType, charset, mediaType, uriTemplate, uriVars); - } - - public ResponseSpec put(T payload, MediaType contentType, Charset charset, String uriTemplate, - Object... uriVars) { - return delegate.put().uri(uriTemplate, uriVars).contentType(new MediaType(contentType, charset)) - .bodyValue(payload).exchange(); - } - - public ResponseSpec put(T payload, String uriTemplate, Object... uriVars) { - return put(payload, mediaType, charset, uriTemplate, uriVars); - } - - public ResponseSpec patch(T payload, MediaType contentType, Charset charset, String uriTemplate, - Object... uriVars) { - return delegate.patch().uri(uriTemplate, uriVars).contentType(new MediaType(contentType, charset)) - .bodyValue(payload).exchange(); - } - - public ResponseSpec patch(T payload, String uriTemplate, Object... uriVars) { - return patch(payload, mediaType, charset, uriTemplate, uriVars); - } - - public ResponseSpec delete(String uriTemplate, Object... uriVars) { - return delegate.delete().uri(uriTemplate, uriVars).exchange(); - } - - public WebTestClientSupport mutateWith(WebTestClientConfigurer configurer) { - delegate = delegate.mutateWith(configurer); - return this; - } - - public WebTestClientSupport setCsrf(boolean isCsrf) { - delegate.mutateWith(SecurityMockServerConfigurers.csrf()); - return this; - } + private MediaType mediaType; + + private Charset charset; + + private WebTestClient delegate; + + public WebTestClientSupport(WebTestClientProperties webTestClientProperties, WebTestClient webTestClient, SpringAddonsSecurityProperties addonsProperties) { + this.mediaType = MediaType.valueOf(webTestClientProperties.getDefaultMediaType()); + this.charset = Charset.forName(webTestClientProperties.getDefaultCharset()); + this.delegate = webTestClient; + this.setCsrf(!addonsProperties.getCsrf().equals(Csrf.DISABLE)); + } + + /** + * @param mediaType override configured default media-type + * @return + */ + public WebTestClientSupport setMediaType(MediaType mediaType) { + this.mediaType = mediaType; + return this; + } + + /** + * @param charset override configured default charset + * @return + */ + public WebTestClientSupport setCharset(Charset charset) { + this.charset = charset; + return this; + } + + public ResponseSpec get(MediaType accept, String uriTemplate, Object... uriVars) { + return delegate.get().uri(uriTemplate, uriVars).accept(accept).exchange(); + } + + public ResponseSpec get(String uriTemplate, Object... uriVars) { + return get(new MediaType(mediaType, charset), uriTemplate, uriVars); + } + + public ResponseSpec post(T payload, MediaType contentType, Charset charset, MediaType accept, String uriTemplate, Object... uriVars) { + return delegate.post().uri(uriTemplate, uriVars).accept(accept).contentType(new MediaType(contentType, charset)).bodyValue(payload).exchange(); + } + + public ResponseSpec post(T payload, String uriTemplate, Object... uriVars) { + return post(payload, mediaType, charset, mediaType, uriTemplate, uriVars); + } + + public ResponseSpec put(T payload, MediaType contentType, Charset charset, String uriTemplate, Object... uriVars) { + return delegate.put().uri(uriTemplate, uriVars).contentType(new MediaType(contentType, charset)).bodyValue(payload).exchange(); + } + + public ResponseSpec put(T payload, String uriTemplate, Object... uriVars) { + return put(payload, mediaType, charset, uriTemplate, uriVars); + } + + public ResponseSpec patch(T payload, MediaType contentType, Charset charset, String uriTemplate, Object... uriVars) { + return delegate.patch().uri(uriTemplate, uriVars).contentType(new MediaType(contentType, charset)).bodyValue(payload).exchange(); + } + + public ResponseSpec patch(T payload, String uriTemplate, Object... uriVars) { + return patch(payload, mediaType, charset, uriTemplate, uriVars); + } + + public ResponseSpec delete(String uriTemplate, Object... uriVars) { + return delegate.delete().uri(uriTemplate, uriVars).exchange(); + } + + public WebTestClientSupport mutateWith(WebTestClientConfigurer configurer) { + delegate = delegate.mutateWith(configurer); + return this; + } + + public WebTestClientSupport setCsrf(boolean isCsrf) { + delegate.mutateWith(SecurityMockServerConfigurers.csrf()); + return this; + } } diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsBackChannelLogoutBeans.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsBackChannelLogoutBeans.java index 23ecf42d9..04456a0da 100644 --- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsBackChannelLogoutBeans.java +++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsBackChannelLogoutBeans.java @@ -38,131 +38,110 @@ /** *

- * This provides with a client side implementation of the OIDC - * Back-Channel - * Logout specification. Keycloak conforms to this OP side of the spec. - * Auth0 - * could some day. + * This provides with a client side implementation of the OIDC Back-Channel Logout + * specification. Keycloak conforms to this OP side of the spec. + * Auth0 could some day. *

*

- * Implementation is made with a security filter-chain intercepting just the - * "/backchannel_logout" route and a controller handling requests to that - * end-point. + * Implementation is made with a security filter-chain intercepting just the "/backchannel_logout" route and a controller handling requests to that end-point. *

*

- * This beans are defined only if - * "com.c4-soft.springaddons.security.client.back-channel-logout-enabled" - * property is true. + * This beans are defined only if "com.c4-soft.springaddons.security.client.back-channel-logout-enabled" property is true. *

* - * * @author Jerome Wacongne ch4mp@c4-soft.com - * */ @ConditionalOnProperty("com.c4-soft.springaddons.security.client.back-channel-logout-enabled") @AutoConfiguration @Import({ SpringAddonsOAuth2ClientProperties.class }) public class SpringAddonsBackChannelLogoutBeans { - /** - * Requests from the OP are anonymous, are not part of a session, and have no - * CSRF token. It contains a logout JWT which serves both to authenticate the - * request and protect against CSRF. - * - * @param http - * @param serverProperties Spring Boot server properties - * @return a security filter-chain dedicated to back-channel logout handling - * @throws Exception - */ - @Order(Ordered.HIGHEST_PRECEDENCE) - @Bean - SecurityFilterChain springAddonsBackChannelLogoutClientFilterChain( - HttpSecurity http, - ServerProperties serverProperties) - throws Exception { - http.securityMatcher(new AntPathRequestMatcher("/backchannel_logout")); - http.authorizeHttpRequests().anyRequest().permitAll(); - if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { - http.requiresChannel().anyRequest().requiresSecure(); - } - http.cors().disable(); - http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); - http.csrf().disable(); - return http.build(); - } + /** + * Requests from the OP are anonymous, are not part of a session, and have no CSRF token. It contains a logout JWT which serves both to authenticate the + * request and protect against CSRF. + * + * @param http + * @param serverProperties Spring Boot server properties + * @return a security filter-chain dedicated to back-channel logout handling + * @throws Exception + */ + @Order(Ordered.HIGHEST_PRECEDENCE) + @Bean + SecurityFilterChain springAddonsBackChannelLogoutClientFilterChain(HttpSecurity http, ServerProperties serverProperties) throws Exception { + http.securityMatcher(new AntPathRequestMatcher("/backchannel_logout")); + http.authorizeHttpRequests().anyRequest().permitAll(); + if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { + http.requiresChannel().anyRequest().requiresSecure(); + } + http.cors().disable(); + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + http.csrf().disable(); + return http.build(); + } - /** - *

- * Handles a POST request containing a JWT logout token provided as - * application/x-www-form-urlencoded as specified in Back-Channel - * Logout specification. - *

- *

- * This end-point will: - *

    - *
  • remove the relevant authorized client (based on issuer URI) for the - * relevant user (based on the subject)
  • - *
  • maybe invalidate user session: only if the removed authorized client was - * the last one the user had
  • - *
- * - * @author Jerome Wacongne ch4mp@c4-soft.com - * - */ - @Component - @RestController - public static class BackChannelLogoutController { - private final SpringAddonsOAuth2AuthorizedClientRepository authorizedClientRepository; - private final Map jwtDecoders; + /** + *

+ * Handles a POST request containing a JWT logout token provided as application/x-www-form-urlencoded as specified in + * Back-Channel Logout specification. + *

+ *

+ * This end-point will: + *

    + *
  • remove the relevant authorized client (based on issuer URI) for the relevant user (based on the subject)
  • + *
  • maybe invalidate user session: only if the removed authorized client was the last one the user had
  • + *
+ * + * @author Jerome Wacongne ch4mp@c4-soft.com + */ + @Component + @RestController + public static class BackChannelLogoutController { + private final SpringAddonsOAuth2AuthorizedClientRepository authorizedClientRepository; + private final Map jwtDecoders; - public BackChannelLogoutController(SpringAddonsOAuth2AuthorizedClientRepository authorizedClientRepository, - InMemoryClientRegistrationRepository registrationRepo) { - this.authorizedClientRepository = authorizedClientRepository; - this.jwtDecoders = StreamSupport.stream(registrationRepo.spliterator(), false) - .filter(reg -> AuthorizationGrantType.AUTHORIZATION_CODE.equals(reg.getAuthorizationGrantType())) - .map(ClientRegistration::getProviderDetails) - .collect(Collectors.toMap(provider -> provider.getIssuerUri(), - provider -> NimbusJwtDecoder.withJwkSetUri(provider.getJwkSetUri()).build())); - } + public BackChannelLogoutController( + SpringAddonsOAuth2AuthorizedClientRepository authorizedClientRepository, + InMemoryClientRegistrationRepository registrationRepo) { + this.authorizedClientRepository = authorizedClientRepository; + this.jwtDecoders = StreamSupport.stream(registrationRepo.spliterator(), false) + .filter(reg -> AuthorizationGrantType.AUTHORIZATION_CODE.equals(reg.getAuthorizationGrantType())) + .map(ClientRegistration::getProviderDetails).collect( + Collectors.toMap(provider -> provider.getIssuerUri(), provider -> NimbusJwtDecoder.withJwkSetUri(provider.getJwkSetUri()).build())); + } - @PostMapping(path = "/backchannel_logout", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public ResponseEntity backChannelLogout(@RequestParam MultiValueMap body) { - final var tokenString = body.get("logout_token"); - if (tokenString == null || tokenString.size() != 1) { - throw new BadLogoutRequestException(); - } - jwtDecoders.forEach((issuer, decoder) -> { - try { - final var jwt = decoder.decode(tokenString.get(0)); - final var isLogoutToken = Optional.ofNullable(jwt.getClaims().get("events")) - .map(Object::toString) - .map(evt -> evt.contains("http://schemas.openid.net/event/backchannel-logout")) - .orElse(false); - if (!isLogoutToken) { - throw new BadLogoutRequestException(); - } - final var logoutIss = Optional.ofNullable(jwt.getIssuer()).map(URL::toString).orElse(null); - if (!Objects.equals(issuer, logoutIss)) { - throw new BadLogoutRequestException(); - } - final var logoutSub = jwt.getSubject(); - final var sessionsToInvalidate = authorizedClientRepository.removeAuthorizedClients(logoutIss, - logoutSub); - sessionsToInvalidate.forEach(s -> { - s.invalidate(); - }); - } catch (JwtException e) { - } - }); - return ResponseEntity.ok().build(); - } + @PostMapping(path = "/backchannel_logout", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity backChannelLogout(@RequestParam MultiValueMap body) { + final var tokenString = body.get("logout_token"); + if (tokenString == null || tokenString.size() != 1) { + throw new BadLogoutRequestException(); + } + jwtDecoders.forEach((issuer, decoder) -> { + try { + final var jwt = decoder.decode(tokenString.get(0)); + final var isLogoutToken = Optional.ofNullable(jwt.getClaims().get("events")).map(Object::toString) + .map(evt -> evt.contains("http://schemas.openid.net/event/backchannel-logout")).orElse(false); + if (!isLogoutToken) { + throw new BadLogoutRequestException(); + } + final var logoutIss = Optional.ofNullable(jwt.getIssuer()).map(URL::toString).orElse(null); + if (!Objects.equals(issuer, logoutIss)) { + throw new BadLogoutRequestException(); + } + final var logoutSub = jwt.getSubject(); + final var sessionsToInvalidate = authorizedClientRepository.removeAuthorizedClients(logoutIss, logoutSub); + sessionsToInvalidate.forEach(s -> { + s.invalidate(); + }); + } catch (JwtException e) { + } + }); + return ResponseEntity.ok().build(); + } - @ResponseStatus(HttpStatus.BAD_REQUEST) - static final class BadLogoutRequestException extends RuntimeException { - } - } + @ResponseStatus(HttpStatus.BAD_REQUEST) + static final class BadLogoutRequestException extends RuntimeException { + private static final long serialVersionUID = -8703279699142477824L; + } + } } diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizationRequestResolver.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizationRequestResolver.java index fcd26ae33..2614c6bdd 100644 --- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizationRequestResolver.java +++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizationRequestResolver.java @@ -14,41 +14,38 @@ import jakarta.servlet.http.HttpServletRequest; /** - * Forces the usage of {@link SpringAddonsOAuth2ClientProperties#getClientUri() - * SpringAddonsOAuth2ClientProperties#client-uri} in post-login redirection URI + * Forces the usage of {@link SpringAddonsOAuth2ClientProperties#getClientUri() SpringAddonsOAuth2ClientProperties#client-uri} in post-login redirection URI * * @author Jerome Wacongne ch4mp@c4-soft.com - * */ public class SpringAddonsOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { - private final OAuth2AuthorizationRequestResolver defaultResolver; - - public SpringAddonsOAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { - defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, - OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI); - } - - @Override - public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { - return toAbsolute(defaultResolver.resolve(request), request); - } - - @Override - public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) { - return toAbsolute(defaultResolver.resolve(request, clientRegistrationId), request); - } - - private OAuth2AuthorizationRequest toAbsolute(OAuth2AuthorizationRequest defaultAuthorizationRequest, - HttpServletRequest request) { - final var clientUriString = request.getRequestURL(); - if (defaultAuthorizationRequest == null || clientUriString == null) { - return defaultAuthorizationRequest; - } - final var clientUri = URI.create(clientUriString.toString()); - final var redirectUri = UriComponentsBuilder.fromUriString(defaultAuthorizationRequest.getRedirectUri()) - .scheme(clientUri.getScheme()).host(clientUri.getHost()) - .port(clientUri.getPort()).build().toUriString(); - return OAuth2AuthorizationRequest.from(defaultAuthorizationRequest).redirectUri(redirectUri).build(); - } + private final OAuth2AuthorizationRequestResolver defaultResolver; + + public SpringAddonsOAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { + defaultResolver = new DefaultOAuth2AuthorizationRequestResolver( + clientRegistrationRepository, + OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI); + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { + return toAbsolute(defaultResolver.resolve(request), request); + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) { + return toAbsolute(defaultResolver.resolve(request, clientRegistrationId), request); + } + + private OAuth2AuthorizationRequest toAbsolute(OAuth2AuthorizationRequest defaultAuthorizationRequest, HttpServletRequest request) { + final var clientUriString = request.getRequestURL(); + if (defaultAuthorizationRequest == null || clientUriString == null) { + return defaultAuthorizationRequest; + } + final var clientUri = URI.create(clientUriString.toString()); + final var redirectUri = UriComponentsBuilder.fromUriString(defaultAuthorizationRequest.getRedirectUri()).scheme(clientUri.getScheme()) + .host(clientUri.getHost()).port(clientUri.getPort()).build().toUriString(); + return OAuth2AuthorizationRequest.from(defaultAuthorizationRequest).redirectUri(redirectUri).build(); + } } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizedClientRepository.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizedClientRepository.java index 79dc76b2a..06e2352d5 100644 --- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizedClientRepository.java +++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizedClientRepository.java @@ -31,234 +31,219 @@ /** *

- * Work around the single tenancy nature of {@link OAuth2AuthenticationToken} - * and {@link InMemoryReactiveClientRegistrationRepository}: if a user - * authenticates sequentially on several OP, his OAuth2AuthenticationToken will - * contain an {@link OAuth2User} corresponding only to the last OP he - * authenticated with. To work around this limitation, this repository keeps an - * OAuth2User for each OP (issuer) and resolves the authorization client with - * the right subject for each issuer. + * Work around the single tenancy nature of {@link OAuth2AuthenticationToken} and {@link InMemoryReactiveClientRegistrationRepository}: if a user authenticates + * sequentially on several OP, his OAuth2AuthenticationToken will contain an {@link OAuth2User} corresponding only to the last OP he authenticated with. To work + * around this limitation, this repository keeps an OAuth2User for each OP (issuer) and resolves the authorization client with the right subject for each + * issuer. *

*

- * This repo is also a session listener to keep track of all the (issuer, - * subject) pairs and their associations with sessions (many to many relation). - * This enables it to expose the required API for back-channel logout where a - * request is received to remove an authorized client based on its issuer and - * subject but without a session token. + * This repo is also a session listener to keep track of all the (issuer, subject) pairs and their associations with sessions (many to many relation). This + * enables it to expose the required API for back-channel logout where a request is received to remove an authorized client based on its issuer and subject but + * without a session token. *

* * @author Jerome Wacongne ch4mp@c4-soft.com - * */ @RequiredArgsConstructor -public class SpringAddonsOAuth2AuthorizedClientRepository - implements OAuth2AuthorizedClientRepository, HttpSessionListener, HttpSessionIdListener { - private static final String OAUTH2_USERS_KEY = "com.c4-soft.spring-addons.OAuth2.client.oauth2-users"; - private static final String AUTHORIZED_CLIENTS_KEY = "com.c4-soft.spring-addons.OAuth2.client.authorized-clients"; - - private static final Map> sessionsByuserId = new ConcurrentHashMap<>(); - private static final Map> userIdsBySessionId = new ConcurrentHashMap<>(); - - private final ClientRegistrationRepository clientRegistrationRepository; - - @Override - public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) { - if (userIdsBySessionId.containsKey(oldSessionId) && !Objects.equals(event.getSession().getId(), oldSessionId)) { - userIdsBySessionId.put(event.getSession().getId(), userIdsBySessionId.get(oldSessionId)); - userIdsBySessionId.remove(oldSessionId); - } - } - - @Override - public void sessionCreated(HttpSessionEvent se) { - } - - @Override - public void sessionDestroyed(HttpSessionEvent se) { - final var idsToUpdate = getUserIds(se.getSession().getId()); - for (var id : idsToUpdate) { - setSessions(id.iss(), id.sub(), new HashSet<>(getSessions(id.iss(), id.sub()).stream() - .filter(s -> !(s.getId().equals(se.getSession().getId()))).collect(Collectors.toSet()))); - } - userIdsBySessionId.remove(se.getSession().getId()); - } - - private Optional getUserSubject(HttpSession session, String issuer) { - final var oauth2Users = getOAuth2Users(session); - return Optional.ofNullable(oauth2Users.get(issuer)).map(u -> u.getAttribute(JWTClaimNames.SUBJECT)); - } - - @SuppressWarnings("unchecked") - @Override - public T loadAuthorizedClient(String clientRegistrationId, - Authentication auth, HttpServletRequest request) { - final var issuer = clientRegistrationRepository.findByRegistrationId(clientRegistrationId).getProviderDetails() - .getIssuerUri(); - final var subject = getUserSubject(request.getSession(), issuer).orElse(auth.getName()); - - return (T) loadAuthorizedClient(request.getSession(), issuer, subject); - } - - public OAuth2AuthorizedClient loadAuthorizedClient(HttpSession session, String issuer, String subject) { - final var authorizedClients = getAuthorizedClients(session); - return authorizedClients.stream() - .filter(ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) - && Objects.equals(ac.getPrincipalName(), subject)) - .findAny().orElse(null); - } - - @Override - public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication auth, - HttpServletRequest request, HttpServletResponse response) { - if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) { - saveAuthorizedClient(request.getSession(), authorizedClient, (OAuth2User) auth.getPrincipal()); - } - } - - private void saveAuthorizedClient(HttpSession session, OAuth2AuthorizedClient authorizedClient, OAuth2User user) { - final var issuer = authorizedClient.getClientRegistration().getProviderDetails().getIssuerUri(); - final var subject = user.getAttributes().get(JWTClaimNames.SUBJECT).toString(); - - final var oauth2Users = getOAuth2Users(session); - if (oauth2Users.containsKey(issuer)) { - removeAuthorizedClient(session, issuer, oauth2Users.get(issuer).getName()); - } - oauth2Users.put(issuer, user); - setOAuth2Users(session, oauth2Users); - - final var authorizedClients = getAuthorizedClients(session); - authorizedClients.add(authorizedClient); - setAuthorizedClients(session, authorizedClients); - - final var sessions = getSessions(issuer, subject); - if (!sessions.contains(session)) { - sessions.add(session); - setSessions(issuer, subject, sessions); - } - - final var userIds = getUserIds(session.getId()); - userIds.add(new UserId(issuer, subject)); - setUserIds(session.getId(), userIds); - } - - @Override - public void removeAuthorizedClient(String clientRegistrationId, Authentication auth, HttpServletRequest request, - HttpServletResponse response) { - if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) { - final var issuer = clientRegistrationRepository.findByRegistrationId(clientRegistrationId) - .getProviderDetails() - .getIssuerUri(); - final var subject = getUserSubject(request.getSession(), issuer).orElse(auth.getName()); - - removeAuthorizedClient(request.getSession(), issuer, subject); - } - } - - public void removeAuthorizedClient(HttpSession session, String issuer, String subject) { - final var allAuthorizedClients = getAuthorizedClients(session); - final var authorizedClientsToRemove = allAuthorizedClients.stream() - .filter(ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) - && Objects.equals(ac.getPrincipalName(), subject)) - .collect(Collectors.toSet()); - allAuthorizedClients.removeAll(authorizedClientsToRemove); - setAuthorizedClients(session, allAuthorizedClients); - - final var oauth2Users = getOAuth2Users(session); - if (oauth2Users.containsKey(issuer)) { - oauth2Users.remove(issuer); - setOAuth2Users(session, oauth2Users); - } - - final var sessions = getSessions(issuer, subject); - if (sessions.contains(session)) { - sessions.remove(session); - setSessions(issuer, subject, sessions); - } - - final var userIds = getUserIds(session.getId()); - userIds.remove(new UserId(issuer, subject)); - } - - /** - * Removes an authorized client and returns a list of sessions to invalidate - * (those for which the current user has no more authorized client after this - * one was removed) - * - * @param issuer OP issuer URI - * @param subject current user subject for this OP - * @return the list of user sessions for which this authorized client was the - * last one - */ - public Collection removeAuthorizedClients(String issuer, String subject) { - final var sessions = getSessions(issuer, subject); - - final var sessionsToInvalidate = sessions.stream().filter(s -> { - return getAuthorizedClients(s).stream() - .filter(authorizedClient -> authorizedClient.getClientRegistration().getProviderDetails() - .getIssuerUri().equals(issuer) - && authorizedClient.getPrincipalName().equals(subject)) - .count() < 1; - }).toList(); - - for (var session : sessions) { - removeAuthorizedClient(session, issuer, subject); - } - - return sessionsToInvalidate; - } - - @SuppressWarnings("unchecked") - private Set getAuthorizedClients(HttpSession session) { - final var sessionAuthorizedClients = (Set) session.getAttribute(AUTHORIZED_CLIENTS_KEY); - return sessionAuthorizedClients == null ? new HashSet<>() : sessionAuthorizedClients; - } - - private void setAuthorizedClients(HttpSession session, Set sessionAuthorizedClients) { - session.setAttribute(AUTHORIZED_CLIENTS_KEY, sessionAuthorizedClients); - } - - public Map getOAuth2UsersBySession(HttpSession session) { - if (session == null) { - return null; - } - return Collections.unmodifiableMap(getOAuth2Users(session)); - } - - @SuppressWarnings("unchecked") - private Map getOAuth2Users(HttpSession s) { - final var sessionOauth2UsersByIssuer = (Map) s.getAttribute(OAUTH2_USERS_KEY); - return sessionOauth2UsersByIssuer == null ? new ConcurrentHashMap() - : sessionOauth2UsersByIssuer; - } - - private void setOAuth2Users(HttpSession s, Map sessionOauth2UsersByIssuer) { - s.setAttribute(OAUTH2_USERS_KEY, sessionOauth2UsersByIssuer); - } - - private Set getSessions(String issuer, String subject) { - return sessionsByuserId.getOrDefault(new UserId(issuer, subject), new HashSet<>()); - } - - private void setSessions(String issuer, String subject, Set sessions) { - if (sessions == null || sessions.isEmpty()) { - sessionsByuserId.remove(new UserId(issuer, subject)); - } else { - sessionsByuserId.put(new UserId(issuer, subject), sessions); - } - } - - private Set getUserIds(String sessionId) { - return userIdsBySessionId.getOrDefault(sessionId, new HashSet<>()); - } - - private void setUserIds(String sessionId, Set userIds) { - if (userIds == null || userIds.isEmpty()) { - userIdsBySessionId.remove(sessionId); - } else { - userIdsBySessionId.put(sessionId, userIds); - } - } - - private static record UserId(String iss, String sub) { - } +public class SpringAddonsOAuth2AuthorizedClientRepository implements OAuth2AuthorizedClientRepository, HttpSessionListener, HttpSessionIdListener { + private static final String OAUTH2_USERS_KEY = "com.c4-soft.spring-addons.OAuth2.client.oauth2-users"; + private static final String AUTHORIZED_CLIENTS_KEY = "com.c4-soft.spring-addons.OAuth2.client.authorized-clients"; + + private static final Map> sessionsByuserId = new ConcurrentHashMap<>(); + private static final Map> userIdsBySessionId = new ConcurrentHashMap<>(); + + private final ClientRegistrationRepository clientRegistrationRepository; + + @Override + public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) { + if (userIdsBySessionId.containsKey(oldSessionId) && !Objects.equals(event.getSession().getId(), oldSessionId)) { + userIdsBySessionId.put(event.getSession().getId(), userIdsBySessionId.get(oldSessionId)); + userIdsBySessionId.remove(oldSessionId); + } + } + + @Override + public void sessionCreated(HttpSessionEvent se) { + } + + @Override + public void sessionDestroyed(HttpSessionEvent se) { + final var idsToUpdate = getUserIds(se.getSession().getId()); + for (var id : idsToUpdate) { + setSessions( + id.iss(), + id.sub(), + new HashSet<>( + getSessions(id.iss(), id.sub()).stream().filter(s -> !(s.getId().equals(se.getSession().getId()))).collect(Collectors.toSet()))); + } + userIdsBySessionId.remove(se.getSession().getId()); + } + + private Optional getUserSubject(HttpSession session, String issuer) { + final var oauth2Users = getOAuth2Users(session); + return Optional.ofNullable(oauth2Users.get(issuer)).map(u -> u.getAttribute(JWTClaimNames.SUBJECT)); + } + + @SuppressWarnings("unchecked") + @Override + public T loadAuthorizedClient(String clientRegistrationId, Authentication auth, HttpServletRequest request) { + final var issuer = clientRegistrationRepository.findByRegistrationId(clientRegistrationId).getProviderDetails().getIssuerUri(); + final var subject = getUserSubject(request.getSession(), issuer).orElse(auth.getName()); + + return (T) loadAuthorizedClient(request.getSession(), issuer, subject); + } + + public OAuth2AuthorizedClient loadAuthorizedClient(HttpSession session, String issuer, String subject) { + final var authorizedClients = getAuthorizedClients(session); + return authorizedClients.stream().filter( + ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) && Objects.equals(ac.getPrincipalName(), subject)) + .findAny().orElse(null); + } + + @Override + public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication auth, HttpServletRequest request, HttpServletResponse response) { + if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) { + saveAuthorizedClient(request.getSession(), authorizedClient, (OAuth2User) auth.getPrincipal()); + } + } + + private void saveAuthorizedClient(HttpSession session, OAuth2AuthorizedClient authorizedClient, OAuth2User user) { + final var issuer = authorizedClient.getClientRegistration().getProviderDetails().getIssuerUri(); + final var subject = user.getAttributes().get(JWTClaimNames.SUBJECT).toString(); + + final var oauth2Users = getOAuth2Users(session); + if (oauth2Users.containsKey(issuer)) { + removeAuthorizedClient(session, issuer, oauth2Users.get(issuer).getName()); + } + oauth2Users.put(issuer, user); + setOAuth2Users(session, oauth2Users); + + final var authorizedClients = getAuthorizedClients(session); + authorizedClients.add(authorizedClient); + setAuthorizedClients(session, authorizedClients); + + final var sessions = getSessions(issuer, subject); + if (!sessions.contains(session)) { + sessions.add(session); + setSessions(issuer, subject, sessions); + } + + final var userIds = getUserIds(session.getId()); + userIds.add(new UserId(issuer, subject)); + setUserIds(session.getId(), userIds); + } + + @Override + public void removeAuthorizedClient(String clientRegistrationId, Authentication auth, HttpServletRequest request, HttpServletResponse response) { + if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) { + final var issuer = clientRegistrationRepository.findByRegistrationId(clientRegistrationId).getProviderDetails().getIssuerUri(); + final var subject = getUserSubject(request.getSession(), issuer).orElse(auth.getName()); + + removeAuthorizedClient(request.getSession(), issuer, subject); + } + } + + public void removeAuthorizedClient(HttpSession session, String issuer, String subject) { + final var allAuthorizedClients = getAuthorizedClients(session); + final var authorizedClientsToRemove = allAuthorizedClients.stream().filter( + ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) && Objects.equals(ac.getPrincipalName(), subject)) + .collect(Collectors.toSet()); + allAuthorizedClients.removeAll(authorizedClientsToRemove); + setAuthorizedClients(session, allAuthorizedClients); + + final var oauth2Users = getOAuth2Users(session); + if (oauth2Users.containsKey(issuer)) { + oauth2Users.remove(issuer); + setOAuth2Users(session, oauth2Users); + } + + final var sessions = getSessions(issuer, subject); + if (sessions.contains(session)) { + sessions.remove(session); + setSessions(issuer, subject, sessions); + } + + final var userIds = getUserIds(session.getId()); + userIds.remove(new UserId(issuer, subject)); + } + + /** + * Removes an authorized client and returns a list of sessions to invalidate (those for which the current user has no more authorized client after this one + * was removed) + * + * @param issuer OP issuer URI + * @param subject current user subject for this OP + * @return the list of user sessions for which this authorized client was the last one + */ + public Collection removeAuthorizedClients(String issuer, String subject) { + final var sessions = getSessions(issuer, subject); + + final var sessionsToInvalidate = sessions.stream().filter(s -> { + return getAuthorizedClients(s).stream() + .filter( + authorizedClient -> authorizedClient.getClientRegistration().getProviderDetails().getIssuerUri().equals(issuer) + && authorizedClient.getPrincipalName().equals(subject)) + .count() < 1; + }).toList(); + + for (var session : sessions) { + removeAuthorizedClient(session, issuer, subject); + } + + return sessionsToInvalidate; + } + + @SuppressWarnings("unchecked") + private Set getAuthorizedClients(HttpSession session) { + final var sessionAuthorizedClients = (Set) session.getAttribute(AUTHORIZED_CLIENTS_KEY); + return sessionAuthorizedClients == null ? new HashSet<>() : sessionAuthorizedClients; + } + + private void setAuthorizedClients(HttpSession session, Set sessionAuthorizedClients) { + session.setAttribute(AUTHORIZED_CLIENTS_KEY, sessionAuthorizedClients); + } + + public Map getOAuth2UsersBySession(HttpSession session) { + if (session == null) { + return null; + } + return Collections.unmodifiableMap(getOAuth2Users(session)); + } + + @SuppressWarnings("unchecked") + private Map getOAuth2Users(HttpSession s) { + final var sessionOauth2UsersByIssuer = (Map) s.getAttribute(OAUTH2_USERS_KEY); + return sessionOauth2UsersByIssuer == null ? new ConcurrentHashMap() : sessionOauth2UsersByIssuer; + } + + private void setOAuth2Users(HttpSession s, Map sessionOauth2UsersByIssuer) { + s.setAttribute(OAUTH2_USERS_KEY, sessionOauth2UsersByIssuer); + } + + private Set getSessions(String issuer, String subject) { + return sessionsByuserId.getOrDefault(new UserId(issuer, subject), new HashSet<>()); + } + + private void setSessions(String issuer, String subject, Set sessions) { + if (sessions == null || sessions.isEmpty()) { + sessionsByuserId.remove(new UserId(issuer, subject)); + } else { + sessionsByuserId.put(new UserId(issuer, subject), sessions); + } + } + + private Set getUserIds(String sessionId) { + return userIdsBySessionId.getOrDefault(sessionId, new HashSet<>()); + } + + private void setUserIds(String sessionId, Set userIds) { + if (userIds == null || userIds.isEmpty()) { + userIdsBySessionId.remove(sessionId); + } else { + userIdsBySessionId.put(sessionId, userIds); + } + } + + private static record UserId(String iss, String sub) { + } } diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2ClientBeans.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2ClientBeans.java index 2a63c7336..7fe0ab3f1 100644 --- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2ClientBeans.java +++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2ClientBeans.java @@ -44,47 +44,28 @@ import lombok.extern.slf4j.Slf4j; /** - * The following {@link ConditionalOnMissingBean @ConditionalOnMissingBeans} - * are auto-configured + * The following {@link ConditionalOnMissingBean @ConditionalOnMissingBeans} are auto-configured *
    - *
  • springAddonsClientFilterChain: a {@link SecurityFilterChain}. - * Instantiated only if - * "com.c4-soft.springaddons.security.client.security-matchers" property has at - * least one entry. If defined, it is with highest precedence, to ensure that - * all routes defined in this security matcher property are intercepted by this - * filter-chain.
  • - *
  • oAuth2AuthorizationRequestResolver: a - * {@link OAuth2AuthorizationRequestResolver}. Default instance is a - * {@link SpringAddonsOAuth2AuthorizationRequestResolver} which sets the client - * hostname in the redirect URI with - * {@link SpringAddonsOAuth2ClientProperties#getClientUri() - * SpringAddonsOAuth2ClientProperties#client-uri}
  • - *
  • logoutRequestUriBuilder: builder for RP-Initiated - * Logout queries, taking configuration from properties for OIDC providers - * which do not strictly comply with the spec: logout URI not provided by OIDC - * conf or non standard parameter names (Auth0 and Cognito are samples of such - * OPs)
  • - *
  • logoutSuccessHandler: a {@link LogoutSuccessHandler}. Default - * instance is a {@link SpringAddonsOAuth2LogoutSuccessHandler} which logs a - * user out from the last authorization server he logged on.
  • - *
  • authoritiesConverter: an {@link OAuth2AuthoritiesConverter}. Default - * instance is a {@link ConfigurableClaimSet2AuthoritiesConverter} which reads + *
  • springAddonsClientFilterChain: a {@link SecurityFilterChain}. Instantiated only if "com.c4-soft.springaddons.security.client.security-matchers" property + * has at least one entry. If defined, it is with highest precedence, to ensure that all routes defined in this security matcher property are intercepted by + * this filter-chain.
  • + *
  • oAuth2AuthorizationRequestResolver: a {@link OAuth2AuthorizationRequestResolver}. Default instance is a + * {@link SpringAddonsOAuth2AuthorizationRequestResolver} which sets the client hostname in the redirect URI with + * {@link SpringAddonsOAuth2ClientProperties#getClientUri() SpringAddonsOAuth2ClientProperties#client-uri}
  • + *
  • logoutRequestUriBuilder: builder for RP-Initiated Logout queries, taking + * configuration from properties for OIDC providers which do not strictly comply with the spec: logout URI not provided by OIDC conf or non standard parameter + * names (Auth0 and Cognito are samples of such OPs)
  • + *
  • logoutSuccessHandler: a {@link LogoutSuccessHandler}. Default instance is a {@link SpringAddonsOAuth2LogoutSuccessHandler} which logs a user out from the + * last authorization server he logged on.
  • + *
  • authoritiesConverter: an {@link OAuth2AuthoritiesConverter}. Default instance is a {@link ConfigurableClaimSet2AuthoritiesConverter} which reads * spring-addons {@link SpringAddonsSecurityProperties}
  • - *
  • grantedAuthoritiesMapper: a {@link GrantedAuthoritiesMapper} using the - * already configured {@link OAuth2AuthoritiesConverter}
  • - *
  • oAuth2AuthorizedClientRepository: a - * {@link SpringAddonsOAuth2AuthorizedClientRepository} (which is also a session - * listener) capable of handling multi-tenancy and back-channel logout.
  • - *
  • clientAuthorizePostProcessor: a - * {@link ClientExpressionInterceptUrlRegistryPostProcessor} post processor to - * fine tune access control from java configuration. It applies to all routes - * not listed in "permit-all" property configuration. Default requires users to - * be authenticated.
  • - *
  • clientHttpPostProcessor: a - * {@link ClientHttpSecurityPostProcessor} to override anything from above - * auto-configuration. It is called just before the security filter-chain is - * returned. Default is a no-op.
  • + *
  • grantedAuthoritiesMapper: a {@link GrantedAuthoritiesMapper} using the already configured {@link OAuth2AuthoritiesConverter}
  • + *
  • oAuth2AuthorizedClientRepository: a {@link SpringAddonsOAuth2AuthorizedClientRepository} (which is also a session listener) capable of handling + * multi-tenancy and back-channel logout.
  • + *
  • clientAuthorizePostProcessor: a {@link ClientExpressionInterceptUrlRegistryPostProcessor} post processor to fine tune access control from java + * configuration. It applies to all routes not listed in "permit-all" property configuration. Default requires users to be authenticated.
  • + *
  • clientHttpPostProcessor: a {@link ClientHttpSecurityPostProcessor} to override anything from above auto-configuration. It is called just before the + * security filter-chain is returned. Default is a no-op.
  • *
* * @author Jerome Wacongne ch4mp@c4-soft.com @@ -96,69 +77,48 @@ @Slf4j public class SpringAddonsOAuth2ClientBeans { - /** - *

- * Instantiated only if - * "com.c4-soft.springaddons.security.client.security-matchers" property has at - * least one entry. If defined, it is with highest precedence, to ensure that - * all routes defined in this security matcher property are intercepted by this - * filter-chain. - *

- * It defines: - *
    - *
  • If the path to login page was provided in conf, a @Controller must be - * provided to handle it. Otherwise Spring Boot default generated one is used - * (be aware that it does not work when bound to 80 or 8080 with SSL - * enabled, so, in that case, use another port or define a login path and a - * controller to handle it)
  • - *
  • logout (using {@link SpringAddonsOAuth2LogoutSuccessHandler} by - * default)
  • - *
  • forces SSL usage if it is enabled
  • - * properties - *
  • CSRF protection as defined in spring-addons client properties - * (enabled by default in this filter-chain).
  • - *
  • allow access to unauthorized requests to path matchers listed in - * spring-security client "permit-all" property
  • - *
  • as usual, apply {@link ClientExpressionInterceptUrlRegistryPostProcessor} - * for access control configuration from Java conf and - * {@link ClientHttpSecurityPostProcessor} to override anything from the - * auto-configuration listed above
  • - *
- * - * @param http the security filter-chain builder to - * configure - * @param serverProperties Spring Boot standard server properties - * @param authorizationRequestResolver the authorization request resolver to - * use. By default - * {@link SpringAddonsOAuth2AuthorizationRequestResolver} - * @param clientProps {@link SpringAddonsOAuth2ClientProperties - * spring-addons client properties} - * @param authorizePostProcessor post process authorization after - * "permit-all" configuration was applied - * (default is "isAuthenticated()" to - * everything that was not matched) - * @param httpPostProcessor post process the "http" builder just - * before it is returned (enables to - * override anything from the - * auto-configuration) - * spring-addons client properties} - * @return a security filter-chain scoped to specified security-matchers and - * adapted to OAuth2 clients - * @throws Exception in case of miss-configuration - */ - @ConditionalOnExpression("!(T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers:}') && T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers[0]:}'))") - @Order(Ordered.HIGHEST_PRECEDENCE + 1) - @Bean - SecurityFilterChain springAddonsClientFilterChain( - HttpSecurity http, - ServerProperties serverProperties, - OAuth2AuthorizationRequestResolver authorizationRequestResolver, - LogoutSuccessHandler logoutSuccessHandler, - SpringAddonsOAuth2ClientProperties clientProps, - ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, - ClientHttpSecurityPostProcessor httpPostProcessor) - throws Exception { - // @formatter:off + /** + *

+ * Instantiated only if "com.c4-soft.springaddons.security.client.security-matchers" property has at least one entry. If defined, it is with highest + * precedence, to ensure that all routes defined in this security matcher property are intercepted by this filter-chain. + *

+ * It defines: + *
    + *
  • If the path to login page was provided in conf, a @Controller must be provided to handle it. Otherwise Spring Boot default generated one is used + * (be aware that it does not work when bound to 80 or 8080 with SSL enabled, so, in that case, use another port or define a login path and a controller to + * handle it)
  • + *
  • logout (using {@link SpringAddonsOAuth2LogoutSuccessHandler} by default)
  • + *
  • forces SSL usage if it is enabled
  • properties + *
  • CSRF protection as defined in spring-addons client properties (enabled by default in this filter-chain).
  • + *
  • allow access to unauthorized requests to path matchers listed in spring-security client "permit-all" property
  • + *
  • as usual, apply {@link ClientExpressionInterceptUrlRegistryPostProcessor} for access control configuration from Java conf and + * {@link ClientHttpSecurityPostProcessor} to override anything from the auto-configuration listed above
  • + *
+ * + * @param http the security filter-chain builder to configure + * @param serverProperties Spring Boot standard server properties + * @param authorizationRequestResolver the authorization request resolver to use. By default {@link SpringAddonsOAuth2AuthorizationRequestResolver} + * @param clientProps {@link SpringAddonsOAuth2ClientProperties spring-addons client properties} + * @param authorizePostProcessor post process authorization after "permit-all" configuration was applied (default is "isAuthenticated()" to + * everything that was not matched) + * @param httpPostProcessor post process the "http" builder just before it is returned (enables to override anything from the + * auto-configuration) spring-addons client properties} + * @return a security filter-chain scoped to specified security-matchers and adapted to OAuth2 clients + * @throws Exception in case of miss-configuration + */ + @ConditionalOnExpression("!(T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers:}') && T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers[0]:}'))") + @Order(Ordered.HIGHEST_PRECEDENCE + 1) + @Bean + SecurityFilterChain springAddonsClientFilterChain( + HttpSecurity http, + ServerProperties serverProperties, + OAuth2AuthorizationRequestResolver authorizationRequestResolver, + LogoutSuccessHandler logoutSuccessHandler, + SpringAddonsOAuth2ClientProperties clientProps, + ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, + ClientHttpSecurityPostProcessor httpPostProcessor) + throws Exception { + // @formatter:off log.info("Applying client OAuth2 configuration for: {}", (Object[]) clientProps.getSecurityMatchers()); http.securityMatcher(clientProps.getSecurityMatchers()); @@ -177,176 +137,154 @@ SecurityFilterChain springAddonsClientFilterChain( }); // @formatter:on - ServletConfigurationSupport.configureClient(http, serverProperties, clientProps, authorizePostProcessor, - httpPostProcessor); + ServletConfigurationSupport.configureClient(http, serverProperties, clientProps, authorizePostProcessor, httpPostProcessor); - return http.build(); - } + return http.build(); + } - /** - * Use a {@link SpringAddonsOAuth2AuthorizationRequestResolver} which takes - * hostname and port from configuration properties (and works even if SSL is - * enabled) - * - * @param clientRegistrationRepository - * @param clientProps - * @return {@link SpringAddonsOAuth2AuthorizationRequestResolver} - */ - @ConditionalOnMissingBean - @Bean - OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver( - ClientRegistrationRepository clientRegistrationRepository, - SpringAddonsOAuth2ClientProperties clientProps) { - return new SpringAddonsOAuth2AuthorizationRequestResolver(clientRegistrationRepository); - } + /** + * Use a {@link SpringAddonsOAuth2AuthorizationRequestResolver} which takes hostname and port from configuration properties (and works even if SSL is + * enabled) + * + * @param clientRegistrationRepository + * @param clientProps + * @return {@link SpringAddonsOAuth2AuthorizationRequestResolver} + */ + @ConditionalOnMissingBean + @Bean + OAuth2AuthorizationRequestResolver + oAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository, SpringAddonsOAuth2ClientProperties clientProps) { + return new SpringAddonsOAuth2AuthorizationRequestResolver(clientRegistrationRepository); + } - /** - * Build logout request for RP-Initiated - * Logout. It works with most OIDC provider: those complying with the spec - * (Keycloak for instance), off course, but also those which are close enough to - * it (Auth0, Cognito, ...) - * - * @param clientProps {@link SpringAddonsOAuth2ClientProperties} to pick logout - * configuration for divergence to the standard (logout URI - * not provided in .well-known/openid-configuration and - * non-conform parameter names) - * @return {@link SpringAddonsOAuth2LogoutRequestUriBuilder] - */ - @ConditionalOnMissingBean - @Bean - LogoutRequestUriBuilder logoutRequestUriBuilder( - SpringAddonsOAuth2ClientProperties clientProps) { - return new SpringAddonsOAuth2LogoutRequestUriBuilder(clientProps); - } + /** + * Build logout request for RP-Initiated Logout. It works with most OIDC + * provider: those complying with the spec (Keycloak for instance), off course, but also those which are close enough to it (Auth0, Cognito, ...) + * + * @param clientProps {@link SpringAddonsOAuth2ClientProperties} to pick logout configuration for divergence to the standard (logout URI not provided in + * .well-known/openid-configuration and non-conform parameter names) + * @return {@link SpringAddonsOAuth2LogoutRequestUriBuilder] + */ + @ConditionalOnMissingBean + @Bean + LogoutRequestUriBuilder logoutRequestUriBuilder(SpringAddonsOAuth2ClientProperties clientProps) { + return new SpringAddonsOAuth2LogoutRequestUriBuilder(clientProps); + } - /** - * Single tenant logout handler for OIDC provider complying to RP-Initiated - * Logout (or approximately complying to it like Auth0 or Cognito) - * - * @param logoutRequestUriBuilder delegate doing the smart job - * @param clientRegistrationRepository - * @return {@link SpringAddonsOAuth2LogoutSuccessHandler} - */ - @ConditionalOnMissingBean - @Bean - LogoutSuccessHandler logoutSuccessHandler(LogoutRequestUriBuilder logoutRequestUriBuilder, - ClientRegistrationRepository clientRegistrationRepository) { - return new SpringAddonsOAuth2LogoutSuccessHandler(logoutRequestUriBuilder, clientRegistrationRepository); - } + /** + * Single tenant logout handler for OIDC provider complying to RP-Initiated + * Logout (or approximately complying to it like Auth0 or Cognito) + * + * @param logoutRequestUriBuilder delegate doing the smart job + * @param clientRegistrationRepository + * @return {@link SpringAddonsOAuth2LogoutSuccessHandler} + */ + @ConditionalOnMissingBean + @Bean + LogoutSuccessHandler logoutSuccessHandler(LogoutRequestUriBuilder logoutRequestUriBuilder, ClientRegistrationRepository clientRegistrationRepository) { + return new SpringAddonsOAuth2LogoutSuccessHandler(logoutRequestUriBuilder, clientRegistrationRepository); + } - /** - * Instantiate a {@link ConfigurableClaimSet2AuthoritiesConverter} from token - * claims to spring authorities (which claims to pick, how to transform roles - * strings for each claim). - * - * @param addonsProperties converter configuration source - * @return {@link ConfigurableClaimSet2AuthoritiesConverter} - */ - @ConditionalOnMissingBean - @Bean - OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { - return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); - } + /** + * Instantiate a {@link ConfigurableClaimSet2AuthoritiesConverter} from token claims to spring authorities (which claims to pick, how to transform roles + * strings for each claim). + * + * @param addonsProperties converter configuration source + * @return {@link ConfigurableClaimSet2AuthoritiesConverter} + */ + @ConditionalOnMissingBean + @Bean + OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { + return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); + } - /** - * - * @param authoritiesConverter the authorities converter to use (by default - * {@link ConfigurableClaimSet2AuthoritiesConverter}) - * @return {@link GrantedAuthoritiesMapper} using the authorities converter in - * the context - */ - @ConditionalOnMissingBean - @Bean - GrantedAuthoritiesMapper grantedAuthoritiesMapper( - Converter, Collection> authoritiesConverter) { - return (authorities) -> { - Set mappedAuthorities = new HashSet<>(); + /** + * @param authoritiesConverter the authorities converter to use (by default {@link ConfigurableClaimSet2AuthoritiesConverter}) + * @return {@link GrantedAuthoritiesMapper} using the authorities converter in the context + */ + @ConditionalOnMissingBean + @Bean + GrantedAuthoritiesMapper grantedAuthoritiesMapper(Converter, Collection> authoritiesConverter) { + return (authorities) -> { + Set mappedAuthorities = new HashSet<>(); - authorities.forEach(authority -> { - if (authority instanceof OidcUserAuthority oidcAuth) { - mappedAuthorities.addAll(authoritiesConverter.convert(oidcAuth.getIdToken().getClaims())); + authorities.forEach(authority -> { + if (authority instanceof OidcUserAuthority oidcAuth) { + mappedAuthorities.addAll(authoritiesConverter.convert(oidcAuth.getIdToken().getClaims())); - } else if (authority instanceof OAuth2UserAuthority oauth2Auth) { - mappedAuthorities.addAll(authoritiesConverter.convert(oauth2Auth.getAttributes())); + } else if (authority instanceof OAuth2UserAuthority oauth2Auth) { + mappedAuthorities.addAll(authoritiesConverter.convert(oauth2Auth.getAttributes())); - } - }); + } + }); - return mappedAuthorities; - }; - } + return mappedAuthorities; + }; + } - /** - * - * @param corsProperties the properties to pick CORS configuration from - * @return a CORS configuration built from properties - */ - CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) { - log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList()); - final var source = new UrlBasedCorsConfigurationSource(); - for (final var corsProps : corsProperties) { - final var configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); - configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); - configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); - configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); - source.registerCorsConfiguration(corsProps.getPath(), configuration); - } - return source; - } + /** + * @param corsProperties the properties to pick CORS configuration from + * @return a CORS configuration built from properties + */ + CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) { + log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList()); + final var source = new UrlBasedCorsConfigurationSource(); + for (final var corsProps : corsProperties) { + final var configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); + configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); + configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); + configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); + source.registerCorsConfiguration(corsProps.getPath(), configuration); + } + return source; + } - /** - * - * @param clientRegistrationRepository the OIDC providers configuration - * @return {@link SpringAddonsOAuth2AuthorizedClientRepository}, an authorized - * client repository supporting multi-tenancy and exposing the required - * API for back-channel logout - */ - @ConditionalOnMissingBean - @Bean - SpringAddonsOAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository( - ClientRegistrationRepository clientRegistrationRepository) { - return new SpringAddonsOAuth2AuthorizedClientRepository(clientRegistrationRepository); - } + /** + * @param clientRegistrationRepository the OIDC providers configuration + * @return {@link SpringAddonsOAuth2AuthorizedClientRepository}, an authorized client repository supporting multi-tenancy and + * exposing the required API for back-channel logout + */ + @ConditionalOnMissingBean + @Bean + SpringAddonsOAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository(ClientRegistrationRepository clientRegistrationRepository) { + return new SpringAddonsOAuth2AuthorizedClientRepository(clientRegistrationRepository); + } - /** - * @return a Post processor for access control in Java configuration which - * requires users to be authenticated. It is called after "permit-all" - * configuration property was applied. - */ - @ConditionalOnMissingBean - @Bean - ClientExpressionInterceptUrlRegistryPostProcessor clientAuthorizePostProcessor() { - return registry -> registry.anyRequest().authenticated(); - } + /** + * @return a Post processor for access control in Java configuration which requires users to be authenticated. It is called after "permit-all" configuration + * property was applied. + */ + @ConditionalOnMissingBean + @Bean + ClientExpressionInterceptUrlRegistryPostProcessor clientAuthorizePostProcessor() { + return registry -> registry.anyRequest().authenticated(); + } - /** - * - * @return a no-op post processor - */ - @ConditionalOnMissingBean - @Bean - ClientHttpSecurityPostProcessor clientHttpPostProcessor() { - return http -> http; - } + /** + * @return a no-op post processor + */ + @ConditionalOnMissingBean + @Bean + ClientHttpSecurityPostProcessor clientHttpPostProcessor() { + return http -> http; + } - static class HasClientSecurityMatcher extends AnyNestedCondition { + static class HasClientSecurityMatcher extends AnyNestedCondition { - public HasClientSecurityMatcher() { - super(ConfigurationPhase.PARSE_CONFIGURATION); - } + public HasClientSecurityMatcher() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } - @ConditionalOnExpression("!(T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers:}') && T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers[0]:}'))") - static class Value1Condition { + @ConditionalOnExpression("!(T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers:}') && T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers[0]:}'))") + static class Value1Condition { - } + } - @ConditionalOnProperty(name = "com.c4-soft.springaddons.security.client.security-matchers[0]") - static class Value2Condition { + @ConditionalOnProperty(name = "com.c4-soft.springaddons.security.client.security-matchers[0]") + static class Value2Condition { - } + } - } + } } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2LogoutSuccessHandler.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2LogoutSuccessHandler.java index db7f87dab..fb41a3a31 100644 --- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2LogoutSuccessHandler.java +++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2LogoutSuccessHandler.java @@ -19,55 +19,41 @@ /** *

- * Provide with - * RP-Initiated - * Logout for authorization-servers fully compliant with OIDC standard as - * well as those "almost" - * implementing the spec. It is (auto)configured with - * {@link SpringAddonsOAuth2ClientProperties}. + * Provide with RP-Initiated Logout for authorization-servers fully compliant with + * OIDC standard as well as those "almost" implementing the spec. It is (auto)configured with {@link SpringAddonsOAuth2ClientProperties}. *

- * *

- * This implementation is not multi-tenant ready. It will terminate the - * user session on this application as well as on a single authorization-server - * (the one which emitted the access-token with which the logout request is - * made). + * This implementation is not multi-tenant ready. It will terminate the user session on this application as well as on a single authorization-server (the + * one which emitted the access-token with which the logout request is made). *

- * *

- * This bean is auto-configured by {@link SpringAddonsOAuth2ClientBeans} as - * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type + * This bean is auto-configured by {@link SpringAddonsOAuth2ClientBeans} as {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type * {@link LogoutSuccessHandler}. Usage: *

* *
  * SecurityFilterChain uiFilterChain(HttpSecurity http, LogoutSuccessHandler logoutSuccessHandler) {
- *     http.logout().logoutSuccessHandler(logoutSuccessHandler);
+ * 	http.logout().logoutSuccessHandler(logoutSuccessHandler);
  * }
  * 
* * @author Jerome Wacongne ch4mp@c4-soft.com - * - * @see SpringAddonsOAuth2LogoutRequestUriBuilder - * @see SpringAddonsOAuth2ClientProperties - * + * @see SpringAddonsOAuth2LogoutRequestUriBuilder + * @see SpringAddonsOAuth2ClientProperties */ @Data @RequiredArgsConstructor @EqualsAndHashCode(callSuper = true) public class SpringAddonsOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { - private final LogoutRequestUriBuilder uriBuilder; - private final ClientRegistrationRepository clientRegistrationRepository; + private final LogoutRequestUriBuilder uriBuilder; + private final ClientRegistrationRepository clientRegistrationRepository; - @Override - protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - if (authentication instanceof OAuth2AuthenticationToken oauth) { - final var clientRegistration = clientRegistrationRepository - .findByRegistrationId(oauth.getAuthorizedClientRegistrationId()); - return uriBuilder.getLogoutRequestUri(clientRegistration, oauth.getName()); - } - return null; - } + @Override + protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + if (authentication instanceof OAuth2AuthenticationToken oauth) { + final var clientRegistration = clientRegistrationRepository.findByRegistrationId(oauth.getAuthorizedClientRegistrationId()); + return uriBuilder.getLogoutRequestUri(clientRegistration, oauth.getName()); + } + return null; + } } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientExpressionInterceptUrlRegistryPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientExpressionInterceptUrlRegistryPostProcessor.java index a56ce29f9..eedb75637 100644 --- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientExpressionInterceptUrlRegistryPostProcessor.java +++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientExpressionInterceptUrlRegistryPostProcessor.java @@ -4,7 +4,6 @@ * Post processor for access control in Java configuration. * * @author Jerome Wacongne ch4mp@c4-soft.com - * */ public interface ClientExpressionInterceptUrlRegistryPostProcessor extends ExpressionInterceptUrlRegistryPostProcessor { } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientHttpSecurityPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientHttpSecurityPostProcessor.java index 01c221d4e..0219f1f4e 100644 --- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientHttpSecurityPostProcessor.java +++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientHttpSecurityPostProcessor.java @@ -1,11 +1,9 @@ package com.c4_soft.springaddons.security.oauth2.config.synchronised; /** - * A post-processor to override anything from spring-addons client security - * filter-chain auto-configuration. + * A post-processor to override anything from spring-addons client security filter-chain auto-configuration. * * @author Jerome Wacongne ch4mp@c4-soft.com - * */ public interface ClientHttpSecurityPostProcessor extends ServerHttpSecurityPostProcessor { } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java index c53ab6086..3beea5559 100644 --- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java +++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java @@ -9,9 +9,8 @@ * Customize access-control for routes which where not listed in {@link SpringAddonsSecurityProperties#permitAll} * * @author ch4mp - * */ public interface ExpressionInterceptUrlRegistryPostProcessor { - AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry authorizeHttpRequests( - AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry); + AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry + authorizeHttpRequests(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry); } diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java index 1529e8d61..e8ee54062 100644 --- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java +++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java @@ -21,100 +21,91 @@ * Support class to statically access current request. *

*

- * It is mainly intended at parsing additional headers when authorizing - * requests. + * It is mainly intended at parsing additional headers when authorizing requests. *

* * @author ch4mp */ public class HttpServletRequestSupport { - /** - * @return the request in current context - */ - public static Optional getRequest() { - RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); - if (requestAttributes instanceof ServletRequestAttributes attr) { - return Optional.ofNullable(attr.getRequest()); - } - return Optional.empty(); - } - - public static Optional getSession() { - return getRequest().flatMap(req -> Optional.ofNullable(req.getSession())); - } - - public static Object getSessionAttribute(String name) { - return getSession().map(session -> session.getAttribute(name)).orElse(null); - } - - public static void setSessionAttribute(String name, Object value) { - getSession().ifPresent(session -> session.setAttribute(name, value)); - } - - /** - * @param headerName name of the header to retrieve - * @return the unique value for the given header in current request - * @throws MissingHeaderException if no non-empty value is found for that - * header - * @throws MultiValuedHeaderException if more than one non-empty value is found - * for that header - */ - public static String getUniqueRequestHeader(String headerName) - throws MissingHeaderException, MultiValuedHeaderException { - final var headers = getNonEmptyRequestHeaderValues(headerName); - if (headers.size() < 1) { - throw new MissingHeaderException(headerName); - } - if (headers.size() > 1) { - throw new MultiValuedHeaderException(headerName); - } - return headers.get(0); - } - - /** - * @param headerName the name of the header to retrieve - * @return a stream of non empty values for a given header from the request in - * current context - */ - public static List getNonEmptyRequestHeaderValues(String headerName) { - return getRequest() - .map( - req -> StreamSupport - .stream(Spliterators.spliteratorUnknownSize(req.getHeaders(headerName).asIterator(), - Spliterator.ORDERED), false) - .filter(StringUtils::hasLength) - .toList()) - .orElse(List.of()); - } - - @ResponseStatus(code = HttpStatus.UNAUTHORIZED) - public static class MissingHeaderException extends RuntimeException { - private static final long serialVersionUID = -4894061353773464761L; - - public MissingHeaderException(String headerName) { - super(headerName + " is missing"); - assert (StringUtils.hasText(headerName)); - } - } - - @ResponseStatus(code = HttpStatus.UNAUTHORIZED) - public static class MultiValuedHeaderException extends RuntimeException { - private static final long serialVersionUID = 1654993007508549674L; - - public MultiValuedHeaderException(String headerName) { - super(headerName + " is not unique"); - assert (StringUtils.hasText(headerName)); - } - } - - @ResponseStatus(code = HttpStatus.UNAUTHORIZED) - public static class InvalidHeaderException extends RuntimeException { - private static final long serialVersionUID = -6233252290377524340L; - - public InvalidHeaderException(String headerName) { - super(headerName + " is not valid"); - assert (StringUtils.hasText(headerName)); - } - } + /** + * @return the request in current context + */ + public static Optional getRequest() { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes instanceof ServletRequestAttributes attr) { + return Optional.ofNullable(attr.getRequest()); + } + return Optional.empty(); + } + + public static Optional getSession() { + return getRequest().flatMap(req -> Optional.ofNullable(req.getSession())); + } + + public static Object getSessionAttribute(String name) { + return getSession().map(session -> session.getAttribute(name)).orElse(null); + } + + public static void setSessionAttribute(String name, Object value) { + getSession().ifPresent(session -> session.setAttribute(name, value)); + } + + /** + * @param headerName name of the header to retrieve + * @return the unique value for the given header in current request + * @throws MissingHeaderException if no non-empty value is found for that header + * @throws MultiValuedHeaderException if more than one non-empty value is found for that header + */ + public static String getUniqueRequestHeader(String headerName) throws MissingHeaderException, MultiValuedHeaderException { + final var headers = getNonEmptyRequestHeaderValues(headerName); + if (headers.size() < 1) { + throw new MissingHeaderException(headerName); + } + if (headers.size() > 1) { + throw new MultiValuedHeaderException(headerName); + } + return headers.get(0); + } + + /** + * @param headerName the name of the header to retrieve + * @return a stream of non empty values for a given header from the request in current context + */ + public static List getNonEmptyRequestHeaderValues(String headerName) { + return getRequest().map( + req -> StreamSupport.stream(Spliterators.spliteratorUnknownSize(req.getHeaders(headerName).asIterator(), Spliterator.ORDERED), false) + .filter(StringUtils::hasLength).toList()) + .orElse(List.of()); + } + + @ResponseStatus(code = HttpStatus.UNAUTHORIZED) + public static class MissingHeaderException extends RuntimeException { + private static final long serialVersionUID = -4894061353773464761L; + + public MissingHeaderException(String headerName) { + super(headerName + " is missing"); + assert (StringUtils.hasText(headerName)); + } + } + + @ResponseStatus(code = HttpStatus.UNAUTHORIZED) + public static class MultiValuedHeaderException extends RuntimeException { + private static final long serialVersionUID = 1654993007508549674L; + + public MultiValuedHeaderException(String headerName) { + super(headerName + " is not unique"); + assert (StringUtils.hasText(headerName)); + } + } + + @ResponseStatus(code = HttpStatus.UNAUTHORIZED) + public static class InvalidHeaderException extends RuntimeException { + private static final long serialVersionUID = -6233252290377524340L; + + public InvalidHeaderException(String headerName) { + super(headerName + " is not valid"); + assert (StringUtils.hasText(headerName)); + } + } } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/OAuth2AuthenticationFactory.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/OAuth2AuthenticationFactory.java index 45265723e..7526645d9 100644 --- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/OAuth2AuthenticationFactory.java +++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/OAuth2AuthenticationFactory.java @@ -5,5 +5,5 @@ import org.springframework.security.authentication.AbstractAuthenticationToken; public interface OAuth2AuthenticationFactory { - AbstractAuthenticationToken build(String bearerString, Map claims); + AbstractAuthenticationToken build(String bearerString, Map claims); } diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerExpressionInterceptUrlRegistryPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerExpressionInterceptUrlRegistryPostProcessor.java index e3b624a01..1636cc57a 100644 --- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerExpressionInterceptUrlRegistryPostProcessor.java +++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerExpressionInterceptUrlRegistryPostProcessor.java @@ -1,5 +1,4 @@ package com.c4_soft.springaddons.security.oauth2.config.synchronised; -public interface ResourceServerExpressionInterceptUrlRegistryPostProcessor - extends ExpressionInterceptUrlRegistryPostProcessor { +public interface ResourceServerExpressionInterceptUrlRegistryPostProcessor extends ExpressionInterceptUrlRegistryPostProcessor { } diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerHttpSecurityPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerHttpSecurityPostProcessor.java index fdd466cce..e2694529e 100644 --- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerHttpSecurityPostProcessor.java +++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerHttpSecurityPostProcessor.java @@ -3,12 +3,10 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; /** - * Process {@link HttpSecurity} of default security filter-chain after it was - * processed by spring-addons. - * This enables to override anything that was auto-configured (or add to it). + * Process {@link HttpSecurity} of default security filter-chain after it was processed by spring-addons. This enables to override anything that was + * auto-configured (or add to it). * * @author ch4mp - * */ public interface ResourceServerHttpSecurityPostProcessor extends ServerHttpSecurityPostProcessor { } diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java index f68165db6..9c5530f72 100644 --- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java +++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java @@ -3,5 +3,5 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; public interface ServerHttpSecurityPostProcessor { - HttpSecurity process(HttpSecurity httpSecurity) throws Exception; + HttpSecurity process(HttpSecurity httpSecurity) throws Exception; } diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java index 0818e6d76..0b2eabdb9 100644 --- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java +++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java @@ -29,140 +29,131 @@ public class ServletConfigurationSupport { - public static HttpSecurity configureResourceServer( - HttpSecurity http, - ServerProperties serverProperties, - SpringAddonsSecurityProperties addonsResourceServerProperties, - ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, - ResourceServerHttpSecurityPostProcessor httpPostProcessor) throws Exception { - - ServletConfigurationSupport.configureCors(http, addonsResourceServerProperties.getCors()); - ServletConfigurationSupport.configureState(http, addonsResourceServerProperties.isStatlessSessions(), - addonsResourceServerProperties.getCsrf()); - ServletConfigurationSupport.configureAccess(http, addonsResourceServerProperties.getPermitAll(), - authorizePostProcessor); - - if (!addonsResourceServerProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) { - http.exceptionHandling(exceptionHandling -> exceptionHandling - .authenticationEntryPoint((request, response, authException) -> { - response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\""); - response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); - })); - } - - if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { - http.requiresChannel(channel -> channel.anyRequest().requiresSecure()); - } - - return httpPostProcessor.process(http); - } - - public static HttpSecurity configureClient( - HttpSecurity http, - ServerProperties serverProperties, - SpringAddonsOAuth2ClientProperties addonsClientProperties, - ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, - ClientHttpSecurityPostProcessor httpPostProcessor) throws Exception { - - ServletConfigurationSupport.configureCors(http, addonsClientProperties.getCors()); - ServletConfigurationSupport.configureState(http, false, addonsClientProperties.getCsrf()); - ServletConfigurationSupport.configureAccess(http, addonsClientProperties.getPermitAll(), - authorizePostProcessor); - - if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { - http.requiresChannel(channel -> channel.anyRequest().requiresSecure()); - } - - return httpPostProcessor.process(http); - } - - public static HttpSecurity configureAccess(HttpSecurity http, String[] permitAll, - ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor) throws Exception { - if (permitAll.length > 0) { - http.anonymous(withDefaults()); - http.authorizeHttpRequests(registry -> authorizePostProcessor - .authorizeHttpRequests(registry.requestMatchers(permitAll).permitAll())); - } else { - http.authorizeHttpRequests(registry -> authorizePostProcessor.authorizeHttpRequests(registry)); - } - return http; - } - - public static HttpSecurity configureCors(HttpSecurity http, CorsProperties[] corsProperties) throws Exception { - if (corsProperties.length == 0) { - http.cors(cors -> cors.disable()); - } else { - final var source = new UrlBasedCorsConfigurationSource(); - for (final var corsProps : corsProperties) { - final var configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); - configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); - configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); - configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); - source.registerCorsConfiguration(corsProps.getPath(), configuration); - } - http.cors(cors -> cors.configurationSource(source)); - } - return http; - } - - public static HttpSecurity configureState( - HttpSecurity http, - boolean isStatless, - SpringAddonsSecurityProperties.Csrf csrfEnum) throws Exception { - - if (isStatless) { - http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - } - - http.csrf(configurer -> { - final var delegate = new XorCsrfTokenRequestAttributeHandler(); - delegate.setCsrfRequestAttributeName("_csrf"); - switch (csrfEnum) { - case DISABLE: - configurer.disable(); - break; - case DEFAULT: - if (isStatless) { - configurer.disable(); - } - break; - case SESSION: - break; - case COOKIE_HTTP_ONLY: - configurer.csrfTokenRepository(new CookieCsrfTokenRepository()) - .csrfTokenRequestHandler(delegate::handle); - http.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); - break; - case COOKIE_ACCESSIBLE_FROM_JS: - // Adapted from - // https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_angularjs_or_another_javascript_framework - configurer.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) - .csrfTokenRequestHandler(delegate::handle); - http.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); - break; - } - }); - - return http; - } - - /** - * https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_a_single_page_application_with_cookiecsrftokenrepository - * - */ - private static final class CsrfCookieFilter extends OncePerRequestFilter { - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) - throws ServletException, IOException { - CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); - // Render the token value to a cookie by causing the deferred token to be loaded - csrfToken.getToken(); - - filterChain.doFilter(request, response); - } - - } + public static HttpSecurity configureResourceServer( + HttpSecurity http, + ServerProperties serverProperties, + SpringAddonsSecurityProperties addonsResourceServerProperties, + ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, + ResourceServerHttpSecurityPostProcessor httpPostProcessor) + throws Exception { + + ServletConfigurationSupport.configureCors(http, addonsResourceServerProperties.getCors()); + ServletConfigurationSupport.configureState(http, addonsResourceServerProperties.isStatlessSessions(), addonsResourceServerProperties.getCsrf()); + ServletConfigurationSupport.configureAccess(http, addonsResourceServerProperties.getPermitAll(), authorizePostProcessor); + + if (!addonsResourceServerProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) { + http.exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint((request, response, authException) -> { + response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\""); + response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); + })); + } + + if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { + http.requiresChannel(channel -> channel.anyRequest().requiresSecure()); + } + + return httpPostProcessor.process(http); + } + + public static HttpSecurity configureClient( + HttpSecurity http, + ServerProperties serverProperties, + SpringAddonsOAuth2ClientProperties addonsClientProperties, + ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, + ClientHttpSecurityPostProcessor httpPostProcessor) + throws Exception { + + ServletConfigurationSupport.configureCors(http, addonsClientProperties.getCors()); + ServletConfigurationSupport.configureState(http, false, addonsClientProperties.getCsrf()); + ServletConfigurationSupport.configureAccess(http, addonsClientProperties.getPermitAll(), authorizePostProcessor); + + if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { + http.requiresChannel(channel -> channel.anyRequest().requiresSecure()); + } + + return httpPostProcessor.process(http); + } + + public static HttpSecurity configureAccess(HttpSecurity http, String[] permitAll, ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor) + throws Exception { + if (permitAll.length > 0) { + http.anonymous(withDefaults()); + http.authorizeHttpRequests(registry -> authorizePostProcessor.authorizeHttpRequests(registry.requestMatchers(permitAll).permitAll())); + } else { + http.authorizeHttpRequests(registry -> authorizePostProcessor.authorizeHttpRequests(registry)); + } + return http; + } + + public static HttpSecurity configureCors(HttpSecurity http, CorsProperties[] corsProperties) throws Exception { + if (corsProperties.length == 0) { + http.cors(cors -> cors.disable()); + } else { + final var source = new UrlBasedCorsConfigurationSource(); + for (final var corsProps : corsProperties) { + final var configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); + configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); + configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); + configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); + source.registerCorsConfiguration(corsProps.getPath(), configuration); + } + http.cors(cors -> cors.configurationSource(source)); + } + return http; + } + + public static HttpSecurity configureState(HttpSecurity http, boolean isStatless, SpringAddonsSecurityProperties.Csrf csrfEnum) throws Exception { + + if (isStatless) { + http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + } + + http.csrf(configurer -> { + final var delegate = new XorCsrfTokenRequestAttributeHandler(); + delegate.setCsrfRequestAttributeName("_csrf"); + switch (csrfEnum) { + case DISABLE: + configurer.disable(); + break; + case DEFAULT: + if (isStatless) { + configurer.disable(); + } + break; + case SESSION: + break; + case COOKIE_HTTP_ONLY: + configurer.csrfTokenRepository(new CookieCsrfTokenRepository()).csrfTokenRequestHandler(delegate::handle); + http.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); + break; + case COOKIE_ACCESSIBLE_FROM_JS: + // Adapted from + // https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_angularjs_or_another_javascript_framework + configurer.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(delegate::handle); + http.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); + break; + } + }); + + return http; + } + + /** + * https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_a_single_page_application_with_cookiecsrftokenrepository + */ + private static final class CsrfCookieFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, + IOException { + CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); + // Render the token value to a cookie by causing the deferred token to be loaded + csrfToken.getToken(); + + filterChain.doFilter(request, response); + } + + } } diff --git a/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java b/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java index 04f4e51db..18987aac6 100644 --- a/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java +++ b/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java @@ -19,18 +19,16 @@ @Import({ SpringAddonsSecurityProperties.class }) public class AddonsSecurityBeans { - /** - * Retrieves granted authorities from the introspected token attributes, - * according to configuration set for the issuer set in this - * attributes - * - * @param securityProperties - * @return - */ - @ConditionalOnMissingBean - @Bean - OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { - log.debug("Building default SimpleJwtGrantedAuthoritiesConverter with: {}", addonsProperties); - return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); - } + /** + * Retrieves granted authorities from the introspected token attributes, according to configuration set for the issuer set in this attributes + * + * @param securityProperties + * @return + */ + @ConditionalOnMissingBean + @Bean + OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { + log.debug("Building default SimpleJwtGrantedAuthoritiesConverter with: {}", addonsProperties); + return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); + } } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java b/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java index a0c08706f..71a611d6a 100644 --- a/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java +++ b/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java @@ -43,37 +43,24 @@ /** *

* Usage
- * If not using spring-boot, @Import or @ComponentScan this class. All - * beans defined here are @ConditionalOnMissingBean => - * just define your own @Beans to override. + * If not using spring-boot, @Import or @ComponentScan this class. All beans defined here are @ConditionalOnMissingBean => just define your own + * @Beans to override. *

*

* Provided @Beans *

*
    - *
  • springAddonsResourceServerSecurityFilterChain: applies CORS, CSRF, - * anonymous, sessionCreationPolicy, SSL, redirect and 401 instead of redirect - * to login as defined in springAddonsResourceServerSecurityFilterChain: applies CORS, CSRF, anonymous, sessionCreationPolicy, SSL, redirect and 401 instead of redirect to login + * as defined in SpringAddonsSecurityProperties
  • - *
  • authorizePostProcessor: a bean of type - * {@link ExpressionInterceptUrlRegistryPostProcessor} to fine - * tune access - * control from java configuration. It applies to all routes not listed in - * "permit-all" property configuration. Default requires users to be - * authenticated. This is a bean to provide in your application configuration - * if you prefer to define fine-grained access control rules with Java - * configuration rather than methods security.
  • - *
  • httpPostProcessor: a bean of type - * {@link ResourceServerHttpSecurityPostProcessor} to - * override anything from above auto-configuration. It is called just before the - * security filter-chain is returned. Default is a no-op.
  • - *
  • introspectionAuthenticationConverter: a converter from a successful - * introspection to something inheriting from - * {@link AbstractAuthenticationToken}. The default instantiate a - * `BearerTokenAuthentication` with authorities mapping as configured for the - * issuer declared in the introspected claims. The easiest to override the type - * of {@link AbstractAuthenticationToken}, is to provide with an - * {@link OAuth2AuthenticationFactory} bean.
  • + *
  • authorizePostProcessor: a bean of type {@link ExpressionInterceptUrlRegistryPostProcessor} to fine tune access control from java configuration. It + * applies to all routes not listed in "permit-all" property configuration. Default requires users to be authenticated. This is a bean to provide in your + * application configuration if you prefer to define fine-grained access control rules with Java configuration rather than methods security.
  • + *
  • httpPostProcessor: a bean of type {@link ResourceServerHttpSecurityPostProcessor} to override anything from above auto-configuration. It is called just + * before the security filter-chain is returned. Default is a no-op.
  • + *
  • introspectionAuthenticationConverter: a converter from a successful introspection to something inheriting from {@link AbstractAuthenticationToken}. The + * default instantiate a `BearerTokenAuthentication` with authorities mapping as configured for the issuer declared in the introspected claims. The easiest to + * override the type of {@link AbstractAuthenticationToken}, is to provide with an {@link OAuth2AuthenticationFactory} bean.
  • *
* * @author Jerome Wacongne ch4mp@c4-soft.com @@ -85,143 +72,118 @@ @Import({ AddonsSecurityBeans.class }) public class AddonsWebSecurityBeans { - /** - *

- * Applies SpringAddonsSecurityProperties to web security config. Be aware that - * defining a {@link SecurityWebFilterChain} bean with no security matcher and - * an order higher than LOWEST_PRECEDENCE will disable most of this lib - * auto-configuration for OpenID resource-servers. - *

- *

- * You should consider to set security matcher to all other - * {@link SecurityWebFilterChain} beans and provide - * a {@link ServerHttpSecurityPostProcessor} bean to override anything from this - * bean - *

- * . - * - * @param http HTTP security to configure - * @param serverProperties Spring "server" configuration - * properties - * @param addonsProperties "com.c4-soft.springaddons.security" - * configuration properties - * @param authorizePostProcessor Hook to override access-control - * rules for all path that are not - * listed in "permit-all" - * @param httpPostProcessor Hook to override all or part of - * HttpSecurity auto-configuration - * @param introspectionAuthenticationConverter Converts successful introspection - * result into an - * {@link Authentication} - * @return A default {@link SecurityWebFilterChain} for servlet resource-servers - * with access-token introspection (matches all unmatched routes with - * lowest precedence) - */ - @Order(Ordered.LOWEST_PRECEDENCE) - @Bean - SecurityFilterChain springAddonsResourceServerSecurityFilterChain( - HttpSecurity http, - ServerProperties serverProperties, - SpringAddonsSecurityProperties addonsProperties, - ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, - ResourceServerHttpSecurityPostProcessor httpPostProcessor, - OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter, - OpaqueTokenIntrospector opaqueTokenIntrospector) - throws Exception { - http.oauth2ResourceServer(server -> server.opaqueToken(ot -> { - ot.introspector(opaqueTokenIntrospector); - ot.authenticationConverter(introspectionAuthenticationConverter); - })); + /** + *

+ * Applies SpringAddonsSecurityProperties to web security config. Be aware that defining a {@link SecurityWebFilterChain} bean with no security matcher and + * an order higher than LOWEST_PRECEDENCE will disable most of this lib auto-configuration for OpenID resource-servers. + *

+ *

+ * You should consider to set security matcher to all other {@link SecurityWebFilterChain} beans and provide a {@link ServerHttpSecurityPostProcessor} bean + * to override anything from this bean + *

+ * . + * + * @param http HTTP security to configure + * @param serverProperties Spring "server" configuration properties + * @param addonsProperties "com.c4-soft.springaddons.security" configuration properties + * @param authorizePostProcessor Hook to override access-control rules for all path that are not listed in "permit-all" + * @param httpPostProcessor Hook to override all or part of HttpSecurity auto-configuration + * @param introspectionAuthenticationConverter Converts successful introspection result into an {@link Authentication} + * @return A default {@link SecurityWebFilterChain} for servlet resource-servers with access-token introspection + * (matches all unmatched routes with lowest precedence) + */ + @Order(Ordered.LOWEST_PRECEDENCE) + @Bean + SecurityFilterChain springAddonsResourceServerSecurityFilterChain( + HttpSecurity http, + ServerProperties serverProperties, + SpringAddonsSecurityProperties addonsProperties, + ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, + ResourceServerHttpSecurityPostProcessor httpPostProcessor, + OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter, + OpaqueTokenIntrospector opaqueTokenIntrospector) + throws Exception { + http.oauth2ResourceServer(server -> server.opaqueToken(ot -> { + ot.introspector(opaqueTokenIntrospector); + ot.authenticationConverter(introspectionAuthenticationConverter); + })); - ServletConfigurationSupport.configureResourceServer(http, serverProperties, addonsProperties, - authorizePostProcessor, httpPostProcessor); + ServletConfigurationSupport.configureResourceServer(http, serverProperties, addonsProperties, authorizePostProcessor, httpPostProcessor); - return http.build(); - } + return http.build(); + } - /** - * Hook to override security rules for all path that are not listed in - * "permit-all". Default is isAuthenticated(). - * - * @return a hook to override security rules for all path that are not listed in - * "permit-all". Default is isAuthenticated(). - */ - @ConditionalOnMissingBean - @Bean - ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() { - return registry -> registry.anyRequest().authenticated(); - } + /** + * Hook to override security rules for all path that are not listed in "permit-all". Default is isAuthenticated(). + * + * @return a hook to override security rules for all path that are not listed in "permit-all". Default is isAuthenticated(). + */ + @ConditionalOnMissingBean + @Bean + ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() { + return registry -> registry.anyRequest().authenticated(); + } - /** - * Hook to override all or part of HttpSecurity auto-configuration. - * Called after spring-addons configuration was applied so that you can - * modify anything - * - * @return a hook to override all or part of HttpSecurity auto-configuration. - * Called after spring-addons configuration was applied so that you can - * modify anything - */ - @ConditionalOnMissingBean - @Bean - ResourceServerHttpSecurityPostProcessor httpPostProcessor() { - return httpSecurity -> httpSecurity; - } + /** + * Hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify anything + * + * @return a hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify + * anything + */ + @ConditionalOnMissingBean + @Bean + ResourceServerHttpSecurityPostProcessor httpPostProcessor() { + return httpSecurity -> httpSecurity; + } - CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) { - log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList()); - final var source = new UrlBasedCorsConfigurationSource(); - for (final var corsProps : corsProperties) { - final var configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); - configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); - configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); - configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); - source.registerCorsConfiguration(corsProps.getPath(), configuration); - } - return source; - } + CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) { + log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList()); + final var source = new UrlBasedCorsConfigurationSource(); + for (final var corsProps : corsProperties) { + final var configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); + configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); + configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); + configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); + source.registerCorsConfiguration(corsProps.getPath(), configuration); + } + return source; + } - /** - * Converter bean from successful introspection result to an - * {@link Authentication} instance - * - * @param authoritiesConverter converts access-token claims into Spring - * authorities - * @param authenticationFactory builds an {@link Authentication} instance from - * access-token string and claims - * @return a converter from successful introspection result to an - * {@link Authentication} instance - */ - @SuppressWarnings("unchecked") - @ConditionalOnMissingBean - @Bean - OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter( - Converter, Collection> authoritiesConverter, - Optional authenticationFactory, - SpringAddonsSecurityProperties addonsProperties, - OAuth2ResourceServerProperties resourceServerProperties) { - return (String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> { - return authenticationFactory - .map(af -> af.build(introspectedToken, authenticatedPrincipal.getAttributes())).orElse( - new BearerTokenAuthentication( - new OAuth2IntrospectionAuthenticatedPrincipal( - new OpenidClaimSet(authenticatedPrincipal.getAttributes(), - Stream.of(addonsProperties.getIssuers()) - .filter(issProps -> resourceServerProperties - .getOpaquetoken().getIntrospectionUri() - .contains(issProps.getLocation().toString())) - .findAny().orElse(addonsProperties.getIssuers()[0]) - .getUsernameClaim()) - .getName(), - authenticatedPrincipal.getAttributes(), - (Collection) authenticatedPrincipal.getAuthorities()), - new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, - introspectedToken, - authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT), - authenticatedPrincipal - .getAttribute(OAuth2TokenIntrospectionClaimNames.EXP)), - authoritiesConverter.convert(authenticatedPrincipal.getAttributes()))); - }; - } + /** + * Converter bean from successful introspection result to an {@link Authentication} instance + * + * @param authoritiesConverter converts access-token claims into Spring authorities + * @param authenticationFactory builds an {@link Authentication} instance from access-token string and claims + * @return a converter from successful introspection result to an {@link Authentication} instance + */ + @SuppressWarnings("unchecked") + @ConditionalOnMissingBean + @Bean + OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter( + Converter, Collection> authoritiesConverter, + Optional authenticationFactory, + SpringAddonsSecurityProperties addonsProperties, + OAuth2ResourceServerProperties resourceServerProperties) { + return (String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> { + return authenticationFactory.map(af -> af.build(introspectedToken, authenticatedPrincipal.getAttributes())).orElse( + new BearerTokenAuthentication( + new OAuth2IntrospectionAuthenticatedPrincipal( + new OpenidClaimSet( + authenticatedPrincipal.getAttributes(), + Stream.of(addonsProperties.getIssuers()) + .filter( + issProps -> resourceServerProperties.getOpaquetoken().getIntrospectionUri() + .contains(issProps.getLocation().toString())) + .findAny().orElse(addonsProperties.getIssuers()[0]).getUsernameClaim()).getName(), + authenticatedPrincipal.getAttributes(), + (Collection) authenticatedPrincipal.getAuthorities()), + new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + introspectedToken, + authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT), + authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP)), + authoritiesConverter.convert(authenticatedPrincipal.getAttributes()))); + }; + } } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java b/webmvc/spring-addons-webmvc-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java index 063c72f27..1a9276ad6 100644 --- a/webmvc/spring-addons-webmvc-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java +++ b/webmvc/spring-addons-webmvc-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java @@ -13,8 +13,8 @@ /** *

- * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or - * repositories (web context is not desired in that case). + * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or repositories (web context + * is not desired in that case). *

* See {@link AutoConfigureAddonsSecurity} * diff --git a/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java b/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java index 94417487d..5edda2d69 100644 --- a/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java +++ b/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java @@ -19,17 +19,16 @@ @Import({ SpringAddonsSecurityProperties.class }) public class AddonsSecurityBeans { - /** - * Retrieves granted authorities from the Jwt (from its private claims or with - * the help of an external service) - * - * @param securityProperties - * @return - */ - @ConditionalOnMissingBean - @Bean - OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { - log.debug("Building default SimpleJwtGrantedAuthoritiesConverter with: {}", addonsProperties); - return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); - } + /** + * Retrieves granted authorities from the Jwt (from its private claims or with the help of an external service) + * + * @param securityProperties + * @return + */ + @ConditionalOnMissingBean + @Bean + OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) { + log.debug("Building default SimpleJwtGrantedAuthoritiesConverter with: {}", addonsProperties); + return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties); + } } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java b/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java index fa9306953..2d4ed55f3 100644 --- a/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java +++ b/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java @@ -48,42 +48,28 @@ /** *

* Usage
- * If not using spring-boot, @Import or @ComponentScan this class. All - * beans defined here are @ConditionalOnMissingBean => - * just define your own @Beans to override. + * If not using spring-boot, @Import or @ComponentScan this class. All beans defined here are @ConditionalOnMissingBean => just define your own + * @Beans to override. *

*

* Provided @Beans *

*
    - *
  • springAddonsResourceServerSecurityFilterChain: applies CORS, CSRF, - * anonymous, sessionCreationPolicy, SSL, redirect and 401 instead of redirect - * to login as defined in springAddonsResourceServerSecurityFilterChain: applies CORS, CSRF, anonymous, sessionCreationPolicy, SSL, redirect and 401 instead of redirect to login + * as defined in SpringAddonsSecurityProperties
  • - *
  • authorizePostProcessor: a bean of type - * {@link ResourceServerExpressionInterceptUrlRegistryPostProcessor} to fine - * tune access - * control from java configuration. It applies to all routes not listed in - * "permit-all" property configuration. Default requires users to be - * authenticated. This is a bean to provide in your application configuration - * if you prefer to define fine-grained access control rules with Java - * configuration rather than methods security.
  • - *
  • httpPostProcessor: a bean of type - * {@link ResourceServerHttpSecurityPostProcessor} to - * override anything from above auto-configuration. It is called just before the - * security filter-chain is returned. Default is a no-op.
  • - *
  • jwtAuthenticationConverter: a converter from a {@link Jwt} to something - * inheriting from {@link AbstractAuthenticationToken}. The default instantiate - * a {@link JwtAuthenticationToken} with username and authorities as configured - * for the issuer of thi token. The easiest to override the type of - * {@link AbstractAuthenticationToken}, is to provide with an - * {@link OAuth2AuthenticationFactory} bean.
  • - *
  • authenticationManagerResolver: to accept authorities from more than one - * issuer, the recommended way is to provide an - * {@link AuthenticationManagerResolver} supporting it. - * Default keeps a - * {@link JwtAuthenticationProvider} with its own {@link JwtDecoder} for each - * issuer.
  • + *
  • authorizePostProcessor: a bean of type {@link ResourceServerExpressionInterceptUrlRegistryPostProcessor} to fine tune access control from java + * configuration. It applies to all routes not listed in "permit-all" property configuration. Default requires users to be authenticated. This is a bean to + * provide in your application configuration if you prefer to define fine-grained access control rules with Java configuration rather than methods + * security.
  • + *
  • httpPostProcessor: a bean of type {@link ResourceServerHttpSecurityPostProcessor} to override anything from above auto-configuration. It is called just + * before the security filter-chain is returned. Default is a no-op.
  • + *
  • jwtAuthenticationConverter: a converter from a {@link Jwt} to something inheriting from {@link AbstractAuthenticationToken}. The default instantiate a + * {@link JwtAuthenticationToken} with username and authorities as configured for the issuer of thi token. The easiest to override the type of + * {@link AbstractAuthenticationToken}, is to provide with an {@link OAuth2AuthenticationFactory} bean.
  • + *
  • authenticationManagerResolver: to accept authorities from more than one issuer, the recommended way is to provide an + * {@link AuthenticationManagerResolver} supporting it. Default keeps a {@link JwtAuthenticationProvider} with its own {@link JwtDecoder} + * for each issuer.
  • *
* * @author Jerome Wacongne ch4mp@c4-soft.com @@ -94,149 +80,120 @@ @Slf4j @Import({ AddonsSecurityBeans.class }) public class AddonsWebSecurityBeans { - /** - *

- * Applies SpringAddonsSecurityProperties to web security config. Be aware that - * defining a {@link SecurityWebFilterChain} bean with no - * security matcher and an order higher than LOWEST_PRECEDENCE will disable most - * of this lib auto-configuration for OpenID resource-servers. - *

- *

- * You should consider to set security matcher to all other - * {@link SecurityWebFilterChain} beans and provide a - * {@link ServerHttpSecurityPostProcessor} bean to override anything from this - * bean - *

- * . - * - * @param http HTTP security to configure - * @param serverProperties Spring "server" configuration properties - * @param addonsProperties "com.c4-soft.springaddons.security" - * configuration properties - * @param authorizePostProcessor Hook to override access-control rules - * for all path that are not listed in - * "permit-all" - * @param httpPostProcessor Hook to override all or part of - * HttpSecurity auto-configuration - * @param authenticationManagerResolver Converts successful JWT decoding result - * into an {@link Authentication} - * @return A default {@link SecurityWebFilterChain} for servlet resource-servers - * with JWT decoder (matches all - * unmatched routes with lowest precedence) - */ - @Order(Ordered.LOWEST_PRECEDENCE) - @Bean - SecurityFilterChain springAddonsResourceServerSecurityFilterChain( - HttpSecurity http, - ServerProperties serverProperties, - SpringAddonsSecurityProperties addonsProperties, - ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, - ResourceServerHttpSecurityPostProcessor httpPostProcessor, - AuthenticationManagerResolver authenticationManagerResolver) - throws Exception { - http.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver)); - - ServletConfigurationSupport.configureResourceServer(http, serverProperties, addonsProperties, - authorizePostProcessor, httpPostProcessor); - - return http.build(); - } - - /** - * hook to override security rules for all path that are not listed in - * "permit-all". Default is isAuthenticated(). - * - * @return a hook to override security rules for all path that are not listed in - * "permit-all". Default is isAuthenticated(). - */ - @ConditionalOnMissingBean - @Bean - ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() { - return registry -> registry.anyRequest().authenticated(); - } - - /** - * Hook to override all or part of HttpSecurity auto-configuration. Called after - * spring-addons configuration was applied so that you can - * modify anything - * - * @return a hook to override all or part of HttpSecurity auto-configuration. - * Called after spring-addons configuration was applied so that - * you can modify anything - */ - @ConditionalOnMissingBean - @Bean - ResourceServerHttpSecurityPostProcessor httpPostProcessor() { - return httpSecurity -> httpSecurity; - } - - CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) { - log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList()); - final var source = new UrlBasedCorsConfigurationSource(); - for (final var corsProps : corsProperties) { - final var configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); - configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); - configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); - configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); - source.registerCorsConfiguration(corsProps.getPath(), configuration); - } - return source; - } - - public static interface Jwt2AuthenticationConverter - extends Converter { - } - - /** - * Converter bean from {@link Jwt} to {@link AbstractAuthenticationToken} - * - * @param authoritiesConverter converts access-token claims into Spring - * authorities - * @param securityProperties Spring "spring.security" configuration - * properties - * @param authenticationFactory builds an {@link Authentication} instance from - * access-token string and claims - * @return a converter from {@link Jwt} to {@link AbstractAuthenticationToken} - */ - @ConditionalOnMissingBean - @Bean - Jwt2AuthenticationConverter jwtAuthenticationConverter( - Converter, Collection> authoritiesConverter, - SpringAddonsSecurityProperties addonsProperties, - Optional authenticationFactory) { - return jwt -> authenticationFactory.map(af -> af.build(jwt.getTokenValue(), jwt.getClaims())).orElse( - new JwtAuthenticationToken( - jwt, - authoritiesConverter.convert(jwt.getClaims()), - new OpenidClaimSet(jwt.getClaims(), - addonsProperties.getIssuerProperties(jwt.getIssuer()).getUsernameClaim()).getName())); - } - - /** - * Provides with multi-tenancy: builds a - * AuthenticationManagerResolver - * per provided OIDC issuer URI - * - * @param auth2ResourceServerProperties "spring.security.oauth2.resourceserver" - * configuration properties - * @param addonsProperties "com.c4-soft.springaddons.security" - * configuration properties - * @param jwtAuthenticationConverter converts from a {@link Jwt} to an - * {@link Authentication} implementation - * @return Multi-tenant - * {@link AuthenticationManagerResolver} (one for - * each configured issuer) - */ - @ConditionalOnMissingBean - @Bean - AuthenticationManagerResolver authenticationManagerResolver( - OAuth2ResourceServerProperties auth2ResourceServerProperties, - SpringAddonsSecurityProperties addonsProperties, - Converter jwtAuthenticationConverter) { - final var jwtProps = Optional.ofNullable(auth2ResourceServerProperties) - .map(OAuth2ResourceServerProperties::getJwt); - // @formatter:off + /** + *

+ * Applies SpringAddonsSecurityProperties to web security config. Be aware that defining a {@link SecurityWebFilterChain} bean with no security matcher and + * an order higher than LOWEST_PRECEDENCE will disable most of this lib auto-configuration for OpenID resource-servers. + *

+ *

+ * You should consider to set security matcher to all other {@link SecurityWebFilterChain} beans and provide a {@link ServerHttpSecurityPostProcessor} bean + * to override anything from this bean + *

+ * . + * + * @param http HTTP security to configure + * @param serverProperties Spring "server" configuration properties + * @param addonsProperties "com.c4-soft.springaddons.security" configuration properties + * @param authorizePostProcessor Hook to override access-control rules for all path that are not listed in "permit-all" + * @param httpPostProcessor Hook to override all or part of HttpSecurity auto-configuration + * @param authenticationManagerResolver Converts successful JWT decoding result into an {@link Authentication} + * @return A default {@link SecurityWebFilterChain} for servlet resource-servers with JWT decoder (matches all unmatched + * routes with lowest precedence) + */ + @Order(Ordered.LOWEST_PRECEDENCE) + @Bean + SecurityFilterChain springAddonsResourceServerSecurityFilterChain( + HttpSecurity http, + ServerProperties serverProperties, + SpringAddonsSecurityProperties addonsProperties, + ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, + ResourceServerHttpSecurityPostProcessor httpPostProcessor, + AuthenticationManagerResolver authenticationManagerResolver) + throws Exception { + http.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver)); + + ServletConfigurationSupport.configureResourceServer(http, serverProperties, addonsProperties, authorizePostProcessor, httpPostProcessor); + + return http.build(); + } + + /** + * hook to override security rules for all path that are not listed in "permit-all". Default is isAuthenticated(). + * + * @return a hook to override security rules for all path that are not listed in "permit-all". Default is isAuthenticated(). + */ + @ConditionalOnMissingBean + @Bean + ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() { + return registry -> registry.anyRequest().authenticated(); + } + + /** + * Hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify anything + * + * @return a hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify + * anything + */ + @ConditionalOnMissingBean + @Bean + ResourceServerHttpSecurityPostProcessor httpPostProcessor() { + return httpSecurity -> httpSecurity; + } + + CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) { + log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList()); + final var source = new UrlBasedCorsConfigurationSource(); + for (final var corsProps : corsProperties) { + final var configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); + configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); + configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); + configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); + source.registerCorsConfiguration(corsProps.getPath(), configuration); + } + return source; + } + + public static interface Jwt2AuthenticationConverter extends Converter { + } + + /** + * Converter bean from {@link Jwt} to {@link AbstractAuthenticationToken} + * + * @param authoritiesConverter converts access-token claims into Spring authorities + * @param securityProperties Spring "spring.security" configuration properties + * @param authenticationFactory builds an {@link Authentication} instance from access-token string and claims + * @return a converter from {@link Jwt} to {@link AbstractAuthenticationToken} + */ + @ConditionalOnMissingBean + @Bean + Jwt2AuthenticationConverter jwtAuthenticationConverter( + Converter, Collection> authoritiesConverter, + SpringAddonsSecurityProperties addonsProperties, + Optional authenticationFactory) { + return jwt -> authenticationFactory.map(af -> af.build(jwt.getTokenValue(), jwt.getClaims())).orElse( + new JwtAuthenticationToken( + jwt, + authoritiesConverter.convert(jwt.getClaims()), + new OpenidClaimSet(jwt.getClaims(), addonsProperties.getIssuerProperties(jwt.getIssuer()).getUsernameClaim()).getName())); + } + + /** + * Provides with multi-tenancy: builds a AuthenticationManagerResolver per provided OIDC issuer URI + * + * @param auth2ResourceServerProperties "spring.security.oauth2.resourceserver" configuration properties + * @param addonsProperties "com.c4-soft.springaddons.security" configuration properties + * @param jwtAuthenticationConverter converts from a {@link Jwt} to an {@link Authentication} implementation + * @return Multi-tenant {@link AuthenticationManagerResolver} (one for each configured issuer) + */ + @ConditionalOnMissingBean + @Bean + AuthenticationManagerResolver authenticationManagerResolver( + OAuth2ResourceServerProperties auth2ResourceServerProperties, + SpringAddonsSecurityProperties addonsProperties, + Converter jwtAuthenticationConverter) { + final var jwtProps = Optional.ofNullable(auth2ResourceServerProperties).map(OAuth2ResourceServerProperties::getJwt); + // @formatter:off Optional.ofNullable(jwtProps.map(OAuth2ResourceServerProperties.Jwt::getIssuerUri)).orElse(jwtProps.map(OAuth2ResourceServerProperties.Jwt::getJwkSetUri)) .filter(StringUtils::hasLength) .ifPresent(jwtConf -> { @@ -244,22 +201,21 @@ AuthenticationManagerResolver authenticationManagerResolver( }); // @formatter:on - final Map jwtManagers = Stream.of(addonsProperties.getIssuers()) - .collect(Collectors.toMap(issuer -> issuer.getLocation().toString(), issuer -> { - JwtDecoder decoder = issuer.getJwkSetUri() != null - && StringUtils.hasLength(issuer.getJwkSetUri().toString()) - ? NimbusJwtDecoder.withJwkSetUri(issuer.getJwkSetUri().toString()).build() - : JwtDecoders.fromIssuerLocation(issuer.getLocation().toString()); - var provider = new JwtAuthenticationProvider(decoder); - provider.setJwtAuthenticationConverter(jwtAuthenticationConverter); - return provider::authenticate; - })); - - log.debug( - "Building default JwtIssuerAuthenticationManagerResolver with: ", - auth2ResourceServerProperties.getJwt(), - Stream.of(addonsProperties.getIssuers()).toList()); - - return new JwtIssuerAuthenticationManagerResolver((AuthenticationManagerResolver) jwtManagers::get); - } + final Map jwtManagers = + Stream.of(addonsProperties.getIssuers()).collect(Collectors.toMap(issuer -> issuer.getLocation().toString(), issuer -> { + JwtDecoder decoder = issuer.getJwkSetUri() != null && StringUtils.hasLength(issuer.getJwkSetUri().toString()) + ? NimbusJwtDecoder.withJwkSetUri(issuer.getJwkSetUri().toString()).build() + : JwtDecoders.fromIssuerLocation(issuer.getLocation().toString()); + var provider = new JwtAuthenticationProvider(decoder); + provider.setJwtAuthenticationConverter(jwtAuthenticationConverter); + return provider::authenticate; + })); + + log.debug( + "Building default JwtIssuerAuthenticationManagerResolver with: ", + auth2ResourceServerProperties.getJwt(), + Stream.of(addonsProperties.getIssuers()).toList()); + + return new JwtIssuerAuthenticationManagerResolver((AuthenticationManagerResolver) jwtManagers::get); + } } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webmvc/jwt/AutoConfigureAddonsWebSecurity.java b/webmvc/spring-addons-webmvc-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webmvc/jwt/AutoConfigureAddonsWebSecurity.java index 4babb2ca7..64c32f710 100644 --- a/webmvc/spring-addons-webmvc-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webmvc/jwt/AutoConfigureAddonsWebSecurity.java +++ b/webmvc/spring-addons-webmvc-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webmvc/jwt/AutoConfigureAddonsWebSecurity.java @@ -14,10 +14,8 @@ /** *

- * Auto-configures {@link AddonsSecurityBeans} and - * {@link AddonsWebSecurityBeans}. To be used to test controllers but not - * services or - * repositories (web context is not desired in that case). + * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or repositories (web context + * is not desired in that case). *

* See {@link AutoConfigureAddonsSecurity} * @@ -26,7 +24,6 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @AutoConfigureAddonsSecurity -@ImportAutoConfiguration({ AddonsWebSecurityBeans.class, SpringAddonsOAuth2ClientBeans.class, - AddonsWebmvcTestConf.class }) +@ImportAutoConfiguration({ AddonsWebSecurityBeans.class, SpringAddonsOAuth2ClientBeans.class, AddonsWebmvcTestConf.class }) public @interface AutoConfigureAddonsWebSecurity { } diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AddonsWebmvcTestConf.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AddonsWebmvcTestConf.java index f5177a54c..af84c9c3a 100644 --- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AddonsWebmvcTestConf.java +++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AddonsWebmvcTestConf.java @@ -1,14 +1,13 @@ /* * Copyright 2020 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.mockmvc; @@ -60,137 +59,138 @@ @Import({ MockMvcProperties.class }) public class AddonsWebmvcTestConf { - @MockBean - JwtDecoder jwtDecoder; - - @MockBean - AuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver; - - @MockBean - OpaqueTokenIntrospector introspector; - - @ConditionalOnMissingBean - @Bean - InMemoryClientRegistrationRepository clientRegistrationRepository() { - final var clientRegistrationRepository = mock(InMemoryClientRegistrationRepository.class); - when(clientRegistrationRepository.iterator()).thenReturn(new ArrayList().iterator()); - when(clientRegistrationRepository.spliterator()).thenReturn(new ArrayList().spliterator()); - return clientRegistrationRepository; - } - - @MockBean - OAuth2AuthorizedClientService oAuth2AuthorizedClientService; - - @Bean - SerializationHelper serializationHelper(ObjectFactory messageConverters) { - return new SerializationHelper(messageConverters); - } - - @Bean - @Scope("prototype") - MockMvcSupport mockMvcSupport( - MockMvc mockMvc, - SerializationHelper serializationHelper, - MockMvcProperties mockMvcProperties, - ServerProperties serverProperties, - SpringAddonsSecurityProperties addonsProperties) { - return new MockMvcSupport(mockMvc, serializationHelper, mockMvcProperties, serverProperties, - addonsProperties); - } - - @ConditionalOnMissingBean - @Bean - OAuth2AuthoritiesConverter claimSet2AuthoritiesConverter() { - return mock(OAuth2AuthoritiesConverter.class); - } - - @ConditionalOnMissingBean - @Bean - SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http, ServerProperties serverProperties, - SpringAddonsSecurityProperties addonsProperties, - ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, - ResourceServerHttpSecurityPostProcessor httpPostProcessor, - CorsConfigurationSource corsConfigurationSource) throws Exception { - - if (addonsProperties.getPermitAll().length > 0) { - http.anonymous(); - } - - if (addonsProperties.getCors().length > 0) { - http.cors().configurationSource(corsConfigurationSource); - } else { - http.cors().disable(); - } - - switch (addonsProperties.getCsrf()) { - case DISABLE: - http.csrf().disable(); - break; - case DEFAULT: - if (addonsProperties.isStatlessSessions()) { - http.csrf().disable(); - } else { - http.csrf(); - } - break; - case SESSION: - http.csrf(); - break; - case COOKIE_HTTP_ONLY: - http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository()); - break; - case COOKIE_ACCESSIBLE_FROM_JS: - http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) - .csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler()::handle); - break; - } - - if (addonsProperties.isStatlessSessions()) { - http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); - } - - if (!addonsProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) { - http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> { - response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\""); - response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); - }); - } - - if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { - http.requiresChannel().anyRequest().requiresSecure(); - } - - authorizePostProcessor.authorizeHttpRequests( - http.authorizeHttpRequests().requestMatchers(addonsProperties.getPermitAll()).permitAll()); - - return httpPostProcessor.process(http).build(); - } - - @ConditionalOnMissingBean - @Bean - ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() { - return registry -> registry.anyRequest().authenticated(); - } - - @ConditionalOnMissingBean - @Bean - ResourceServerHttpSecurityPostProcessor httpPostProcessor() { - return httpSecurity -> httpSecurity; - } - - @ConditionalOnMissingBean - @Bean - CorsConfigurationSource corsConfigurationSource(SpringAddonsSecurityProperties addonsProperties) { - final var source = new UrlBasedCorsConfigurationSource(); - for (final var corsProps : addonsProperties.getCors()) { - final var configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); - configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); - configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); - configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); - source.registerCorsConfiguration(corsProps.getPath(), configuration); - } - return source; - } + @MockBean + JwtDecoder jwtDecoder; + + @MockBean + AuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver; + + @MockBean + OpaqueTokenIntrospector introspector; + + @ConditionalOnMissingBean + @Bean + InMemoryClientRegistrationRepository clientRegistrationRepository() { + final var clientRegistrationRepository = mock(InMemoryClientRegistrationRepository.class); + when(clientRegistrationRepository.iterator()).thenReturn(new ArrayList().iterator()); + when(clientRegistrationRepository.spliterator()).thenReturn(new ArrayList().spliterator()); + return clientRegistrationRepository; + } + + @MockBean + OAuth2AuthorizedClientService oAuth2AuthorizedClientService; + + @Bean + SerializationHelper serializationHelper(ObjectFactory messageConverters) { + return new SerializationHelper(messageConverters); + } + + @Bean + @Scope("prototype") + MockMvcSupport mockMvcSupport( + MockMvc mockMvc, + SerializationHelper serializationHelper, + MockMvcProperties mockMvcProperties, + ServerProperties serverProperties, + SpringAddonsSecurityProperties addonsProperties) { + return new MockMvcSupport(mockMvc, serializationHelper, mockMvcProperties, serverProperties, addonsProperties); + } + + @ConditionalOnMissingBean + @Bean + OAuth2AuthoritiesConverter claimSet2AuthoritiesConverter() { + return mock(OAuth2AuthoritiesConverter.class); + } + + @ConditionalOnMissingBean + @Bean + SecurityFilterChain resourceServerSecurityFilterChain( + HttpSecurity http, + ServerProperties serverProperties, + SpringAddonsSecurityProperties addonsProperties, + ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, + ResourceServerHttpSecurityPostProcessor httpPostProcessor, + CorsConfigurationSource corsConfigurationSource) + throws Exception { + + if (addonsProperties.getPermitAll().length > 0) { + http.anonymous(); + } + + if (addonsProperties.getCors().length > 0) { + http.cors().configurationSource(corsConfigurationSource); + } else { + http.cors().disable(); + } + + switch (addonsProperties.getCsrf()) { + case DISABLE: + http.csrf().disable(); + break; + case DEFAULT: + if (addonsProperties.isStatlessSessions()) { + http.csrf().disable(); + } else { + http.csrf(); + } + break; + case SESSION: + http.csrf(); + break; + case COOKIE_HTTP_ONLY: + http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository()); + break; + case COOKIE_ACCESSIBLE_FROM_JS: + http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + .csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler()::handle); + break; + } + + if (addonsProperties.isStatlessSessions()) { + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + } + + if (!addonsProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) { + http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> { + response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\""); + response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); + }); + } + + if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) { + http.requiresChannel().anyRequest().requiresSecure(); + } + + authorizePostProcessor.authorizeHttpRequests(http.authorizeHttpRequests().requestMatchers(addonsProperties.getPermitAll()).permitAll()); + + return httpPostProcessor.process(http).build(); + } + + @ConditionalOnMissingBean + @Bean + ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() { + return registry -> registry.anyRequest().authenticated(); + } + + @ConditionalOnMissingBean + @Bean + ResourceServerHttpSecurityPostProcessor httpPostProcessor() { + return httpSecurity -> httpSecurity; + } + + @ConditionalOnMissingBean + @Bean + CorsConfigurationSource corsConfigurationSource(SpringAddonsSecurityProperties addonsProperties) { + final var source = new UrlBasedCorsConfigurationSource(); + for (final var corsProps : addonsProperties.getCors()) { + final var configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins())); + configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods())); + configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders())); + configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders())); + source.registerCorsConfiguration(corsProps.getPath(), configuration); + } + return source; + } } diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AuthenticationRequestPostProcessor.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AuthenticationRequestPostProcessor.java index 8f3e1ae76..ab43b7a30 100644 --- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AuthenticationRequestPostProcessor.java +++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AuthenticationRequestPostProcessor.java @@ -1,17 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.mockmvc; @@ -22,11 +18,10 @@ import com.c4_soft.springaddons.security.oauth2.AuthenticationBuilder; /** - *Redundant code for {@link Authentication} MockMvc request post-processors + * Redundant code for {@link Authentication} MockMvc request post-processors * - * @author Jérôme Wacongne <ch4mp#64;c4-soft.com> - * - * @param concrete {@link Authentication} type to build and configure in test security context + * @author Jérôme Wacongne <ch4mp#64;c4-soft.com> + * @param concrete {@link Authentication} type to build and configure in test security context */ public interface AuthenticationRequestPostProcessor extends RequestPostProcessor, AuthenticationBuilder { @Override diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockAuthenticationRequestPostProcessor.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockAuthenticationRequestPostProcessor.java index d2c291a3b..4a9218025 100644 --- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockAuthenticationRequestPostProcessor.java +++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockAuthenticationRequestPostProcessor.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.mockmvc; diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockMvcSupport.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockMvcSupport.java index c07e7ba55..edc248f90 100644 --- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockMvcSupport.java +++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockMvcSupport.java @@ -1,14 +1,13 @@ /* * Copyright 2018 Jérôme Wacongne. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.mockmvc; @@ -44,633 +43,565 @@ *

* Highlighted features: *
    - *
  • auto sets "Accept" and "Content-Type" headers according - * {@code com.c4-soft.springaddons.test.web.default-media-type} and - * {@code com.c4-soft.springaddons.test.web.default-charset} to test properties, - * defaulted to {@code application/json} and - * {@code utf-8}
  • - *
  • serializes request body according to Content-type using registered - * message converters
  • - *
  • provides with shortcuts to issue requests in basic but most common cases - * (no fancy headers, cookies, etc): get, post, patch, put and - * delete methods
  • - *
  • wraps MockMvc {@link MockMvc#perform(RequestBuilder) perform} and exposes - * request builder helpers for advanced cases (when you need - * to further customize {@link MockHttpServletRequestBuilder} with cookies or - * additional headers for instance)
  • + *
  • auto sets "Accept" and "Content-Type" headers according {@code com.c4-soft.springaddons.test.web.default-media-type} and + * {@code com.c4-soft.springaddons.test.web.default-charset} to test properties, defaulted to {@code application/json} and {@code utf-8}
  • + *
  • serializes request body according to Content-type using registered message converters
  • + *
  • provides with shortcuts to issue requests in basic but most common cases (no fancy headers, cookies, etc): get, post, patch, put and delete methods
  • + *
  • wraps MockMvc {@link MockMvc#perform(RequestBuilder) perform} and exposes request builder helpers for advanced cases (when you need to further customize + * {@link MockHttpServletRequestBuilder} with cookies or additional headers for instance)
  • *
* * @author Jérôme Wacongne <ch4mp@c4-soft.com> */ @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class MockMvcSupport { - private final MockMvc mockMvc; - - private final SerializationHelper conv; - - private MediaType mediaType; - - private Charset charset; - - private boolean isSecure; - - private boolean isCsrf; - - private final List postProcessors; - - /** - * @param mockMvc wrapped Spring MVC testing helper - * @param serializationHelper used to serialize payloads to requested - * {@code Content-type} using Spring registered - * message converters - * @param mockMvcProperties default values for media-type, charset and https - * usage - */ - public MockMvcSupport( - MockMvc mockMvc, - SerializationHelper serializationHelper, - MockMvcProperties mockMvcProperties, - ServerProperties serverProperties, - SpringAddonsSecurityProperties addonsProperties) { - this.mockMvc = mockMvc; - this.conv = serializationHelper; - this.mediaType = MediaType.valueOf(mockMvcProperties.getDefaultMediaType()); - this.charset = Charset.forName(mockMvcProperties.getDefaultCharset()); - this.postProcessors = new ArrayList<>(); - this.isSecure = serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled(); - this.isCsrf = !addonsProperties.getCsrf().equals(Csrf.DISABLE); - } - - /** - * @param isSecure if true, requests are sent with https instead of http - * @return - */ - public MockMvcSupport setSecure(boolean isSecure) { - this.isSecure = isSecure; - return this; - } - - /** - * @param isCsrf should MockMvcRequests be issued with CSRF - * @return - */ - public MockMvcSupport setCsrf(boolean isCsrf) { - this.isCsrf = isCsrf; - return this; - } - - /** - * @param mediaType override configured default media-type - * @return - */ - public MockMvcSupport setMediaType(MediaType mediaType) { - this.mediaType = mediaType; - return this; - } - - /** - * @param charset override configured default charset - * @return - */ - public MockMvcSupport setCharset(Charset charset) { - this.charset = charset; - return this; - } - - /** - * Factory for a generic - * {@link org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder - * MockHttpServletRequestBuilder} - * with relevant "Accept" and "Content-Type" headers. You might prefer to use - * {@link #getRequestBuilder(MediaType, String, Object...) - * getRequestBuilder} or alike which go further with request pre-configuration - * or even {@link #get(MediaType, String, Object...) get}, - * {@link #post(Object, String, Object...)} and so on which issue simple - * requests in one step. - * - * @param accept should be non-empty when issuing response with body (GET, - * POST, OPTION), none otherwise - * @param charset char-set to be used for serialized payloads - * @param method whatever HTTP verb you need - * @param urlTemplate end-point to be requested - * @param uriVars end-point template placeholders values - * @return a request builder with minimal info you can tweak further: add - * headers, cookies, etc. - */ - public MockHttpServletRequestBuilder requestBuilder(Optional accept, Optional charset, - HttpMethod method, String urlTemplate, Object... uriVars) { - final var builder = request(method, urlTemplate, uriVars); - accept.ifPresent(builder::accept); - charset.ifPresent(c -> builder.characterEncoding(c.toString())); - builder.secure(isSecure); - if (isCsrf) { - builder.with(csrf()); - } - return builder; - } - - /** - * To be called with fully configured request builder (wraps MockMvc - * {@link org.springframework.test.web.servlet.MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder) - * perform}). - * - * @param requestBuilder fully configured request - * @return API answer to be tested - */ - public ResultActions perform(MockHttpServletRequestBuilder requestBuilder) { - postProcessors.forEach(requestBuilder::with); - try { - return mockMvc.perform(requestBuilder); - } catch (final Exception e) { - throw new MockMvcPerformException(e); - } - } - - /* GET */ - /** - * Factory providing with a request builder to issue a GET request (with Accept - * header). - * - * @param accept determines request Accept header (and response body - * format) - * @param urlTemplate API end-point to call - * @param uriVars values to feed URL template placeholders - * @return a request builder to be further configured (additional headers, - * cookies, etc.) - */ - public MockHttpServletRequestBuilder getRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) { - return requestBuilder(Optional.of(accept), Optional.empty(), HttpMethod.GET, urlTemplate, uriVars); - } - - /** - * Factory providing with a request builder to issue a GET request (with Accept - * header defaulted to what this helper is constructed with). - * - * @param urlTemplate API end-point to call - * @param uriVars values to feed URL template placeholders - * @return a request builder to be further configured (additional headers, - * cookies, etc.) - */ - public MockHttpServletRequestBuilder getRequestBuilder(String urlTemplate, Object... uriVars) { - return getRequestBuilder(mediaType, urlTemplate, uriVars); - } - - /** - * Shortcut to issue a GET request with minimal headers and submit it. - * - * @param accept determines request Accept header (and response body - * format) - * @param urlTemplate API endpoint to be requested - * @param uriVars values to replace endpoint placeholders with - * @return API response to test - */ - public ResultActions get(MediaType accept, String urlTemplate, Object... uriVars) { - return perform(getRequestBuilder(accept, urlTemplate, uriVars)); - } - - /** - * Shortcut to create a builder for a GET request with minimal headers and - * submit it (Accept header defaulted to what this helper was - * constructed with). - * - * @param urlTemplate API endpoint to be requested - * @param uriVars values to replace endpoint placeholders with - * @return API response to test - */ - public ResultActions get(String urlTemplate, Object... uriVars) { - return perform(getRequestBuilder(urlTemplate, uriVars)); - } - - /* POST */ - /** - * Factory for a POST request builder containing a body set to payload - * serialized in given media type (with adequate Content-type header). - * - * @param payload to be serialized as body in contentType format - * @param contentType format to be used for payload serialization - * @param charset char-set for request and response - * @param accept how should the response body be serialized (if any) - * @param urlTemplate API end-point to be requested - * @param uriVars values to replace end-point placeholders with - * @param payload type - * @return Request builder to further configure (cookies, additional headers, - * etc.) - */ - public MockHttpServletRequestBuilder postRequestBuilder(T payload, MediaType contentType, Charset charset, - MediaType accept, String urlTemplate, Object... uriVars) { - return feed(requestBuilder(Optional.of(accept), Optional.of(charset), HttpMethod.POST, urlTemplate, uriVars), - payload, contentType, charset); - } - - /** - * Factory for a POST request builder containing a body set to payload - * serialized in given media type (with adequate Content-type header). - * - * @param payload to be serialized as body in contentType format - * @param contentType format to be used for payload serialization - * @param accept how should the response body be serialized (if any) - * @param urlTemplate API end-point to be requested - * @param uriVars values to replace end-point placeholders with - * @param payload type - * @return Request builder to further configure (cookies, additional headers, - * etc.) - */ - public MockHttpServletRequestBuilder postRequestBuilder(T payload, MediaType contentType, MediaType accept, - String urlTemplate, Object... uriVars) { - return postRequestBuilder(payload, contentType, charset, accept, urlTemplate, uriVars); - } - - /** - * Factory for a POST request builder. Body is pre-set to payload. Both - * Content-type and Accept headers are set to default media-type. - * - * @param payload request body - * @param urlTemplate API end-point - * @param uriVars values ofr URL template placeholders - * @param payload type - * @return Request builder to further configure (cookies, additional headers, - * etc.) - */ - public MockHttpServletRequestBuilder postRequestBuilder(T payload, String urlTemplate, Object... uriVars) { - return postRequestBuilder(payload, mediaType, charset, mediaType, urlTemplate, uriVars); - } - - /** - * Shortcut to issue a POST request with provided payload as body, using given - * media-type for serialization (and Content-type header). - * - * @param payload POST request body - * @param contentType media type used to serialize payload and set Content-type - * header - * @param accept media-type to be set as Accept header (and response - * serialization) - * @param charset char-set for request and response - * @param urlTemplate API end-point to be called - * @param uriVars values ofr URL template placeholders - * @param payload type - * @return API response to test - */ - public ResultActions post(T payload, MediaType contentType, Charset charset, MediaType accept, - String urlTemplate, Object... uriVars) { - return perform(postRequestBuilder(payload, contentType, charset, accept, urlTemplate, uriVars)); - } - - /** - * Shortcut to issue a POST request with provided payload as body, using given - * media-type for serialization (and Content-type header). - * - * @param payload POST request body - * @param contentType media type used to serialize payload and set Content-type - * header - * @param accept media-type to be set as Accept header (and response - * serialization) - * @param urlTemplate API end-point to be called - * @param uriVars values ofr URL template placeholders - * @param payload type - * @return API response to test - */ - public ResultActions post(T payload, MediaType contentType, MediaType accept, String urlTemplate, - Object... uriVars) { - return perform(postRequestBuilder(payload, contentType, accept, urlTemplate, uriVars)); - } - - /** - * Shortcut to issue a POST request with provided payload as body, using default - * media-type for serialization (and Content-type header). - * - * @param payload POST request body - * @param urlTemplate API end-point to be called - * @param uriVars values ofr URL template placeholders - * @param payload type - * @return API response to test - */ - public ResultActions post(T payload, String urlTemplate, Object... uriVars) { - return perform(postRequestBuilder(payload, urlTemplate, uriVars)); - } - - /* PUT */ - /** - * Factory for a POST request builder containing a body. - * - * @param payload to be serialized as body in contentType format - * @param contentType format to be used for payload serialization - * @param charset char-set for request - * @param urlTemplate API end-point to be requested - * @param uriVars values to replace end-point placeholders with - * @param payload type - * @return Request builder to further configure (cookies, additional headers, - * etc.) - */ - public MockHttpServletRequestBuilder putRequestBuilder(T payload, MediaType contentType, Charset charset, - String urlTemplate, Object... uriVars) { - return feed(requestBuilder(Optional.empty(), Optional.of(charset), HttpMethod.PUT, urlTemplate, uriVars), - payload, contentType, charset); - } - - /** - * Factory for a POST request builder containing a body. - * - * @param payload to be serialized as body in contentType format - * @param contentType format to be used for payload serialization - * @param urlTemplate API end-point to be requested - * @param uriVars values to replace end-point placeholders with - * @param payload type - * @return Request builder to further configure (cookies, additional headers, - * etc.) - */ - public MockHttpServletRequestBuilder putRequestBuilder(T payload, MediaType contentType, String urlTemplate, - Object... uriVars) { - return putRequestBuilder(payload, contentType, charset, urlTemplate, uriVars); - } - - /** - * Factory for a POST request builder containing a body. Default media-type is - * used for payload serialization (and Content-type header). - * - * @param payload to be serialized as body in contentType format - * @param urlTemplate API end-point to be requested - * @param uriVars values to replace end-point placeholders with - * @param payload type - * @return Request builder to further configure (cookies, additional headers, - * etc.) - */ - public MockHttpServletRequestBuilder putRequestBuilder(T payload, String urlTemplate, Object... uriVars) { - return putRequestBuilder(payload, mediaType, charset, urlTemplate, uriVars); - } - - /** - * Shortcut to issue a PUT request. - * - * @param payload request body - * @param contentType payload serialization media-type - * @param charset char-set for request and response - * @param urlTemplate API end-point to request - * @param uriVars values to be used in end-point URL placehoders - * @param payload type - * @return API response to be tested - */ - public ResultActions put(T payload, MediaType contentType, String charset, String urlTemplate, - Object... uriVars) { - return perform(putRequestBuilder(payload, contentType, charset, urlTemplate, uriVars)); - } - - /** - * Shortcut to issue a PUT request. - * - * @param payload request body - * @param contentType payload serialization media-type - * @param urlTemplate API end-point to request - * @param uriVars values to be used in end-point URL placehoders - * @param payload type - * @return API response to be tested - */ - public ResultActions put(T payload, MediaType contentType, String urlTemplate, Object... uriVars) { - return perform(putRequestBuilder(payload, contentType, urlTemplate, uriVars)); - } - - /** - * Shortcut to issue a PUT request (with default media-type as Content-type). - * - * @param payload request body - * @param urlTemplate API end-point to request - * @param uriVars values to be used in end-point URL placehoders - * @param payload type - * @return API response to be tested - */ - public ResultActions put(T payload, String urlTemplate, Object... uriVars) { - return perform(putRequestBuilder(payload, urlTemplate, uriVars)); - } - - /* PATCH */ - /** - * Factory for a patch request builder (with Content-type already set). - * - * @param payload request body - * @param charset char-set to be used for serialized payloads - * @param contentType payload serialization format - * @param urlTemplate API end-point - * @param uriVars values for end-point placeholders - * @param payload type - * @return request builder to further configure (additional headers, cookies, - * etc.) - */ - public MockHttpServletRequestBuilder patchRequestBuilder(T payload, MediaType contentType, Charset charset, - String urlTemplate, Object... uriVars) { - return feed(requestBuilder(Optional.empty(), Optional.of(charset), HttpMethod.PATCH, urlTemplate, uriVars), - payload, contentType, charset); - } - - /** - * Factory for a patch request builder (with Content-type already set). - * - * @param payload request body - * @param contentType payload serialization format - * @param urlTemplate API end-point - * @param uriVars values for end-point placeholders - * @param payload type - * @return request builder to further configure (additional headers, cookies, - * etc.) - */ - public MockHttpServletRequestBuilder patchRequestBuilder(T payload, MediaType contentType, String urlTemplate, - Object... uriVars) { - return patchRequestBuilder(payload, contentType, charset, urlTemplate, uriVars); - } - - /** - * Factory for a patch request builder (with Content-type set to default - * media-type). - * - * @param payload request body - * @param urlTemplate API end-point - * @param uriVars values for end-point placeholders - * @param payload type - * @return request builder to further configure (additional headers, cookies, - * etc.) - */ - public MockHttpServletRequestBuilder patchRequestBuilder(T payload, String urlTemplate, Object... uriVars) { - return patchRequestBuilder(payload, mediaType, charset, urlTemplate, uriVars); - } - - /** - * Shortcut to issue a patch request with Content-type header and a body. - * - * @param payload request body - * @param contentType to be used for payload serialization - * @param charset to be used for payload serialization - * @param urlTemplate end-point URL - * @param uriVars values for end-point URL placeholders - * @param payload type - * @return API response to be tested - */ - public ResultActions patch(T payload, MediaType contentType, Charset charset, String urlTemplate, - Object... uriVars) { - return perform(patchRequestBuilder(payload, contentType, charset, urlTemplate, uriVars)); - } - - /** - * Shortcut to issue a patch request with Content-type header and a body. - * - * @param payload request body - * @param contentType to be used for payload serialization - * @param urlTemplate end-point URL - * @param uriVars values for end-point URL placeholders - * @param payload type - * @return API response to be tested - */ - public ResultActions patch(T payload, MediaType contentType, String urlTemplate, Object... uriVars) { - return perform(patchRequestBuilder(payload, contentType, urlTemplate, uriVars)); - } - - /** - * Shortcut to issue a patch request with Content-type header and a body (using - * default media-type). - * - * @param payload request body - * @param urlTemplate end-point URL - * @param uriVars values for end-point URL placeholders - * @param payload type - * @return API response to be tested - */ - public ResultActions patch(T payload, String urlTemplate, Object... uriVars) { - return perform(patchRequestBuilder(payload, urlTemplate, uriVars)); - } - - /* DELETE */ - /** - * Factory for a DELETE request builder. - * - * @param urlTemplate API end-point - * @param uriVars values for end-point URL placeholders - * @return request builder to further configure (additional headers, cookies, - * etc.) - */ - public MockHttpServletRequestBuilder deleteRequestBuilder(String urlTemplate, Object... uriVars) { - return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.DELETE, urlTemplate, uriVars); - } - - /** - * Shortcut to issue a DELETE request (no header) - * - * @param urlTemplate API end-point - * @param uriVars values for end-point URL placeholders - * @return API response to be tested - */ - public ResultActions delete(String urlTemplate, Object... uriVars) { - return perform(deleteRequestBuilder(urlTemplate, uriVars)); - } - - /* HEAD */ - /** - * Factory for a HEAD request builder. - * - * @param urlTemplate API end-point - * @param uriVars values for end-point URL placeholders - * @return request builder to further configure (additional headers, cookies, - * etc.) - */ - public MockHttpServletRequestBuilder headRequestBuilder(String urlTemplate, Object... uriVars) { - return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.HEAD, urlTemplate, uriVars); - } - - /** - * Shortcut to issue a HEAD request (no header) - * - * @param urlTemplate API end-point - * @param uriVars values for end-point URL placeholders - * @return API response to be tested - */ - public ResultActions head(String urlTemplate, Object... uriVars) { - return perform(headRequestBuilder(urlTemplate, uriVars)); - } - - /* OPTION */ - /** - * Factory for an OPTION request initialized with an Accept header. - * - * @param accept response body media-type - * @param urlTemplate API end-point - * @param uriVars values for end-point URL placeholders - * @return request builder to be further configured (additional headers, - * cookies, etc.) - */ - public MockHttpServletRequestBuilder optionRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) { - return requestBuilder(Optional.of(accept), Optional.empty(), HttpMethod.OPTIONS, urlTemplate, uriVars); - } - - /** - * Factory for an OPTION request initialized with an Accept header set to - * default media-type. - * - * @param urlTemplate API end-point - * @param uriVars values for end-point URL placeholders - * @return request builder to be further configured (additional headers, - * cookies, etc.) - */ - public MockHttpServletRequestBuilder optionRequestBuilder(String urlTemplate, Object... uriVars) { - return optionRequestBuilder(mediaType, urlTemplate, uriVars); - } - - /** - * Shortcut to issue an OPTION request with Accept header - * - * @param accept response body media-type - * @param urlTemplate API end-point - * @param uriVars values for end-point URL placeholders - * @return API response to be further configured - */ - public ResultActions option(MediaType accept, String urlTemplate, Object... uriVars) { - return perform(optionRequestBuilder(accept, urlTemplate, uriVars)); - } - - /** - * Shortcut to issue an OPTION request with default media-type as Accept header - * - * @param urlTemplate API end-point - * @param uriVars values for end-point URL placeholders - * @return API response to be further configured - */ - public ResultActions option(String urlTemplate, Object... uriVars) { - return perform(optionRequestBuilder(urlTemplate, uriVars)); - } - - /** - * Adds serialized payload to request content. Rather low-level, consider using - * this class - * {@link org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder - * MockHttpServletRequestBuilder} factories instead - * (getRequestBuilder, postRequestBuilder, etc.) - * - * @param request builder you want to set body to - * @param payload object to be serialized as body - * @param mediaType what format you want payload to be serialized to - * (corresponding HttpMessageConverter must be registered) - * @param charset char-set to be used for payload serialization - * @param payload type - * @return the request with provided payload as content - */ - public MockHttpServletRequestBuilder feed(MockHttpServletRequestBuilder request, T payload, MediaType mediaType, - Charset charset) { - if (payload == null) { - return request; - } - - final var msg = conv.outputMessage(payload, new MediaType(mediaType, charset)); - return request.headers(msg.headers).content(msg.out.toByteArray()); - } - - public DispatcherServlet getDispatcherServlet() { - return mockMvc.getDispatcherServlet(); - } - - /** - * @param postProcessor request post-processor to be added to the list of those - * applied before request is performed - * @return this {@link MockMvcSupport} - */ - public MockMvcSupport with(RequestPostProcessor postProcessor) { - Assert.notNull(postProcessor, "postProcessor is required"); - this.postProcessors.add(postProcessor); - return this; - } + private final MockMvc mockMvc; + + private final SerializationHelper conv; + + private MediaType mediaType; + + private Charset charset; + + private boolean isSecure; + + private boolean isCsrf; + + private final List postProcessors; + + /** + * @param mockMvc wrapped Spring MVC testing helper + * @param serializationHelper used to serialize payloads to requested {@code Content-type} using Spring registered message converters + * @param mockMvcProperties default values for media-type, charset and https usage + */ + public MockMvcSupport( + MockMvc mockMvc, + SerializationHelper serializationHelper, + MockMvcProperties mockMvcProperties, + ServerProperties serverProperties, + SpringAddonsSecurityProperties addonsProperties) { + this.mockMvc = mockMvc; + this.conv = serializationHelper; + this.mediaType = MediaType.valueOf(mockMvcProperties.getDefaultMediaType()); + this.charset = Charset.forName(mockMvcProperties.getDefaultCharset()); + this.postProcessors = new ArrayList<>(); + this.isSecure = serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled(); + this.isCsrf = !addonsProperties.getCsrf().equals(Csrf.DISABLE); + } + + /** + * @param isSecure if true, requests are sent with https instead of http + * @return + */ + public MockMvcSupport setSecure(boolean isSecure) { + this.isSecure = isSecure; + return this; + } + + /** + * @param isCsrf should MockMvcRequests be issued with CSRF + * @return + */ + public MockMvcSupport setCsrf(boolean isCsrf) { + this.isCsrf = isCsrf; + return this; + } + + /** + * @param mediaType override configured default media-type + * @return + */ + public MockMvcSupport setMediaType(MediaType mediaType) { + this.mediaType = mediaType; + return this; + } + + /** + * @param charset override configured default charset + * @return + */ + public MockMvcSupport setCharset(Charset charset) { + this.charset = charset; + return this; + } + + /** + * Factory for a generic {@link org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder MockHttpServletRequestBuilder} with relevant + * "Accept" and "Content-Type" headers. You might prefer to use {@link #getRequestBuilder(MediaType, String, Object...) getRequestBuilder} or alike which go + * further with request pre-configuration or even {@link #get(MediaType, String, Object...) get}, {@link #post(Object, String, Object...)} and so on which + * issue simple requests in one step. + * + * @param accept should be non-empty when issuing response with body (GET, POST, OPTION), none otherwise + * @param charset char-set to be used for serialized payloads + * @param method whatever HTTP verb you need + * @param urlTemplate end-point to be requested + * @param uriVars end-point template placeholders values + * @return a request builder with minimal info you can tweak further: add headers, cookies, etc. + */ + public + MockHttpServletRequestBuilder + requestBuilder(Optional accept, Optional charset, HttpMethod method, String urlTemplate, Object... uriVars) { + final var builder = request(method, urlTemplate, uriVars); + accept.ifPresent(builder::accept); + charset.ifPresent(c -> builder.characterEncoding(c.toString())); + builder.secure(isSecure); + if (isCsrf) { + builder.with(csrf()); + } + return builder; + } + + /** + * To be called with fully configured request builder (wraps MockMvc + * {@link org.springframework.test.web.servlet.MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder) perform}). + * + * @param requestBuilder fully configured request + * @return API answer to be tested + */ + public ResultActions perform(MockHttpServletRequestBuilder requestBuilder) { + postProcessors.forEach(requestBuilder::with); + try { + return mockMvc.perform(requestBuilder); + } catch (final Exception e) { + throw new MockMvcPerformException(e); + } + } + + /* GET */ + /** + * Factory providing with a request builder to issue a GET request (with Accept header). + * + * @param accept determines request Accept header (and response body format) + * @param urlTemplate API end-point to call + * @param uriVars values to feed URL template placeholders + * @return a request builder to be further configured (additional headers, cookies, etc.) + */ + public MockHttpServletRequestBuilder getRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) { + return requestBuilder(Optional.of(accept), Optional.empty(), HttpMethod.GET, urlTemplate, uriVars); + } + + /** + * Factory providing with a request builder to issue a GET request (with Accept header defaulted to what this helper is constructed with). + * + * @param urlTemplate API end-point to call + * @param uriVars values to feed URL template placeholders + * @return a request builder to be further configured (additional headers, cookies, etc.) + */ + public MockHttpServletRequestBuilder getRequestBuilder(String urlTemplate, Object... uriVars) { + return getRequestBuilder(mediaType, urlTemplate, uriVars); + } + + /** + * Shortcut to issue a GET request with minimal headers and submit it. + * + * @param accept determines request Accept header (and response body format) + * @param urlTemplate API endpoint to be requested + * @param uriVars values to replace endpoint placeholders with + * @return API response to test + */ + public ResultActions get(MediaType accept, String urlTemplate, Object... uriVars) { + return perform(getRequestBuilder(accept, urlTemplate, uriVars)); + } + + /** + * Shortcut to create a builder for a GET request with minimal headers and submit it (Accept header defaulted to what this helper was constructed with). + * + * @param urlTemplate API endpoint to be requested + * @param uriVars values to replace endpoint placeholders with + * @return API response to test + */ + public ResultActions get(String urlTemplate, Object... uriVars) { + return perform(getRequestBuilder(urlTemplate, uriVars)); + } + + /* POST */ + /** + * Factory for a POST request builder containing a body set to payload serialized in given media type (with adequate Content-type header). + * + * @param payload to be serialized as body in contentType format + * @param contentType format to be used for payload serialization + * @param charset char-set for request and response + * @param accept how should the response body be serialized (if any) + * @param urlTemplate API end-point to be requested + * @param uriVars values to replace end-point placeholders with + * @param payload type + * @return Request builder to further configure (cookies, additional headers, etc.) + */ + public < + T> + MockHttpServletRequestBuilder + postRequestBuilder(T payload, MediaType contentType, Charset charset, MediaType accept, String urlTemplate, Object... uriVars) { + return feed(requestBuilder(Optional.of(accept), Optional.of(charset), HttpMethod.POST, urlTemplate, uriVars), payload, contentType, charset); + } + + /** + * Factory for a POST request builder containing a body set to payload serialized in given media type (with adequate Content-type header). + * + * @param payload to be serialized as body in contentType format + * @param contentType format to be used for payload serialization + * @param accept how should the response body be serialized (if any) + * @param urlTemplate API end-point to be requested + * @param uriVars values to replace end-point placeholders with + * @param payload type + * @return Request builder to further configure (cookies, additional headers, etc.) + */ + public MockHttpServletRequestBuilder postRequestBuilder(T payload, MediaType contentType, MediaType accept, String urlTemplate, Object... uriVars) { + return postRequestBuilder(payload, contentType, charset, accept, urlTemplate, uriVars); + } + + /** + * Factory for a POST request builder. Body is pre-set to payload. Both Content-type and Accept headers are set to default media-type. + * + * @param payload request body + * @param urlTemplate API end-point + * @param uriVars values ofr URL template placeholders + * @param payload type + * @return Request builder to further configure (cookies, additional headers, etc.) + */ + public MockHttpServletRequestBuilder postRequestBuilder(T payload, String urlTemplate, Object... uriVars) { + return postRequestBuilder(payload, mediaType, charset, mediaType, urlTemplate, uriVars); + } + + /** + * Shortcut to issue a POST request with provided payload as body, using given media-type for serialization (and Content-type header). + * + * @param payload POST request body + * @param contentType media type used to serialize payload and set Content-type header + * @param accept media-type to be set as Accept header (and response serialization) + * @param charset char-set for request and response + * @param urlTemplate API end-point to be called + * @param uriVars values ofr URL template placeholders + * @param payload type + * @return API response to test + */ + public ResultActions post(T payload, MediaType contentType, Charset charset, MediaType accept, String urlTemplate, Object... uriVars) { + return perform(postRequestBuilder(payload, contentType, charset, accept, urlTemplate, uriVars)); + } + + /** + * Shortcut to issue a POST request with provided payload as body, using given media-type for serialization (and Content-type header). + * + * @param payload POST request body + * @param contentType media type used to serialize payload and set Content-type header + * @param accept media-type to be set as Accept header (and response serialization) + * @param urlTemplate API end-point to be called + * @param uriVars values ofr URL template placeholders + * @param payload type + * @return API response to test + */ + public ResultActions post(T payload, MediaType contentType, MediaType accept, String urlTemplate, Object... uriVars) { + return perform(postRequestBuilder(payload, contentType, accept, urlTemplate, uriVars)); + } + + /** + * Shortcut to issue a POST request with provided payload as body, using default media-type for serialization (and Content-type header). + * + * @param payload POST request body + * @param urlTemplate API end-point to be called + * @param uriVars values ofr URL template placeholders + * @param payload type + * @return API response to test + */ + public ResultActions post(T payload, String urlTemplate, Object... uriVars) { + return perform(postRequestBuilder(payload, urlTemplate, uriVars)); + } + + /* PUT */ + /** + * Factory for a POST request builder containing a body. + * + * @param payload to be serialized as body in contentType format + * @param contentType format to be used for payload serialization + * @param charset char-set for request + * @param urlTemplate API end-point to be requested + * @param uriVars values to replace end-point placeholders with + * @param payload type + * @return Request builder to further configure (cookies, additional headers, etc.) + */ + public MockHttpServletRequestBuilder putRequestBuilder(T payload, MediaType contentType, Charset charset, String urlTemplate, Object... uriVars) { + return feed(requestBuilder(Optional.empty(), Optional.of(charset), HttpMethod.PUT, urlTemplate, uriVars), payload, contentType, charset); + } + + /** + * Factory for a POST request builder containing a body. + * + * @param payload to be serialized as body in contentType format + * @param contentType format to be used for payload serialization + * @param urlTemplate API end-point to be requested + * @param uriVars values to replace end-point placeholders with + * @param payload type + * @return Request builder to further configure (cookies, additional headers, etc.) + */ + public MockHttpServletRequestBuilder putRequestBuilder(T payload, MediaType contentType, String urlTemplate, Object... uriVars) { + return putRequestBuilder(payload, contentType, charset, urlTemplate, uriVars); + } + + /** + * Factory for a POST request builder containing a body. Default media-type is used for payload serialization (and Content-type header). + * + * @param payload to be serialized as body in contentType format + * @param urlTemplate API end-point to be requested + * @param uriVars values to replace end-point placeholders with + * @param payload type + * @return Request builder to further configure (cookies, additional headers, etc.) + */ + public MockHttpServletRequestBuilder putRequestBuilder(T payload, String urlTemplate, Object... uriVars) { + return putRequestBuilder(payload, mediaType, charset, urlTemplate, uriVars); + } + + /** + * Shortcut to issue a PUT request. + * + * @param payload request body + * @param contentType payload serialization media-type + * @param charset char-set for request and response + * @param urlTemplate API end-point to request + * @param uriVars values to be used in end-point URL placehoders + * @param payload type + * @return API response to be tested + */ + public ResultActions put(T payload, MediaType contentType, String charset, String urlTemplate, Object... uriVars) { + return perform(putRequestBuilder(payload, contentType, charset, urlTemplate, uriVars)); + } + + /** + * Shortcut to issue a PUT request. + * + * @param payload request body + * @param contentType payload serialization media-type + * @param urlTemplate API end-point to request + * @param uriVars values to be used in end-point URL placehoders + * @param payload type + * @return API response to be tested + */ + public ResultActions put(T payload, MediaType contentType, String urlTemplate, Object... uriVars) { + return perform(putRequestBuilder(payload, contentType, urlTemplate, uriVars)); + } + + /** + * Shortcut to issue a PUT request (with default media-type as Content-type). + * + * @param payload request body + * @param urlTemplate API end-point to request + * @param uriVars values to be used in end-point URL placehoders + * @param payload type + * @return API response to be tested + */ + public ResultActions put(T payload, String urlTemplate, Object... uriVars) { + return perform(putRequestBuilder(payload, urlTemplate, uriVars)); + } + + /* PATCH */ + /** + * Factory for a patch request builder (with Content-type already set). + * + * @param payload request body + * @param charset char-set to be used for serialized payloads + * @param contentType payload serialization format + * @param urlTemplate API end-point + * @param uriVars values for end-point placeholders + * @param payload type + * @return request builder to further configure (additional headers, cookies, etc.) + */ + public MockHttpServletRequestBuilder patchRequestBuilder(T payload, MediaType contentType, Charset charset, String urlTemplate, Object... uriVars) { + return feed(requestBuilder(Optional.empty(), Optional.of(charset), HttpMethod.PATCH, urlTemplate, uriVars), payload, contentType, charset); + } + + /** + * Factory for a patch request builder (with Content-type already set). + * + * @param payload request body + * @param contentType payload serialization format + * @param urlTemplate API end-point + * @param uriVars values for end-point placeholders + * @param payload type + * @return request builder to further configure (additional headers, cookies, etc.) + */ + public MockHttpServletRequestBuilder patchRequestBuilder(T payload, MediaType contentType, String urlTemplate, Object... uriVars) { + return patchRequestBuilder(payload, contentType, charset, urlTemplate, uriVars); + } + + /** + * Factory for a patch request builder (with Content-type set to default media-type). + * + * @param payload request body + * @param urlTemplate API end-point + * @param uriVars values for end-point placeholders + * @param payload type + * @return request builder to further configure (additional headers, cookies, etc.) + */ + public MockHttpServletRequestBuilder patchRequestBuilder(T payload, String urlTemplate, Object... uriVars) { + return patchRequestBuilder(payload, mediaType, charset, urlTemplate, uriVars); + } + + /** + * Shortcut to issue a patch request with Content-type header and a body. + * + * @param payload request body + * @param contentType to be used for payload serialization + * @param charset to be used for payload serialization + * @param urlTemplate end-point URL + * @param uriVars values for end-point URL placeholders + * @param payload type + * @return API response to be tested + */ + public ResultActions patch(T payload, MediaType contentType, Charset charset, String urlTemplate, Object... uriVars) { + return perform(patchRequestBuilder(payload, contentType, charset, urlTemplate, uriVars)); + } + + /** + * Shortcut to issue a patch request with Content-type header and a body. + * + * @param payload request body + * @param contentType to be used for payload serialization + * @param urlTemplate end-point URL + * @param uriVars values for end-point URL placeholders + * @param payload type + * @return API response to be tested + */ + public ResultActions patch(T payload, MediaType contentType, String urlTemplate, Object... uriVars) { + return perform(patchRequestBuilder(payload, contentType, urlTemplate, uriVars)); + } + + /** + * Shortcut to issue a patch request with Content-type header and a body (using default media-type). + * + * @param payload request body + * @param urlTemplate end-point URL + * @param uriVars values for end-point URL placeholders + * @param payload type + * @return API response to be tested + */ + public ResultActions patch(T payload, String urlTemplate, Object... uriVars) { + return perform(patchRequestBuilder(payload, urlTemplate, uriVars)); + } + + /* DELETE */ + /** + * Factory for a DELETE request builder. + * + * @param urlTemplate API end-point + * @param uriVars values for end-point URL placeholders + * @return request builder to further configure (additional headers, cookies, etc.) + */ + public MockHttpServletRequestBuilder deleteRequestBuilder(String urlTemplate, Object... uriVars) { + return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.DELETE, urlTemplate, uriVars); + } + + /** + * Shortcut to issue a DELETE request (no header) + * + * @param urlTemplate API end-point + * @param uriVars values for end-point URL placeholders + * @return API response to be tested + */ + public ResultActions delete(String urlTemplate, Object... uriVars) { + return perform(deleteRequestBuilder(urlTemplate, uriVars)); + } + + /* HEAD */ + /** + * Factory for a HEAD request builder. + * + * @param urlTemplate API end-point + * @param uriVars values for end-point URL placeholders + * @return request builder to further configure (additional headers, cookies, etc.) + */ + public MockHttpServletRequestBuilder headRequestBuilder(String urlTemplate, Object... uriVars) { + return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.HEAD, urlTemplate, uriVars); + } + + /** + * Shortcut to issue a HEAD request (no header) + * + * @param urlTemplate API end-point + * @param uriVars values for end-point URL placeholders + * @return API response to be tested + */ + public ResultActions head(String urlTemplate, Object... uriVars) { + return perform(headRequestBuilder(urlTemplate, uriVars)); + } + + /* OPTION */ + /** + * Factory for an OPTION request initialized with an Accept header. + * + * @param accept response body media-type + * @param urlTemplate API end-point + * @param uriVars values for end-point URL placeholders + * @return request builder to be further configured (additional headers, cookies, etc.) + */ + public MockHttpServletRequestBuilder optionRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) { + return requestBuilder(Optional.of(accept), Optional.empty(), HttpMethod.OPTIONS, urlTemplate, uriVars); + } + + /** + * Factory for an OPTION request initialized with an Accept header set to default media-type. + * + * @param urlTemplate API end-point + * @param uriVars values for end-point URL placeholders + * @return request builder to be further configured (additional headers, cookies, etc.) + */ + public MockHttpServletRequestBuilder optionRequestBuilder(String urlTemplate, Object... uriVars) { + return optionRequestBuilder(mediaType, urlTemplate, uriVars); + } + + /** + * Shortcut to issue an OPTION request with Accept header + * + * @param accept response body media-type + * @param urlTemplate API end-point + * @param uriVars values for end-point URL placeholders + * @return API response to be further configured + */ + public ResultActions option(MediaType accept, String urlTemplate, Object... uriVars) { + return perform(optionRequestBuilder(accept, urlTemplate, uriVars)); + } + + /** + * Shortcut to issue an OPTION request with default media-type as Accept header + * + * @param urlTemplate API end-point + * @param uriVars values for end-point URL placeholders + * @return API response to be further configured + */ + public ResultActions option(String urlTemplate, Object... uriVars) { + return perform(optionRequestBuilder(urlTemplate, uriVars)); + } + + /** + * Adds serialized payload to request content. Rather low-level, consider using this class + * {@link org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder MockHttpServletRequestBuilder} factories instead (getRequestBuilder, + * postRequestBuilder, etc.) + * + * @param request builder you want to set body to + * @param payload object to be serialized as body + * @param mediaType what format you want payload to be serialized to (corresponding HttpMessageConverter must be registered) + * @param charset char-set to be used for payload serialization + * @param payload type + * @return the request with provided payload as content + */ + public MockHttpServletRequestBuilder feed(MockHttpServletRequestBuilder request, T payload, MediaType mediaType, Charset charset) { + if (payload == null) { + return request; + } + + final var msg = conv.outputMessage(payload, new MediaType(mediaType, charset)); + return request.headers(msg.headers).content(msg.out.toByteArray()); + } + + public DispatcherServlet getDispatcherServlet() { + return mockMvc.getDispatcherServlet(); + } + + /** + * @param postProcessor request post-processor to be added to the list of those applied before request is performed + * @return this {@link MockMvcSupport} + */ + public MockMvcSupport with(RequestPostProcessor postProcessor) { + Assert.notNull(postProcessor, "postProcessor is required"); + this.postProcessors.add(postProcessor); + return this; + } } \ No newline at end of file diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/OidcIdAuthenticationTokenRequestPostProcessor.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/OidcIdAuthenticationTokenRequestPostProcessor.java index 006be8cc4..f6857150b 100644 --- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/OidcIdAuthenticationTokenRequestPostProcessor.java +++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/OidcIdAuthenticationTokenRequestPostProcessor.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may - * obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.mockmvc; diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/ServletUnitTestingSupport.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/ServletUnitTestingSupport.java index 33ee02dc6..91f42aa90 100644 --- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/ServletUnitTestingSupport.java +++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/ServletUnitTestingSupport.java @@ -1,14 +1,13 @@ /* * Copyright 2019 Jérôme Wacongne * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.c4_soft.springaddons.security.oauth2.test.mockmvc; @@ -18,10 +17,8 @@ import org.springframework.test.web.servlet.MockMvc; /** - * Helper class for servlet {@code @Controller} unit-tests using security flow - * API (useless if using annotations).
- * Might be used either as a parent class (easier) or collaborator (requires - * some test configuration).
+ * Helper class for servlet {@code @Controller} unit-tests using security flow API (useless if using annotations).
+ * Might be used either as a parent class (easier) or collaborator (requires some test configuration).
* * @author Jérôme Wacongne <ch4mp@c4-soft.com> */ @@ -32,8 +29,7 @@ public class ServletUnitTestingSupport { protected BeanFactory beanFactory; /** - * @return ready to use {@link MockMvcSupport}, a {@link MockMvc} wrapper - * providing helpers for common REST requests + * @return ready to use {@link MockMvcSupport}, a {@link MockMvc} wrapper providing helpers for common REST requests */ public MockMvcSupport mockMvc() { return beanFactory.getBean(MockMvcSupport.class);