如何生成带有一些自定义声明的JWT访问令牌? [英] How to generate a JWT access token with some custom claims in it?

查看:109
本文介绍了如何生成带有一些自定义声明的JWT访问令牌?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试让我的授权服务器生成一个JWT访问令牌,其中包含一些自定义声明.

这是授权服务器/auth/token端点返回的Bearer令牌的样子:51aea31c-6b57-4c80-9d19-a72e15cb2bb7

我发现此令牌有点短,它只是JWT令牌,并且包含我的自定义声明...

并且在随后对资源服务器的请求中使用它时,它会报错:Cannot convert access token to JSON

我正在使用以下依赖项:

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
    <relativePath/>
  </parent>

  <dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.2.RELEASE</version>
  </dependency>

授权服务器的配置方式如下:

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  endpoints
  .tokenServices(defaultTokenServices())
  .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
  .accessTokenConverter(jwtAccessTokenConverter())
  .userDetailsService(userDetailsService);

  endpoints
  .pathMapping("/oauth/token", RESTConstants.SLASH + DomainConstants.AUTH + RESTConstants.SLASH + DomainConstants.TOKEN);

  TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
  tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));
  endpoints
  .tokenStore(tokenStore())
  .tokenEnhancer(tokenEnhancerChain)
  .authenticationManager(authenticationManager);
}

@Bean
@Primary
public DefaultTokenServices defaultTokenServices() {
  DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
  defaultTokenServices.setTokenStore(tokenStore());
  defaultTokenServices.setSupportRefreshToken(true);
  return defaultTokenServices;
}

@Bean
public TokenStore tokenStore() {
  return new JwtTokenStore(jwtAccessTokenConverter());
}

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
  JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
  jwtAccessTokenConverter.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource(jwtProperties.getSslKeystoreFilename()), jwtProperties.getSslKeystorePassword().toCharArray()).getKeyPair(jwtProperties.getSslKeyPair()));
return jwtAccessTokenConverter;
}

@Bean
public TokenEnhancer tokenEnhancer() {
  return new CustomTokenEnhancer();
}

它正在使用类:

class CustomTokenEnhancer implements TokenEnhancer {

  @Autowired
  private TokenAuthenticationService tokenAuthenticationService;

  // Add user information to the token
  @Override
  public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
    User user = (User) authentication.getPrincipal();
    Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
    info.put(CommonConstants.JWT_CLAIM_USER_EMAIL, user.getEmail().getEmailAddress());
    info.put(CommonConstants.JWT_CLAIM_USER_FULLNAME, user.getFirstname() + " " + user.getLastname());
    info.put("scopes", authentication.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
    info.put("organization", authentication.getName());
    DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
    customAccessToken.setAdditionalInformation(info);
    customAccessToken.setExpiration(tokenAuthenticationService.getExpirationDate());
    return customAccessToken;
  }

}

我也有课:

@Configuration
class CustomOauth2RequestFactory extends DefaultOAuth2RequestFactory {

  @Autowired
  private TokenStore tokenStore;

  @Autowired
  private UserDetailsService userDetailsService;

  public CustomOauth2RequestFactory(ClientDetailsService clientDetailsService) {
    super(clientDetailsService);
  }

  @Override
  public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) {
    if (requestParameters.get("grant_type").equals("refresh_token")) {
      OAuth2Authentication authentication = tokenStore
          .readAuthenticationForRefreshToken(tokenStore.readRefreshToken(requestParameters.get("refresh_token")));
      SecurityContextHolder.getContext()
          .setAuthentication(new UsernamePasswordAuthenticationToken(authentication.getName(), null,
              userDetailsService.loadUserByUsername(authentication.getName()).getAuthorities()));
    }
    return super.createTokenRequest(requestParameters, authenticatedClient);
  }

}

更新:我还尝试了指定自定义声明的另一种方法:

@Component
class CustomAccessTokenConverter extends JwtAccessTokenConverter {

    @Autowired
  private TokenAuthenticationService tokenAuthenticationService;

  @Override
  public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
    OAuth2Authentication authentication = super.extractAuthentication(claims);
    authentication.setDetails(claims);
    return authentication;
  }

  @Override
  public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
    User user = (User) authentication.getPrincipal();
    Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
    info.put(CommonConstants.JWT_CLAIM_USER_EMAIL, user.getEmail().getEmailAddress());
    info.put(CommonConstants.JWT_CLAIM_USER_FULLNAME, user.getFirstname() + " " + user.getLastname());
    info.put("scopes", authentication.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
    info.put("organization", authentication.getName());
    DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
    customAccessToken.setAdditionalInformation(info);
    customAccessToken.setExpiration(tokenAuthenticationService.getExpirationDate());
    return super.enhance(customAccessToken, authentication);
  }

}

