Spring oauth2 刷新令牌 - 无法将访问令牌转换为 JSON

Posted

技术标签:

【中文标题】Spring oauth2 刷新令牌 - 无法将访问令牌转换为 JSON【英文标题】:Spring oauth2 refresh token - Cannot convert access token to JSON 【发布时间】:2017-02-07 23:51:01 【问题描述】:

我正在尝试在 Spring OAuth 应用程序中使用刷新令牌,但没有成功。系统将在密码授权时发出刷新令牌:

  
  "access_token": "xxxxx",
  "token_type": "bearer",
  "refresh_token": "xxxxxx",
  "expires_in": 21599,
  "scope": "read write"

但尝试使用刷新令牌会导致以下错误:

curl -u acme -d "grant_type=refresh_token&refresh_token=xxxxxx" http://localhost:9999/uaa/oauth/token


  "error": "invalid_token",
  "error_description": "Cannot convert access token to JSON"

我的认证服务器配置如下:

@Controller
@SessionAttributes("authorizationRequest")
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@EnableResourceServer
@ImportResource("classpath:/spring/application-context.xml")
@Configuration
public class ApplicationConfiguration extends WebMvcConfigurerAdapter 

    @RequestMapping("/user")
    @ResponseBody
    public Principal user(Principal user) 
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println(auth.toString());
        return user;
    

    @Override
    public void addViewControllers(ViewControllerRegistry registry) 
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/oauth/confirm_access").setViewName("authorize");
    

    @Configuration
    @Order(-20)
    protected static class LoginConfig extends WebSecurityConfigurerAdapter 

        @Autowired
        private AuthenticationManager authenticationManager;

        @Override
        protected void configure(HttpSecurity http) throws Exception 

            http.csrf().disable();

            // @formatter:off
            http
                .formLogin().loginPage("/login").permitAll()
            .and()
                .requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
            .and()
                .authorizeRequests().anyRequest().authenticated();
            // @formatter:on

        

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

    @Configuration
    public static class JwtConfiguration 

        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() 
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "foobar".toCharArray())
                    .getKeyPair("test");
            converter.setKeyPair(keyPair);
            return converter;
        

        @Bean
        public JwtTokenStore jwtTokenStore() 
            return new JwtTokenStore(jwtAccessTokenConverter());
        
    

    @Configuration
    @EnableAuthorizationServer
    protected static class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter implements
            EnvironmentAware 

        private static final String ENV_OAUTH = "authentication.oauth.";
        private static final String PROP_CLIENTID = "clientid";
        private static final String PROP_SECRET = "secret";
        private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";

        private RelaxedPropertyResolver propertyResolver;

        @Autowired
        private AuthenticationManager authenticationManager;

        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;

        @Autowired
        private JwtTokenStore jwtTokenStore;

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

        @Autowired
        private DataSource dataSource;

        @Override
        public void setEnvironment(Environment environment) 
            this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
        

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

        @Bean
        @Primary
        public DefaultTokenServices tokenServices() 
            DefaultTokenServices tokenServices = new DefaultTokenServices();
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setTokenStore(jwtTokenStore);
            tokenServices.setAuthenticationManager(authenticationManager);
            tokenServices.setTokenEnhancer(tokenEnhancer());
            return tokenServices;
        

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception 
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
            // The order is important here - the custom enhancer must come before the jwtAccessTokenConverter.
            tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter));
            endpoints
                    .authenticationManager(authenticationManager)
                    .tokenEnhancer(tokenEnhancerChain)
                    .tokenStore(jwtTokenStore)
                    .userDetailsService(userDetailsService);
        

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer)
                throws Exception 
            oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
        

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
            clients.jdbc(dataSource);
                    /*.withClient(propertyResolver.getProperty(PROP_CLIENTID))
                    .scopes("read", "write")
                    .autoApprove(true)
                    .authorities(ClientAuthoritiesConstants.CLIENT)
                    .authorizedGrantTypes("authorization_code", "refresh_token", "password")
                    .secret(propertyResolver.getProperty(PROP_SECRET))
                    .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer
                    .class, 1800));*/
        
    

    /**
     * Configures the global LDAP authentication
     */
    @Configuration
    protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter implements EnvironmentAware 

        private static final String ENV_LDAP = "authentication.ldap.";
        private static final String PROP_SEARCH_BASE = "userSearchBase";
        private static final String PROP_SEARCH_FILTER = "userSearchFilter";
        private static final String PROP_GROUP_SEARCH_FILTER = "groupSearchFilter";
        private static final String PROP_LDAP_URL = "url";
        private static final String PROP_LDAP_USER = "userDn";
        private static final String PROP_LDAP_PASS = "password";

        private RelaxedPropertyResolver propertyResolver;

        /**
         * Maps the LDAP user to the Principle that we'll be using in the app
         */
        public UserDetailsContextMapper userDetailsContextMapper() 
            return new UserDetailsContextMapper() 
                @Override
                public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
                                                      Collection<? extends GrantedAuthority> authorities) 
                    // Get the common name of the user
                    String commonName = ctx.getStringAttribute("cn");
                    // Get the users email address
                    String email = ctx.getStringAttribute("mail");
                    // Get the domino user UNID
                    String uId = ctx.getStringAttribute("uid");
                    return new CustomUserDetails(email, "", commonName, authorities);
                

                @Override
                public void mapUserToContext(UserDetails user, DirContextAdapter ctx) 
                    throw new IllegalStateException("Only retrieving data from LDAP is currently supported");
                

            ;
        

        @Override
        public void setEnvironment(Environment environment) 
            this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_LDAP);
        

        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception 
            auth
                    .ldapAuthentication()
                    .userSearchBase(propertyResolver.getProperty(PROP_SEARCH_BASE))
                    .groupSearchBase(propertyResolver.getProperty(PROP_SEARCH_BASE))
                    .userSearchFilter(propertyResolver.getProperty(PROP_SEARCH_FILTER))
                    .groupSearchFilter(propertyResolver.getProperty(PROP_GROUP_SEARCH_FILTER))
                    .userDetailsContextMapper(userDetailsContextMapper())
                    .contextSource()
                    .url(propertyResolver.getProperty(PROP_LDAP_URL))
                    .managerDn(propertyResolver.getProperty(PROP_LDAP_USER))
                    .managerPassword(propertyResolver.getProperty(PROP_LDAP_PASS));
        
    

