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

Spring Boot+OAuth+JWT+MySQL刷新令牌第二次不工作

庄瀚玥
2023-03-14

我正在使用Spring boot、OAuth2、JWT custome令牌和MySQL。

问题:我可以获得令牌和刷新令牌,使用刷新令牌我只能按时获得新令牌,如果我再次尝试使用新刷新令牌获得新令牌意味着我将得到以下错误。

错误信息

{
"error": "invalid_grant",
"error_description": "Invalid refresh token: eyJhbGciOiJSUzI1NiJ9.eyJsb2dpbklkIjoibmF2ZWVuIiwidXNlcl9uYW1lIjoibmF2ZWVuIiwic2NvcGUiOlsiY2l0eSIsInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiI4YmVlMzZhZi1lZWM1LTQzODItYjNkZi1jYTU3Mjc0NjQ5N2MiLCJleHAiOjE0NzUyMjc4NzUsImF1dGhvcml0aWVzIjpbIlRlc3RlciIsIlVzZXIiLCJBZG1pbiIsImNhbXBhaWduLWNhbmFsbCIsIm9yZy1jYW5hbGwiXSwianRpIjoiNjE1OWM4NTYtYTZmNi00Njg3LTg3OTMtMTA1NDdkODE4YmVhIiwiY2xpZW50X2lkIjoiY2l0eUNsaWVudElkIn0.FvA821Hv0ZzA6mdwNp-XlcHAy6tCncP8snkQDlmDWulFE-BIe-KxTT0ugjoK2l1ncAQugtyfXCnS_a0bgAPcu1HKmYgIvj4f3XBj1WLRagiDfJqjZAwZhDPvrwks7W1IsvWrzy5k-pmoO7373C5DU0jbFsanzkvMQ6LQAwb_bFfOB3GYH5BSIW4rcbe8AH1B3QKxn9J26Jj1yQWnkY8HnUqnxN5C-3jBwr8pvqPmX2AjOVeAnkoGfY6B3Dq1vz8EE17I8GG2uqGgUsaTiVqP3Lka__ue00MjajxcpVHeh7t1Qs0IbTa2oeuahAwcYOC_ik_Rplhn3w-LHpyhPBrTHA"
}

请查找Auth服务器和资源服务器配置文件。我找不到我错在哪里。

授权服务器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends  AuthorizationServerConfigurerAdapter {

@Autowired
private DataSource dataSource;

@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;

@Autowired
@Qualifier("userDetailsService")
UserDetailsService userDetailsService;

@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
            //.passwordEncoder(passwordEncoder());
}

@Bean
public PasswordEncoder passwordEncoder() {
    CustomPasswordEncoder encoder = new CustomPasswordEncoder();
    return encoder;
}

@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
    clients.jdbc(dataSource);
}

@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
    endpoints.tokenStore(tokenStore())
            // .approvalStore(approvalStore())
            .userDetailsService(userDetailsService).tokenEnhancer(tokenEnhancerChain)
            .authenticationManager(authenticationManager);
}

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"),
            "mypass".toCharArray());
    converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
    return converter;
}

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

// JDBC token store configuration
@Bean
public TokenStore tokenStore() {
    return new JdbcTokenStore(dataSource);
}

@Bean
@Primary
public ResourceServerTokenServices tokenServices() {
    final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(tokenStore());
    defaultTokenServices.setReuseRefreshToken(true);
    return defaultTokenServices;
}
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private AppUserService appUserService;

@Autowired
private AppUserRepo appUserRepo;

@Transactional(readOnly = true)
@Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
    // TODO: clean the server methods
    final AppUser appUser = appUserRepo.findExceptDeletedByAppUserName(username);
    final AppUserCreateDto appUserCreateDto = appUserService.getAppUserCreateByAppUserId(appUser.getAppUserId());
    return buildUserForAuthentication(appUser, buildUserAuthority(appUserCreateDto));
}

// Converts com.mkyong.users.model.User user to
// org.springframework.security.core.userdetails.User
private User buildUserForAuthentication(AppUser appUser, List<GrantedAuthority> authorities) {
    return new User(appUser.getLoginId(), appUser.getPasswordHash(), appUser.isActive(), true, true, true,
            authorities);
}

