当前位置: 首页 > 知识库问答 >
问题:

如何生成一个包含一些自定义声明的JWT访问令牌?

龙高超
2023-03-14

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

我正在使用以下依赖项:

  <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())

与调试器一起运行时,不会调用这两个增强器重写。

共有1个答案

爱亮
2023-03-14

要使用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安全配置(以发布authenticationManagerbean-将在下一步中使用),例如:

@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();
    }
}
@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();
        }
    }
}
@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。它只包含一个客户端,该客户端具有'client'名称、空白密码和授予的类型“password”和“refresh_token”。它使我们有可能创建一个新的访问令牌并刷新它。(要使用许多类型的客户端或在其他场景中工作,您必须实现ClientDetailsService的更复杂的、可能是持久的变体。)

授权endpoint使用TokenEnhancerChain配置,它包含TokenEnhancerTokenConverter。在这个序列中添加它们是很重要的。第一个使用附加声明(在本例中为用户电子邮件)增强访问令牌。第二个创建一个JWT令牌。使用简单的JWTTokenStore、我们的TokenEnhancerChainAuthenticationManager设置的endpoint

jWTTokenStore注意事项-如果您决定实现存储的持久性变体,可以在这里找到更多信息。

这里的最后一点是authextractor,它使我们可以从传入请求的JWT令牌中提取声明。

然后所有的事情都设置好了,我们可以请求我们的服务器获得一个访问令牌:

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"
}
{
  "user_email": "user@mail.com",
  "user_name": "user",
  "scope": [
    "*"
  ],
  "exp": 1547764238,
  "authorities": [
    "ADMIN"
  ],
  "jti": "c95c93a0-18f8-48fc-a3de-5efcf5ab10a9",
  "client_id": "client"
}
@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")
        );
    }
}

相关信息:

  • OAuth2自动配置
  • https://www.baeldung.com/spring-security-oauth
  • https://projects.spring.io/spring-security-oauth/docs/oauth2.html
 类似资料:
  • 我创建了一个公共日志back-common.xml。我想在另一个文件logback.spring.xml中使用这个文件。请帮助我如何有效地使用它。 截至目前,应用程序正在启动,但不会在控制台中打印日志,并且日志不会填充到日志文件中。请帮忙。不要将其标记为重复,因为我几乎尝试了所有内容,并且我已经为此投入了2天。与此相关的其他问题没有附上有效的答案。 logback-spring.xml logba

  • JWT令牌的大多数示例都使用clj时间,而现在不推荐使用clj时间,而是使用本机java。时间我正试图与buddy一起使用java time对令牌进行签名/验证,但我一直在尝试将exp声明传递给我的令牌。以下是我所拥有的一个例子: 当我测试是否可以取消令牌签名时 我得到以下错误: 柴郡执行错误(JsonGenerationException)。生成/生成(generate.clj:152)。无法对

  • 问题内容: 在Java中,你可以在一个文件中定义多个顶级类,条件是其中最多一个是公共的(请参见JLS§7.6)。参见以下示例。 是否有此技术整洁名(类似于)? JLS表示系统可能会强制执行这些二级类不能为的限制,例如,它们不能被视为程序包专用。这真的在Java实现之间有所改变吗? 例如,PublicClass.java: 问题答案: 对于这种技术,我建议的名称(在一个源文件中包括多个顶级类)将是“

  • 问题内容: 我正在使用dgrijalva / jwt-go /软件包。 我想从令牌中提取有效载荷,但是我找不到方法。 示例(摘自:https : //jwt.io/): 对于编码: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOixMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydhV​​rF7H

  • 我正在使用微软图形SDK为我的应用程序(不是用户)获取访问令牌,以便从共享点读取。我一直在关注这个文档,并发布了这个SO问题。链接的SO中的代码是相同的。我能够添加应用程序权限以及在Azure门户中授予它们(通过按下按钮)。问题是,返回使用的令牌中不包含任何角色/scp声明。因此,当使用令牌时,我收到“令牌中需要存在scp或角色声明”消息。 可以肯定的是,在获取访问令牌时,我传递的范围的唯一值是:

  • 我正在考虑将Auth0用于我的API和web应用程序,并进行查询。生成Jwt令牌时,我希望包含一些仅存在于我的用户数据库中的自定义用户声明。这是可能的,还是所有声明都需要作为Auth0中的预定义属性存在。 我有自己的用户数据库,因为我需要在那里存储一些动态和复杂的用户权限。我意识到一个选择是不将这些权限存储在令牌中,我可以有一个单独的api来获取它们,但是为了性能和简单性,我宁愿将它们包装到Jwt