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 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 + 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..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,6 +42,7 @@ 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) .redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc") 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()))); + }; + } + } 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..b506d0cb5 --- /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