private List<GrantedAuthority> buildUserAuthority(AppUserCreateDto appUserCreateDto) {
    Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
    if (appUserCreateDto.isPrimary()) {
        setAuths.add(new SimpleGrantedAuthority("SuperAdmin"));
    } else {
        for (AppUserRoleDto userRole : appUserCreateDto.getAppUserRoleDtos()) {
            setAuths.add(new SimpleGrantedAuthority(userRole.getAppRoleDto().getName()));
        }
        for (AppUserClaimDto userClaim : appUserCreateDto.getAppUserClaimDtos()) {
            setAuths.add(new SimpleGrantedAuthority(
                    userClaim.getAppClaimDto().getClaimType() + "-" + userClaim.getAppClaimDto().getClaimValue()));
        }
    }
    List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
    return Result;
}
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
    final Map<String, Object> additionalInfo = new HashMap<>();
    additionalInfo.put("loginId", authentication.getName());
    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
    return accessToken;
}
@Configuration
@EnableWebSecurity
public class AuthorizationServerWebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
@Qualifier("userDetailsService")
UserDetailsService userDetailsService;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

@Bean
public PasswordEncoder passwordEncoder() {
    CustomPasswordEncoder encoder = new CustomPasswordEncoder();
    return encoder;
}

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

@Override
public void configure(final HttpSecurity http) throws Exception {
    // @formatter:off
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and().anonymous().and()
            .authorizeRequests().antMatchers("/api/p/**").permitAll().antMatchers("/api/ping").permitAll()
            .antMatchers("/api/**").authenticated();
    // @formatter:on
}

@Override
public void configure(final ResourceServerSecurityConfigurer config) {
    config.tokenServices(tokenServices());
}

@Bean
@Primary
public ResourceServerTokenServices tokenServices() {
    final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(tokenStore());
    defaultTokenServices.setReuseRefreshToken(true);
    return defaultTokenServices;
}

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

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    final Resource resource = new ClassPathResource("public.txt");
    String publicKey = null;
    try {
        publicKey = IOUtils.toString(resource.getInputStream());
    } catch (final IOException e) {
        throw new RuntimeException(e);
    }
    converter.setVerifierKey(publicKey);
    return converter;
}
{
"access_token":"eyJhbGciOiJSUzI1NiJ9.eyJsb2dpbklkIjoibmF2ZWVuIiwidXNlcl9uYW1lIjoibmF2ZWVuIiwic2NvcGUiOlsiY2l0eSIsInJlYWQiLCJ3cml0ZSJdLCJleHAiOjE0NzUyMjUyMzAsImF1dGhvcml0aWVzIjpbIlRlc3RlciIsIlVzZXIiLCJBZG1pbiIsImNhbXBhaWduLWNhbmFsbCIsIm9yZy1jYW5hbGwiXSwianRpIjoiYWNlMmQwYzgtZjRhOS00NzY5LThhN2EtNDk1ZjY3ZDFhMjk4IiwiY2xpZW50X2lkIjoiY2l0eUNsaWVudElkIn0.cKZgk39yQ_tl7NW4OhUmSnhPSgvWD8UPp6RRfpc0hsW28ICVjIaCURRaC-eEs9J_YuC2X7NDTFFy3KknFka7rDV2JMCFSILivW13EFT2i0TkHUVFCBWk4MMlEKOXQOUVPiMZ3t3zD6_Tkmo_NneNPjouRVyFjCZ4WGqWPEzGpofExZWlBzoV7bDuF28fTQKqxIQM-ubwx-hKY_btlaXQpyJCuKn4QoCwvp2Bh5zqSEIk2RiGh0nsUyi_MZl3TQX2kYDv-SwWOxf3K9bibY9xPhzgLVIER39Ipo7FrUE9KsYoEkXM1-CghbADjIXu03xd7GZ2530fs-MHhr24YzVY1Q",
"token_type": "bearer",
"refresh_token":"eyJhbGciOiJSUzI1NiJ9.eyJsb2dpbklkIjoibmF2ZWVuIiwidXNlcl9uYW1lIjoibmF2ZWVuIiwic2NvcGUiOlsiY2l0eSIsInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiJhY2UyZDBjOC1mNGE5LTQ3NjktOGE3YS00OTVmNjdkMWEyOTgiLCJleHAiOjE0NzUyMjc4NzUsImF1dGhvcml0aWVzIjpbIlRlc3RlciIsIlVzZXIiLCJBZG1pbiIsImNhbXBhaWduLWNhbmFsbCIsIm9yZy1jYW5hbGwiXSwianRpIjoiNjE1OWM4NTYtYTZmNi00Njg3LTg3OTMtMTA1NDdkODE4YmVhIiwiY2xpZW50X2lkIjoiY2l0eUNsaWVudElkIn0.S5pCyuaJVf6HnzLfct2HMQQdkcoZO0-FlgGIRJueAvMmVFRpiCqYCT7AniW8NUvltcMTkDXdZPJo21OmomdWUr3gO1BV3Ki9aJNuewXxsoymIy_L3xWbNb8k8hdrwhYZQufe1YnWLKgHpDUSc13cW-6SNQQwd9ugXkMIvp1qG7d9j6GCZrxOXj0HNLKR3CubfesweUb9GtG8D0XkEGc7O-xPSHZnJWX73sCT5Qi1fot1btTMoeCwp63r9Wa4TkESXrmXdSMzI0GUwc6x_7r3mv5gF34gzF8Z3fChSMglgxreRtF2PbTPGZKXQ3Dk-f0WcWOmbkpetg0n4Wo0dNujaw",
"expires_in": 359,
"scope": "city read write",
"loginId": "naveen",
"jti": "ace2d0c8-f4a9-4769-8a7a-495f67d1a298"
}

