mysql - Spring Boot + OAuth + JWT + MySQL 刷新 token 第二次不起作用

标签 mysql spring-boot oauth-2.0 jwt spring-security-oauth2

我正在使用 Spring boot、OAuth2、JWT 客户 token 和 MySQL。

问题:我能够获取 token 和刷新 token ,使用刷新 token 我只能按时获取新 token ,如果我再次尝试使用新刷新 token 获取新 token 意味着我收到以下错误。

错误信息

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

请找到授权服务器和资源服务器配置文件。 我找不到我做错的地方。

授权服务器

@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;
}

UserDetailsS​​ervice

@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;
}

自定义 token

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;
}

第一次获取 token

{
"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"
}

第一次获取刷新 token

{
"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"
}

使用上面的刷新 token 第二次获取刷新 token

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

最佳答案

我也遇到了与 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;
    }

关于mysql - Spring Boot + OAuth + JWT + MySQL 刷新 token 第二次不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39787350/

相关文章:

mysql - 使用托管商上托管的 phpMyadmin 从我的本地系统连接到 MySql 数据库

spring - 在运行时设置/覆盖 Spring/Spring Boot 属性

java - 如何使用 Spring Boot 从 gradle 获取应用程序版本和其他一些信息?

Azure Active Directory OAuth 2.0 授权给出错误请求

ruby - 如何从 OAuth 2 for Ruby 获取有效/无效登录

php - 如何将多个图像添加到mysql数据库?

php - 显示 $_SERVER ['PHP_SELF' 之后选择的选项]

mysql - 将 IPA 插入 MySQL

java - 需要上传 xml 文件,解析并存储在数据库中,而不将文件存储在任何物理物理位置

java - Spring boot 2.0.3 + 安全性 + Oauth2 自动配置