像这样被称呼:

endpoints
.tokenStore(tokenStore())
.tokenEnhancer(jwtAccessTokenConverter())
.accessTokenConverter(jwtAccessTokenConverter())

但是它什么也没改变,并且错误仍然相同.

与调试器一起运行时,这两个增强器替代都不会被调用.

解决方案

要使用OAuth2,JWT和其他声明构建Spring Boot服务器,我们应该:

1)向项目添加依赖项:

 <dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>
 

2)添加Web安全配置(以发布AuthenticationManager bean-它将在下一步中使用),例如:

 @EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    @Autowired
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(username -> AuthUser.with()
                .username(username)
                .password("{noop}" + username)
                .email(username + "@mail.com")
                .authority(AuthUser.Role.values()[ThreadLocalRandom.current().nextInt(2)])
                .build()
        );
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}
 

这里实现了一个简单的UserDetailsService以用于测试目的.它与以下简单的用户"对象和实现GrantedAuthority接口的Role枚举一起使用. AuthUser仅具有一个附加属性email,它将作为声明添加到JWT令牌中.

 @Value
@EqualsAndHashCode(callSuper = false)
public class AuthUser extends User {

    private String email;

    @Builder(builderMethodName = "with")
    public AuthUser(final String username, final String password, @Singular final Collection<? extends GrantedAuthority> authorities, final String email) {
        super(username, password, authorities);
        this.email = email;
    }

    public enum Role implements GrantedAuthority {
        USER, ADMIN;

        @Override
        public String getAuthority() {
            return this.name();
        }
    }
}
 

3)配置授权服务器并启用资源服务器:

 @Configuration
@EnableAuthorizationServer
@EnableResourceServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    public static final String TOKEN_KEY = "abracadabra";

    private final AuthenticationManager authenticationManager;

    public AuthServerConfig(final AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clientDetailsService) throws Exception {
        clientDetailsService.inMemory()
                .withClient("client")
                .secret("{noop}")
                .scopes("*")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(60 * 2) // 2 min
                .refreshTokenValiditySeconds(60 * 60); // 60 min
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain chain = new TokenEnhancerChain();
        chain.setTokenEnhancers(List.of(tokenEnhancer(), tokenConverter()));
        endpoints
                .tokenStore(tokenStore())
                .reuseRefreshTokens(false)
                .tokenEnhancer(chain)
                .authenticationManager(authenticationManager);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(tokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter tokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(TOKEN_KEY);
        converter.setAccessTokenConverter(authExtractor());
        return converter;
    }

    private TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
                if (authentication != null && authentication.getPrincipal() instanceof AuthUser) {
                    AuthUser authUser = (AuthUser) authentication.getPrincipal();
                    Map<String, Object> additionalInfo = new HashMap<>();
                    additionalInfo.put("user_email", authUser.getEmail());
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
                }
                return accessToken;
            };
    }

    @Bean
    public DefaultAccessTokenConverter authExtractor() {
        return new DefaultAccessTokenConverter() {
            @Override
            public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
                OAuth2Authentication authentication = super.extractAuthentication(claims);
                authentication.setDetails(claims);
                return authentication;
            }
        };
    }
}
 

此处实现了一个简单的ClientDetailsService.它仅包含一个客户端,该客户端具有客户端"名称,空白密码以及授予的类型密码"和"refresh_token".它使我们可以创建一个新的访问令牌并刷新它. (要使用多种类型的客户端或在其他情况下,您必须实现更复杂,并且可能是持久性的ClientDetailsService变体.)

授权端点使用TokenEnhancerChain配置,其中包含tokenEnhancertokenConverter.按此顺序添加它们很重要.第一个使用其他声明(在本例中为用户电子邮件)增强了访问令牌.第二个创建一个JWT令牌.通过简单的JwtTokenStore,我们的TokenEnhancerChainauthenticationManager设置的endpoints.

JwtTokenStore的注意事项-如果您决定实施商店的持久变体,则可以找到更多信息 curl -i \ --user client: \ -H "Content-Type: application/x-www-form-urlencoded" \ -X POST \ -d "grant_type=password&username=user&password=user&scope=*" \ http://localhost:8080/oauth/token