有人知道为什么在给定有效的刷新令牌时身份验证服务器没有发出新令牌吗?

【问题讨论】:

【参考方案1】:

有这个问题。我正在发送“Bearer xxxxxx...”,而 TokenEnhancer 只期望“xxxxx...”没有“Bearer”前缀

【讨论】:

这个,在Authorization 标头中?喜欢Authorization=[9a1b74f0-9dea-4506-ae5e-0e2e824822b9] 没有密钥对 jks 配置,它可以工作,我需要 Bearer,我认为你不应该删除它。我已经尝试过,但确实会出现授权错误。【参考方案2】:

我遇到了同样的问题。经过一些调试,结果发现我的签名不匹配。

在我的情况下,我设置的密钥有点不同,并且存在签名和验证密钥不匹配的错误。

https://github.com/spring-projects/spring-security-oauth/issues/1144

【讨论】:

【参考方案3】:

Spring Boot 1.5.4 也有同样的问题

实际上jwtAccessTokenConverter.setVerifierKey(publicKey);并没有真正设置验证器(在调试值是null)中使用-

JwtAccessTokenConverter
...protected Map<String, Object> decode(String token) 
        try 
            Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);

解决方法有帮助:

private JwtAccessTokenConverter jwtAccessTokenConverter() 
        JwtAccessTokenConverter jwtAccessTokenConverter = new CustomTokenEnhancer();
        jwtAccessTokenConverter.setSigningKey(jwtSigningKey);
        jwtAccessTokenConverter.setVerifier(new RsaVerifier(jwtPublicKey));
        log.info("Set JWT signing key to: ", jwtAccessTokenConverter.getKey());

        return jwtAccessTokenConverter;
    

