diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index ecbc3d534..4a8bcb6d2 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -10,3 +10,4 @@ ** xref:guides/how-to-pkce.adoc[] ** xref:guides/how-to-social-login.adoc[] ** xref:guides/how-to-userinfo.adoc[] +** xref:guides/how-to-custom-claims-authorities.adoc[] diff --git a/docs/modules/ROOT/pages/guides/how-to-custom-claims-authorities.adoc b/docs/modules/ROOT/pages/guides/how-to-custom-claims-authorities.adoc new file mode 100644 index 000000000..0792906f1 --- /dev/null +++ b/docs/modules/ROOT/pages/guides/how-to-custom-claims-authorities.adoc @@ -0,0 +1,54 @@ +[[how-to-custom-claims-authorities]] += How-to: Add authorities as custom claims in JWT-based access tokens +:index-link: ../how-to.html +:docs-dir: .. + +This guide demonstrates how to add resource owner authorities to a JWT access token. +The term "authorities" may represent varying forms such as roles, permissions, or groups of the resource owner. + +To make resource owners' authorities available to the resource server, we add custom claims to an access token issued by Spring Authorization Server. +The client using the issued token to access protected resources will then have information about the resource owner’s level of access, among other potential uses and benefits. + +* xref:guides/how-to-custom-claims-authorities.adoc#custom-claims[Add custom claims to JWT access tokens] +* xref:guides/how-to-custom-claims-authorities.adoc#custom-claims-authorities[Add authorities as custom claims to JWT access tokens] + +[[custom-claims]] +== Add custom claims to JWT access tokens + +You may add your own custom claims to an access token using `OAuth2TokenCustomizer` bean. +Please note that this bean may only be defined once, and so care must be taken care of to ensure that you are customizing the appropriate token type — an access token in this case. +If you are interested in customizing the identity token, see xref:guides/how-to-userinfo.adoc#customize-user-info-mapper[the UserInfo mapper guide for more information]. + +The following is an example of adding custom claims to an access token — in other words, every access token that is issued by the authorization server will have the custom claims populated. + +[[sample.customClaims]] +[source,java] +---- +include::{examples-dir}/main/java/sample/customClaims/CustomClaimsConfiguration.java[] +---- + +[[custom-claims-authorities]] +== Add authorities as custom claims to JWT access tokens + +To add authorities of the resource owner to a JWT-based access token, we can refer to the custom claim mapping method above +and populate custom claims with the authorities of the `Principal`. + +We define a sample user with a mix of authorities for demonstration purposes, and populate custom claims in an access token +with those authorities. + +[[sample.customClaims.authorities]] +[source,java] +---- +include::{examples-dir}/main/java/sample/customClaims/authorities/CustomClaimsWithAuthoritiesConfiguration.java[] +---- + +<1> Define a sample user `user1` with an in-memory user details service. +<2> Define a few roles for `user1`. +<3> Define `OAuth2TokenCustomizer` `@Bean` that allows for customizing JWT token claims. +<4> Check whether the JWT token is an access token. +<5> From the encoding context, modify the claims of the access token. +<6> Extract user roles from the `Principal` object. The role information for internal users is stored as a string prefixed with `ROLE_`, so we strip the prefix here. +<7> Set custom claim `roles` to the set of roles collected from the previous step. + +As a result of this customization, authorities information about the user will be included as a custom claim within the +access token. diff --git a/docs/modules/ROOT/pages/how-to.adoc b/docs/modules/ROOT/pages/how-to.adoc index cee5e4621..933442fd4 100644 --- a/docs/modules/ROOT/pages/how-to.adoc +++ b/docs/modules/ROOT/pages/how-to.adoc @@ -11,3 +11,4 @@ * xref:guides/how-to-ext-grant-type.adoc[Implement an Extension Authorization Grant Type] * xref:guides/how-to-userinfo.adoc[Customize the OpenID Connect 1.0 UserInfo response] * xref:guides/how-to-jpa.adoc[Implement core services with JPA] +* xref:guides/how-to-custom-claims-authorities.adoc[Add authorities as custom claims in JWT-based access tokens] diff --git a/docs/src/main/java/sample/customClaims/CustomClaimsConfiguration.java b/docs/src/main/java/sample/customClaims/CustomClaimsConfiguration.java new file mode 100644 index 000000000..0e121bb8e --- /dev/null +++ b/docs/src/main/java/sample/customClaims/CustomClaimsConfiguration.java @@ -0,0 +1,22 @@ +package sample.customClaims; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; +import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; + +@Configuration +public class CustomClaimsConfiguration { + @Bean + 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"); + }); + } + }; + } +} diff --git a/docs/src/main/java/sample/customClaims/authorities/CustomClaimsWithAuthoritiesConfiguration.java b/docs/src/main/java/sample/customClaims/authorities/CustomClaimsWithAuthoritiesConfiguration.java new file mode 100644 index 000000000..8361eda7f --- /dev/null +++ b/docs/src/main/java/sample/customClaims/authorities/CustomClaimsWithAuthoritiesConfiguration.java @@ -0,0 +1,44 @@ +package sample.customClaims.authorities; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.authority.AuthorityUtils; +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.server.authorization.OAuth2TokenType; +import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +@Configuration +public class CustomClaimsWithAuthoritiesConfiguration { + @Bean + public UserDetailsService users() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("user1") // <1> + .password("password") + .roles(new String[] { "user", "admin" }) // <2> + .build(); + return new InMemoryUserDetailsManager(user); + } + + @Bean + public OAuth2TokenCustomizer jwtTokenCustomizer() { // <3> + return (context) -> { + if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { // <4> + context.getClaims().claims((claims) -> { // <5> + Set roles = AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities()) + .stream() + .map(c -> c.replaceFirst("^ROLE_", "")) + .collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet)); // <6> + claims.put("roles", roles); // <7> + }); + } + }; + } +}