第一次获取刷新令牌

{
"access_token":"eyJhbGciOiJSUzI1NiJ9.eyJsb2dpbklkIjoibmF2ZWVuIiwidXNlcl9uYW1lIjoibmF2ZWVuIiwic2NvcGUiOlsiY2l0eSIsInJlYWQiLCJ3cml0ZSJdLCJleHAiOjE0NzUyMjUzNDcsImF1dGhvcml0aWVzIjpbIlRlc3RlciIsIlVzZXIiLCJBZG1pbiIsImNhbXBhaWduLWNhbmFsbCIsIm9yZy1jYW5hbGwiXSwianRpIjoiNzViYTA4OWYtZTczZC00MWIyLWEwM2ItMzBlZDRjNzIyMmM4IiwiY2xpZW50X2lkIjoiY2l0eUNsaWVudElkIn0.fhBqLGTyu4BaAv9zS9gGAgZYymhbBxgnIWBWedmmX1bHNGVWmGjkfxsOtMebVRzMx1WpQUreKSj4IO8bfSV6J9UXgJiq3bEP49gL_egvXIS0bmol35MaN1Kna1hod40RmhxEgfsScuP3Lf35eLX1cjqvpM_B6xtfjStf63AYZ0-e0_oigcXJkTU6QJmC2XFeeoaCHWEdWrWo6lIGMbriv2vlqIn81qENAZ_d9aNGpd-LtUqjJgD299xEOFhO6OCKfjx61gRLwB18daRI_lm_ns9mHUug3T87Ovq-axDYB4NaHB2LvMVi0pXYsdxjlRD-fQ--dNz4JcdTxbuhuxbr8Q",
"token_type": "bearer",
"refresh_token":"eyJhbGciOiJSUzI1NiJ9.eyJsb2dpbklkIjoibmF2ZWVuIiwidXNlcl9uYW1lIjoibmF2ZWVuIiwic2NvcGUiOlsiY2l0eSIsInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiI3NWJhMDg5Zi1lNzNkLTQxYjItYTAzYi0zMGVkNGM3MjIyYzgiLCJleHAiOjE0NzUyMjc4NzUsImF1dGhvcml0aWVzIjpbIlRlc3RlciIsIlVzZXIiLCJBZG1pbiIsImNhbXBhaWduLWNhbmFsbCIsIm9yZy1jYW5hbGwiXSwianRpIjoiNjE1OWM4NTYtYTZmNi00Njg3LTg3OTMtMTA1NDdkODE4YmVhIiwiY2xpZW50X2lkIjoiY2l0eUNsaWVudElkIn0.fTJw2F3z-YZQOZ8gTLy9oZheIcZVP9UnbqhTFBG0kVuNojTO7NkzvrzdbG6CwHifolK4A31o2smmw5RHlx6224PgBnE-mCzI6lFG94O77IGvBfCNARJL_X6HbWm2wvtTNnz8k0UN_xPgqHtTpBcIUAHHxMG3TyFZFAoKWYbsQ6WL1mVNFwUxr2R60JYUlCPMB8Tl-2P9IEQr2FIH9amX80fsV23n8023quouwLOVmgUGyVzT1bJ1s2KtgQ51D3T6bvxR4IBlEhSYJ2hmt7DB1IbYQBkxWkd53BiMQQEPyFNgR_9JWFLH7Uq2TUOOb8xL_NnsoyAIO71IFxRPOOsN9w",
"expires_in": 359,
"scope": "city read write",
"loginId": "naveen",
"jti": "75ba089f-e73d-41b2-a03b-30ed4c7222c8"
}

使用上述刷新令牌第二次获取刷新令牌