响应:

 {
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2VtYWlsIjoidXNlckBtYWlsLmNvbSIsInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyIqIl0sImV4cCI6MTU0Nzc2NDIzOCwiYXV0aG9yaXRpZXMiOlsiQURNSU4iXSwianRpIjoiYzk1YzkzYTAtMThmOC00OGZjLWEzZGUtNWVmY2Y1YWIxMGE5IiwiY2xpZW50X2lkIjoiY2xpZW50In0.RWSGMC0w8tNafT28i2GLTnPnIiXfAlCdydEsNNZK-Lw",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2VtYWlsIjoidXNlckBtYWlsLmNvbSIsInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyIqIl0sImF0aSI6ImM5NWM5M2EwLTE4ZjgtNDhmYy1hM2RlLTVlZmNmNWFiMTBhOSIsImV4cCI6MTU0Nzc2NzcxOCwiYXV0aG9yaXRpZXMiOlsiQURNSU4iXSwianRpIjoiZDRhNGU2ZjUtNDY2Mi00NGZkLWI0ZDgtZWE5OWRkMDJkYWI2IiwiY2xpZW50X2lkIjoiY2xpZW50In0.m7XvxwuPiTnPaQXAptLfi3CxN3imfQCVKyjmMCIPAVM",
    "expires_in": 119,
    "scope": "*",
    "user_email": "user@mail.com",
    "jti": "c95c93a0-18f8-48fc-a3de-5efcf5ab10a9"
}
 

如果我们在 https://jwt.io/上解码此访问令牌,我们可以看到它包含user_email声明:

 {
  "user_email": "user@mail.com",
  "user_name": "user",
  "scope": [
    "*"
  ],
  "exp": 1547764238,
  "authorities": [
    "ADMIN"
  ],
  "jti": "c95c93a0-18f8-48fc-a3de-5efcf5ab10a9",
  "client_id": "client"
}
 

要从传入请求的JWT令牌中提取此类声明(和其他数据),我们可以使用以下方法:

 @RestController
public class DemoController {

    @GetMapping("/demo")
    public Map demo(OAuth2Authentication auth) {

        var details = (OAuth2AuthenticationDetails) auth.getDetails();
        //noinspection unchecked
        var decodedDetails = (Map<String, Object>) details.getDecodedDetails();

        return Map.of(
                "name", decodedDetails.get("user_name"),
                "email", decodedDetails.get("user_email"),
                "roles", decodedDetails.get("authorities")
        );
    }
}
 

我的工作演示: sb-jwt-oauth-demo

相关信息:

I'm trying to have my authorization server generate a JWT access token with some custom claims in it.

Here is what the Bearer token returned by the authorization server /auth/token endpoint looks like: 51aea31c-6b57-4c80-9d19-a72e15cb2bb7

I find this token a bit short to be a JWT token and to contain my custom claims...

And when using it in subsequent requests to the resource server, it complains with the error: Cannot convert access token to JSON

I'm using the following dependencies:

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
    <relativePath/>
  </parent>

  <dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.2.RELEASE</version>
  </dependency>

The authorization server is configured this way:

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  endpoints
  .tokenServices(defaultTokenServices())
  .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
  .accessTokenConverter(jwtAccessTokenConverter())
  .userDetailsService(userDetailsService);

  endpoints
  .pathMapping("/oauth/token", RESTConstants.SLASH + DomainConstants.AUTH + RESTConstants.SLASH + DomainConstants.TOKEN);

  TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
  tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));
  endpoints
  .tokenStore(tokenStore())
  .tokenEnhancer(tokenEnhancerChain)
  .authenticationManager(authenticationManager);
}

@Bean
@Primary
public DefaultTokenServices defaultTokenServices() {
  DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
  defaultTokenServices.setTokenStore(tokenStore());
  defaultTokenServices.setSupportRefreshToken(true);
  return defaultTokenServices;
}

@Bean
public TokenStore tokenStore() {
  return new JwtTokenStore(jwtAccessTokenConverter());
}

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
  JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
  jwtAccessTokenConverter.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource(jwtProperties.getSslKeystoreFilename()), jwtProperties.getSslKeystorePassword().toCharArray()).getKeyPair(jwtProperties.getSslKeyPair()));
return jwtAccessTokenConverter;
}

@Bean
public TokenEnhancer tokenEnhancer() {
  return new CustomTokenEnhancer();
}

And it's using the class:

class CustomTokenEnhancer implements TokenEnhancer {

  @Autowired
  private TokenAuthenticationService tokenAuthenticationService;