【讨论】:

我正在使用 spring boot 1.5.9 并且我有 Invalid Token: Cannot convert access token to JSON,这没有帮助【参考方案4】:

如果它对任何人有帮助,我已经两年了,但我同样的问题是由于我没有使用我在令牌服务提供商 DefaultTokenServices 中的 JwtTokenStore 中使用的 tokenEnhancer。

<!-- Access token converter -->
<bean id="jwtAccessTokenConverter"
      class="org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter">
    <property name="signingKey" value="$security.jwt.signing-key"/>
</bean>

<!-- Token store -->
<bean id="jwtTokenStore"
      class="org.springframework.security.oauth2.provider.token.store.JwtTokenStore">
    <constructor-arg name="jwtTokenEnhancer" ref="jwtAccessTokenConverter"/>
</bean>

<!-- Creates token store services provider -->
<bean id="tokenServiceProvider"
      class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
    <property name="tokenStore"
              ref="jwtTokenStore"/>
    <!--This must be set according to z docs -->
    <property name="tokenEnhancer"
              ref="jwtAccessTokenConverter"/>
    <property name="supportRefreshToken"
              value="true"/>
    <property name="accessTokenValiditySeconds"
              value="$security.jwt.access-token-validity-seconds"/>
    <property name="refreshTokenValiditySeconds"
              value="$security.jwt.refresh-token-validity-seconds"/>
</bean>

【讨论】:

【参考方案5】:

我在使用 Spring Boot 2.5.7 时遇到了同样的问题。因为我错过了为 JwtAccessTokenConverter 设置的 verifier

我的解决方案:

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() 
  JwtAccessTokenConverter jwtAccessTokenConverter = new CustomJwtAccessTokenConverter(privateKey);
  jwtAccessTokenConverter.setVerifier(new RsaVerifier(publicKey));
  return jwtAccessTokenConverter;

【讨论】:

【参考方案6】:

所以看起来问题是无效的 refresh_token 格式。由于我的配置,身份验证服务器期望的是一个有效的 JWT,而我发送的是一个普通的不记名令牌。因此出现错误消息“无法将令牌转换为 JSON”。

顺便说一句,我发现这份文档有助于理解 Spring OAuth 的所有部分如何组合在一起,这让我弄清楚了这里发生了什么:

https://github.com/spring-projects/spring-security-oauth/blob/master/docs/oauth2.md

【讨论】:

如果您发布了您的解决方案,而不是仅仅给出一个特定的链接,那肯定会有所帮助 @TimS 你能补充一下我们必须做些什么来解决这个问题。我想不通。 @Ibrahim 您需要向其发送有效的 JWT 以解决问题。不幸的是,您的令牌无效的原因有很多,并且取决于您的配置。就我而言,由于我以错误的格式发送它,我的不记名令牌没有正确编码。无论哪种方式,我都建议阅读答案中的链接,因为它列出了每个 Spring OAuth 2.0 组件的功能以及它们如何组合在一起。如果您了解 Spring 实现的细节,解决这些问题会容易得多。 这不是一个“解决方案”,只是一个参考 链接已损坏。

以上是关于Spring oauth2 刷新令牌 - 无法将访问令牌转换为 JSON的主要内容,如果未能解决你的问题,请参考以下文章

Spring OAuth2刷新令牌刷新访问令牌后更改

带有刷新令牌的 Spring Google OAuth2

是否可以在 Spring Security 中仅使用刷新令牌请求访问令牌 oauth2?没有基本身份验证?

Spring OAuth2在发送刷新令牌时要求输入密码

Spring 5,401 返回后带有 Google 刷新访问令牌的 OAuth2

如何在spring security oauth2中分离访问令牌和刷新令牌端点