{
"error": "invalid_grant",
"error_description": "Invalid refresh token: eyJhbGciOiJSUzI1NiJ9.eyJsb2dpbklkIjoibmF2ZWVuIiwidXNlcl9uYW1lIjoibmF2ZWVuIiwic2NvcGUiOlsiY2l0eSIsInJlYWQiLCJ3cml0ZSJdLCJhdGkiOiI3NWJhMDg5Zi1lNzNkLTQxYjItYTAzYi0zMGVkNGM3MjIyYzgiLCJleHAiOjE0NzUyMjc4NzUsImF1dGhvcml0aWVzIjpbIlRlc3RlciIsIlVzZXIiLCJBZG1pbiIsImNhbXBhaWduLWNhbmFsbCIsIm9yZy1jYW5hbGwiXSwianRpIjoiNjE1OWM4NTYtYTZmNi00Njg3LTg3OTMtMTA1NDdkODE4YmVhIiwiY2xpZW50X2lkIjoiY2l0eUNsaWVudElkIn0.fTJw2F3z-YZQOZ8gTLy9oZheIcZVP9UnbqhTFBG0kVuNojTO7NkzvrzdbG6CwHifolK4A31o2smmw5RHlx6224PgBnE-mCzI6lFG94O77IGvBfCNARJL_X6HbWm2wvtTNnz8k0UN_xPgqHtTpBcIUAHHxMG3TyFZFAoKWYbsQ6WL1mVNFwUxr2R60JYUlCPMB8Tl-2P9IEQr2FIH9amX80fsV23n8023quouwLOVmgUGyVzT1bJ1s2KtgQ51D3T6bvxR4IBlEhSYJ2hmt7DB1IbYQBkxWkd53BiMQQEPyFNgR_9JWFLH7Uq2TUOOb8xL_NnsoyAIO71IFxRPOOsN9w"
}

共有1个答案

颜永怡
2023-03-14

我在JdbcTokenStore中也遇到了同样的问题。我通过在AuthorizationServerEndpointsConfigurer中设置reuseRefreshTokens(false)和在DefaultTokenServices中设置setSupportRefreshToken(true)来解决这个问题

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.tokenStore(tokenStore)
            .reuseRefreshTokens(false)
            .accessTokenConverter(accessTokenConverter)
            .authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService)
            ;


}

   @Bean
    @Primary
    //Making this primary to avoid any accidental duplication with another token service instance of the same name
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
 类似资料:
  • 我正在构建一个移动应用程序,并且正在使用JWT进行身份验证。 最好的方法似乎是将JWT访问令牌与刷新令牌配对,这样我就可以根据需要频繁地使访问令牌过期。 刷新令牌是什么样子的?是随机字符串吗?那串加密了吗?是另一个JWT吗? 刷新令牌将存储在用户模型的数据库中以便访问,对吗?在这种情况下似乎应该加密 在用户登录后,我是否会将刷新令牌发送回,然后让客户端访问单独的路由来检索访问令牌?

  • 本文向大家介绍oauth 刷新访问令牌,包括了oauth 刷新访问令牌的使用技巧和注意事项,需要的朋友参考一下 示例 资源

  • 我正在构建一个使用JWT进行身份验证的应用程序。我开始做一些研究,但对于诸如刷新令牌和令牌存储之类的主题缺乏共识,我感到惊讶。 据我所知,JWT和OAuth是两个不同的协议,它们遵循不同的规范。 但我的问题是,对于一个没有通过第三方资源服务器如Google、Facebook等认证的应用程序,有一个刷新令牌真的有用吗?为什么不让JWT令牌像刷新令牌一样持续时间长。 另一方面,我可以看到,如本文所述,

  • 我使用jwt为用户在我的应用程序上执行操作提供授权。它工作得很好,因为我不必访问数据库来查看用户是否有效。我在jwt中遇到的问题是,当令牌过期时,用户必须再次登录。在网上阅读我一直在阅读刷新令牌可以用来解决这个问题,但没有白痴证明解释它如何工作。它们是如何工作的?需要数据库访问吗?

  • 我正在使用Tymon提供的包来处理从我的laravel后端到spa前端的Auth,我正在创建AuthController,这几乎是我从文档中获取的,只是稍微调整一下它以满足我的需要。从登录到注销以及令牌过期,一切正常。 问题是,我确实看到该控制器上有一个令牌刷新功能,如果我的猜测是正确的,那就是刷新客户端已经拥有的当前令牌。但是怎么做呢?如何处理前端上的刷新令牌?因为它是相当烦人的,每60分钟(默

  • null 很抱歉太啰嗦了。 提前谢了。