  // Add user information to the token
  @Override
  public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
    User user = (User) authentication.getPrincipal();
    Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
    info.put(CommonConstants.JWT_CLAIM_USER_EMAIL, user.getEmail().getEmailAddress());
    info.put(CommonConstants.JWT_CLAIM_USER_FULLNAME, user.getFirstname() + " " + user.getLastname());
    info.put("scopes", authentication.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
    info.put("organization", authentication.getName());
    DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
    customAccessToken.setAdditionalInformation(info);
    customAccessToken.setExpiration(tokenAuthenticationService.getExpirationDate());
    return customAccessToken;
  }

}

I also have the class:

@Configuration
class CustomOauth2RequestFactory extends DefaultOAuth2RequestFactory {

  @Autowired
  private TokenStore tokenStore;

  @Autowired
  private UserDetailsService userDetailsService;

  public CustomOauth2RequestFactory(ClientDetailsService clientDetailsService) {
    super(clientDetailsService);
  }

  @Override
  public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) {
    if (requestParameters.get("grant_type").equals("refresh_token")) {
      OAuth2Authentication authentication = tokenStore
          .readAuthenticationForRefreshToken(tokenStore.readRefreshToken(requestParameters.get("refresh_token")));
      SecurityContextHolder.getContext()
          .setAuthentication(new UsernamePasswordAuthenticationToken(authentication.getName(), null,
              userDetailsService.loadUserByUsername(authentication.getName()).getAuthorities()));
    }
    return super.createTokenRequest(requestParameters, authenticatedClient);
  }

}

UPDATE: I also tried the alternative way of specifying the custom claim:

@Component
class CustomAccessTokenConverter extends JwtAccessTokenConverter {

    @Autowired
  private TokenAuthenticationService tokenAuthenticationService;

  @Override
  public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
    OAuth2Authentication authentication = super.extractAuthentication(claims);
    authentication.setDetails(claims);
    return authentication;
  }

  @Override
  public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
    User user = (User) authentication.getPrincipal();
    Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
    info.put(CommonConstants.JWT_CLAIM_USER_EMAIL, user.getEmail().getEmailAddress());
    info.put(CommonConstants.JWT_CLAIM_USER_FULLNAME, user.getFirstname() + " " + user.getLastname());
    info.put("scopes", authentication.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
    info.put("organization", authentication.getName());
    DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
    customAccessToken.setAdditionalInformation(info);
    customAccessToken.setExpiration(tokenAuthenticationService.getExpirationDate());
    return super.enhance(customAccessToken, authentication);
  }

}

with it being called like:

endpoints
.tokenStore(tokenStore())
.tokenEnhancer(jwtAccessTokenConverter())
.accessTokenConverter(jwtAccessTokenConverter())

but it changed nothing and the error remained identical.

Running with the debugger, none of these two enhancer overrides are called.

解决方案

To build Spring Boot server with OAuth2, JWT and extra claims we should:

1) Add dependency to the project:

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>

2) Add Web security configuration (to publish AuthenticationManager bean - it will be used in the next step), for example:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    @Autowired
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(username -> AuthUser.with()
                .username(username)
                .password("{noop}" + username)
                .email(username + "@mail.com")
                .authority(AuthUser.Role.values()[ThreadLocalRandom.current().nextInt(2)])
                .build()
        );
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

Here is implemented a simple UserDetailsService for testing purpose. It works with the following simple 'User' object and Role enum which implements GrantedAuthority interface. AuthUser has only one additional property email which will be added to the JWT token as a claim.

@Value
@EqualsAndHashCode(callSuper = false)
public class AuthUser extends User {

    private String email;

    @Builder(builderMethodName = "with")
    public AuthUser(final String username, final String password, @Singular final Collection<? extends GrantedAuthority> authorities, final String email) {
        super(username, password, authorities);
        this.email = email;
    }

    public enum Role implements GrantedAuthority {
        USER, ADMIN;

        @Override
        public String getAuthority() {
            return this.name();
        }
    }
}

3) Configure Authorization server and enable Resource server:

