From 175155ba6a4059b6303a52eaa7268644f636da3c Mon Sep 17 00:00:00 2001 From: h_sharifi Date: Wed, 3 Jan 2024 13:56:31 +0330 Subject: [PATCH 1/7] BAEL-7275: add controller for claims test --- .../java/com/baeldung/web/ArticlesController.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/oauth-authorization-server/client-server/src/main/java/com/baeldung/web/ArticlesController.java b/oauth-authorization-server/client-server/src/main/java/com/baeldung/web/ArticlesController.java index 269225189..70db60337 100644 --- a/oauth-authorization-server/client-server/src/main/java/com/baeldung/web/ArticlesController.java +++ b/oauth-authorization-server/client-server/src/main/java/com/baeldung/web/ArticlesController.java @@ -1,5 +1,7 @@ package com.baeldung.web; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; @@ -7,7 +9,8 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.client.WebClient; -import javax.servlet.http.HttpServletRequest; +import java.text.ParseException; +import java.util.Map; import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient; @@ -29,4 +32,14 @@ public String[] getArticles( .bodyToMono(String[].class) .block(); } + + @GetMapping(value = "/claims") + public String getClaims( + @RegisteredOAuth2AuthorizedClient("articles-client-authorization-code") OAuth2AuthorizedClient authorizedClient + ) throws ParseException { + SignedJWT signedJWT = SignedJWT.parse(authorizedClient.getAccessToken().getTokenValue()); + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + Map claims = claimsSet.getClaims(); + return claims.get("authorities").toString(); + } } \ No newline at end of file From 6212a2d352b770b12f5f85c7915f25ceb85166a7 Mon Sep 17 00:00:00 2001 From: h_sharifi Date: Wed, 3 Jan 2024 13:57:02 +0330 Subject: [PATCH 2/7] BAEL-7275: add CLIENT_CREDENTIALS grant type --- .../main/java/com/baeldung/config/AuthorizationServerConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/AuthorizationServerConfig.java b/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/AuthorizationServerConfig.java index b27e589e1..234521a17 100644 --- a/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/AuthorizationServerConfig.java +++ b/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/AuthorizationServerConfig.java @@ -44,6 +44,7 @@ public RegisteredClientRepository registeredClientRepository() { .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc") .redirectUri("http://127.0.0.1:8080/authorized") .scope(OidcScopes.OPENID) From add9b82f73cf216183ce5a5ce88225dd446717cb Mon Sep 17 00:00:00 2001 From: h_sharifi Date: Wed, 3 Jan 2024 13:57:45 +0330 Subject: [PATCH 3/7] BAEL-7275: add token customizer for adding claims --- .../config/DefaultSecurityConfig.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/DefaultSecurityConfig.java b/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/DefaultSecurityConfig.java index 2ab6d4360..52a464520 100644 --- a/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/DefaultSecurityConfig.java +++ b/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/DefaultSecurityConfig.java @@ -1,14 +1,23 @@ package com.baeldung.config; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.core.OAuth2TokenType; +import org.springframework.security.oauth2.server.authorization.JwtEncodingContext; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; +import java.util.Collection; +import java.util.stream.Collectors; + import static org.springframework.security.config.Customizer.withDefaults; @EnableWebSecurity @@ -33,4 +42,28 @@ UserDetailsService users() { return new InMemoryUserDetailsManager(user); } + @Bean + @Profile("basic-claim") + public OAuth2TokenCustomizer jwtTokenCustomizer() { + return (context) -> { + if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { + context.getClaims().claims((claims) -> { + claims.put("claim-1", "value-1"); + claims.put("claim-2", "value-2"); + }); + } + }; + } + + @Bean + @Profile("authority-claim") + public OAuth2TokenCustomizer tokenCustomizer(@Qualifier("users") UserDetailsService userDetailsService) { + return (context) -> { + UserDetails userDetails = userDetailsService.loadUserByUsername(context.getPrincipal().getName()); + Collection authorities = userDetails.getAuthorities(); + context.getClaims().claims(claims -> + claims.put("authorities", authorities.stream().map(authority -> authority.getAuthority()).collect(Collectors.toList()))); + }; + } + } From c422e3275c33bbcf7de33b06b67c4b673e2f24c2 Mon Sep 17 00:00:00 2001 From: h_sharifi Date: Wed, 3 Jan 2024 13:58:10 +0330 Subject: [PATCH 4/7] BAEL-7275: add spring-boot-starter-test dependency --- .../spring-authorization-server/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/oauth-authorization-server/spring-authorization-server/pom.xml b/oauth-authorization-server/spring-authorization-server/pom.xml index 24cd20a28..5f22ca701 100644 --- a/oauth-authorization-server/spring-authorization-server/pom.xml +++ b/oauth-authorization-server/spring-authorization-server/pom.xml @@ -29,6 +29,10 @@ spring-security-oauth2-authorization-server ${spring-auth-server.version} + + org.springframework.boot + spring-boot-starter-test + From 9d2453f7779e91c6e55fd0a13c5d9c977e8ce8d2 Mon Sep 17 00:00:00 2001 From: h_sharifi Date: Wed, 3 Jan 2024 13:59:25 +0330 Subject: [PATCH 5/7] BAEL-7275: add test case for CLIENT_CREDENTIALS grant type --- .../java/CustomClaimsConfigurationTest.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 oauth-authorization-server/spring-authorization-server/src/test/java/CustomClaimsConfigurationTest.java diff --git a/oauth-authorization-server/spring-authorization-server/src/test/java/CustomClaimsConfigurationTest.java b/oauth-authorization-server/spring-authorization-server/src/test/java/CustomClaimsConfigurationTest.java new file mode 100644 index 000000000..e7e0b389d --- /dev/null +++ b/oauth-authorization-server/spring-authorization-server/src/test/java/CustomClaimsConfigurationTest.java @@ -0,0 +1,72 @@ +import com.baeldung.OAuth2AuthorizationServerApplication; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.text.ParseException; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = OAuth2AuthorizationServerApplication.class) +@ActiveProfiles(value = "basic-claim") +public class CustomClaimsConfigurationTest { + + private static final String ISSUER_URL = "http://localhost:"; + private static final String USERNAME = "articles-client"; + private static final String PASSWORD = "secret"; + private static final String GRANT_TYPE = "client_credentials"; + + @Autowired + private TestRestTemplate restTemplate; + + @LocalServerPort + private int serverPort; + + @Test + public void givenAccessToken_whenGetCustomClaim_thenSuccess() throws ParseException { + String url = ISSUER_URL + serverPort + "/oauth2/token"; + HttpHeaders headers = new HttpHeaders(); + headers.setBasicAuth(USERNAME, PASSWORD); + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", GRANT_TYPE); + HttpEntity> requestEntity = new HttpEntity<>(params, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, TokenDTO.class); + + SignedJWT signedJWT = SignedJWT.parse(response.getBody().getAccessToken()); + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + Map claims = claimsSet.getClaims(); + + assertEquals("value-1", claims.get("claim-1")); + assertEquals("value-2", claims.get("claim-2")); + } + + static class TokenDTO { + @JsonProperty("access_token") + private String accessToken; + @JsonProperty("token_type") + private String tokenType; + @JsonProperty("expires_in") + private String expiresIn; + @JsonProperty("scope") + private String scope; + + public String getAccessToken() { + return accessToken; + } + } + +} \ No newline at end of file From ef8dc4974e6c5282d9acb2187ece56572ad9e6ba Mon Sep 17 00:00:00 2001 From: h_sharifi Date: Sat, 6 Jan 2024 13:41:14 +0330 Subject: [PATCH 6/7] BAEL-7275: remove extra space --- .../src/test/java/CustomClaimsConfigurationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth-authorization-server/spring-authorization-server/src/test/java/CustomClaimsConfigurationTest.java b/oauth-authorization-server/spring-authorization-server/src/test/java/CustomClaimsConfigurationTest.java index e7e0b389d..b506d0cb5 100644 --- a/oauth-authorization-server/spring-authorization-server/src/test/java/CustomClaimsConfigurationTest.java +++ b/oauth-authorization-server/spring-authorization-server/src/test/java/CustomClaimsConfigurationTest.java @@ -38,7 +38,7 @@ public class CustomClaimsConfigurationTest { @Test public void givenAccessToken_whenGetCustomClaim_thenSuccess() throws ParseException { - String url = ISSUER_URL + serverPort + "/oauth2/token"; + String url = ISSUER_URL + serverPort + "/oauth2/token"; HttpHeaders headers = new HttpHeaders(); headers.setBasicAuth(USERNAME, PASSWORD); MultiValueMap params = new LinkedMultiValueMap<>(); From aa549080007bc1e110dfb224def9d29a5c7d25ab Mon Sep 17 00:00:00 2001 From: h_sharifi Date: Sun, 7 Jan 2024 18:09:07 +0330 Subject: [PATCH 7/7] BAEL-7275: move client_credential in the first step --- .../java/com/baeldung/config/AuthorizationServerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/AuthorizationServerConfig.java b/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/AuthorizationServerConfig.java index 234521a17..0630afe86 100644 --- a/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/AuthorizationServerConfig.java +++ b/oauth-authorization-server/spring-authorization-server/src/main/java/com/baeldung/config/AuthorizationServerConfig.java @@ -42,9 +42,9 @@ public RegisteredClientRepository registeredClientRepository() { .clientId("articles-client") .clientSecret("{noop}secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc") .redirectUri("http://127.0.0.1:8080/authorized") .scope(OidcScopes.OPENID)