@Configuration
@EnableAuthorizationServer
@EnableResourceServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    public static final String TOKEN_KEY = "abracadabra";

    private final AuthenticationManager authenticationManager;

    public AuthServerConfig(final AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clientDetailsService) throws Exception {
        clientDetailsService.inMemory()
                .withClient("client")
                .secret("{noop}")
                .scopes("*")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(60 * 2) // 2 min
                .refreshTokenValiditySeconds(60 * 60); // 60 min
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain chain = new TokenEnhancerChain();
        chain.setTokenEnhancers(List.of(tokenEnhancer(), tokenConverter()));
        endpoints
                .tokenStore(tokenStore())
                .reuseRefreshTokens(false)
                .tokenEnhancer(chain)
                .authenticationManager(authenticationManager);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(tokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter tokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(TOKEN_KEY);
        converter.setAccessTokenConverter(authExtractor());
        return converter;
    }

    private TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
                if (authentication != null && authentication.getPrincipal() instanceof AuthUser) {
                    AuthUser authUser = (AuthUser) authentication.getPrincipal();
                    Map<String, Object> additionalInfo = new HashMap<>();
                    additionalInfo.put("user_email", authUser.getEmail());
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
                }
                return accessToken;
            };
    }

    @Bean
    public DefaultAccessTokenConverter authExtractor() {
        return new DefaultAccessTokenConverter() {
            @Override
            public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
                OAuth2Authentication authentication = super.extractAuthentication(claims);
                authentication.setDetails(claims);
                return authentication;
            }
        };
    }
}

A simple ClientDetailsService is implemented here. It contains only one client, which has 'client' name, blank password and granted types "password" and "refresh_token". It gives us a possibility to create a new access token and refresh it. (To work with many types of clients or in other scenarios you have to implement more complex, and maybe persistent, variants of ClientDetailsService.)

Authorization endpoints are configured with TokenEnhancerChain which contains tokenEnhancer and tokenConverter. It's important to add them in this sequence. The first one enhances an access token with additional claims (user email in our case). The second one creates a JWT token. The endpoints set with a simple JwtTokenStore, our TokenEnhancerChain and authenticationManager.

Note to JwtTokenStore - if you decide to implement a persistent variant of the store you can find more info here.

The last thing here is authExtractor which gives us a possibility to extract claims from JWT tokens of incoming requests.

Then all things are set up we can request our server to get an access token:

curl -i \
--user client: \
-H "Content-Type: application/x-www-form-urlencoded" \
-X POST \
-d "grant_type=password&username=user&password=user&scope=*" \
http://localhost:8080/oauth/token

Rsponse:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2VtYWlsIjoidXNlckBtYWlsLmNvbSIsInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyIqIl0sImV4cCI6MTU0Nzc2NDIzOCwiYXV0aG9yaXRpZXMiOlsiQURNSU4iXSwianRpIjoiYzk1YzkzYTAtMThmOC00OGZjLWEzZGUtNWVmY2Y1YWIxMGE5IiwiY2xpZW50X2lkIjoiY2xpZW50In0.RWSGMC0w8tNafT28i2GLTnPnIiXfAlCdydEsNNZK-Lw",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2VtYWlsIjoidXNlckBtYWlsLmNvbSIsInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyIqIl0sImF0aSI6ImM5NWM5M2EwLTE4ZjgtNDhmYy1hM2RlLTVlZmNmNWFiMTBhOSIsImV4cCI6MTU0Nzc2NzcxOCwiYXV0aG9yaXRpZXMiOlsiQURNSU4iXSwianRpIjoiZDRhNGU2ZjUtNDY2Mi00NGZkLWI0ZDgtZWE5OWRkMDJkYWI2IiwiY2xpZW50X2lkIjoiY2xpZW50In0.m7XvxwuPiTnPaQXAptLfi3CxN3imfQCVKyjmMCIPAVM",
    "expires_in": 119,
    "scope": "*",
    "user_email": "user@mail.com",
    "jti": "c95c93a0-18f8-48fc-a3de-5efcf5ab10a9"
}

If we decode this access token on https://jwt.io/ we can see that it contain the user_email claim:

{
  "user_email": "user@mail.com",
  "user_name": "user",
  "scope": [
    "*"
  ],
  "exp": 1547764238,
  "authorities": [
    "ADMIN"
  ],
  "jti": "c95c93a0-18f8-48fc-a3de-5efcf5ab10a9",
  "client_id": "client"
}

To extract such a claim (and other data) from a JWT token of incoming requests we can use the following approach:

@RestController
public class DemoController {

    @GetMapping("/demo")
    public Map demo(OAuth2Authentication auth) {

        var details = (OAuth2AuthenticationDetails) auth.getDetails();
        //noinspection unchecked
        var decodedDetails = (Map<String, Object>) details.getDecodedDetails();

        return Map.of(
                "name", decodedDetails.get("user_name"),
                "email", decodedDetails.get("user_email"),
                "roles", decodedDetails.get("authorities")
        );
    }
}

My working demo: sb-jwt-oauth-demo

Related info:

这篇关于如何生成带有一些自定义声明的JWT访问令牌?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