【问题标题】:spring + oauth2 /api/oauth/token is `Unauthorized` after Tomcat/server is restartTomcat/服务器重启后 spring + oauth2 /api/oauth/token 为“未授权”
【发布时间】:2019-05-18 13:47:59
【问题描述】:

我正在使用spring-security-5spring-boot 2.0.5oauth2。我已经通过在线参考进行了检查和测试。

喜欢:

Spring Security and OAuth2 to protect REST API endpoints

Spring Boot 2 Applications and OAuth 2

我的项目一切正常。

当我请求这个 URL 时,http://localhost:8080/api/oauth/token,我得到的响应是

然后我重新启动服务器(Tomcat),我再次请求该 URL,我得到响应

所以我的问题是,客户端应用程序如何在Tomcatspring-boot 应用程序重新启动之后再次获得access_token

一件事 对于这种情况,如果我删除数据库中OAUTH_CLIENT_DETAILS表的记录,重新请求就可以了。我也再次收到access_token

更新

请不要错过理解响应json 格式,我用自定义对象包装的每个响应,如下所示。

{
    "status": "SUCCESS", <-- here my custom
    "data": {
        "timestamp": "2018-12-18T07:17:00.776+0000", <-- actual response from oauth2
        "status": 401,  <-- actual response from oauth2                 
        "error": "Unauthorized", <-- actual response from oauth2
        "message": "Unauthorized", <-- actual response from oauth2
        "path": "/api/oauth/token" <-- actual response from oauth2
    }
}

更新 2

我使用JDBCTokenStore,所有oauth信息都保存在数据库中

package com.mutu.spring.rest.oauth2;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    static final String CLIEN_ID = "zto-api-client";
//  static final String CLIENT_SECRET = "zto-api-client";
    static final String CLIENT_SECRET = "$2a$04$HvD/aIuuta3B5DjXXzL08OSIcYEoFsAYK9Ys4fKpMNHTODZm.mzsq";
    static final String GRANT_TYPE_PASSWORD = "password";
    static final String AUTHORIZATION_CODE = "authorization_code";
    static final String REFRESH_TOKEN = "refresh_token";
    static final String IMPLICIT = "implicit";
    static final String SCOPE_READ = "read";
    static final String SCOPE_WRITE = "write";
    static final String TRUST = "trust";
    static final int ACCESS_TOKEN_VALIDITY_SECONDS = 1*60;
    static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 2*60;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private DataSource dataSource;

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

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


    @Override
    public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {

        configurer
                .jdbc(dataSource)
                .withClient(CLIEN_ID)
                .secret("{bcrypt}" + CLIENT_SECRET)
                .authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT )
//              .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                .scopes(SCOPE_READ, SCOPE_WRITE, TRUST)
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
                .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
                .authenticationManager(authenticationManager);
    }
}

【问题讨论】:

    标签: java spring spring-boot spring-security spring-oauth2


    【解决方案1】:

    您需要将tokenStore 设置为与InMemory 不同的值。

    我倾向于使用 redis,因为它可以很好地扩展,速度非常快,一旦它在那里,你就可以将它用作缓存:

    @Configuration
    @EnableAuthorizationServer
    class AuthorizationServerConfig : AuthorizationServerConfigurerAdapter() {
    
        @Bean
        fun tokenStore(): TokenStore = RedisTokenStore(redisConnectionFactory).apply {
            setAuthenticationKeyGenerator(authenticationKeyGenerator)
        }
    }
    

    Application.yaml:

    spring:
        redis: 
            host: 0.0.0.0
            password:
            port: 6380
            database: 0
    

    如果使用 docker 启动并运行:

    version: '3'
    services:
    
      cache:
        image: redis:latest
        ports:
          - "6380:6379"
    
      db:
        image: postgres:latest
        ports:
          - "5454:5432"
        environment:
          - POSTGRES_DB=mydb
    

    JWTTokenStore 可以让您在没有 3rd 方软件的情况下使用,并且可以很好地扩展,但更难撤销令牌。

    对于较小的应用程序,令牌可能可以存储在数据库中(请参阅JdbcTokenStore)。

    【讨论】:

    • 我没有使用inMemory。我已经在使用JdbcTokenStore 可以查看我的更新帖子吗?
    • 你能检查一下authenticationKeyGenerator使用的是什么类型的吗?我认为默认的是使用一些运行时配置。我记得也有这个,@Autowired private lateinit var authenticationKeyGenerator: AuthenticationKeyGenerator 让它消失了。
    【解决方案2】:

    在我的问题中,即使我使用JdbcTokenStore,重启服务器后仍然收到Unauthorized 响应。

    现在,我使用JwtTokenStore 解决了我的问题。它是stateless。我只需要修改我的AuthorizationServerConfig 类如下。我的数据库中现在不需要任何oauth 相关表。

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
    import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
    
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        static final String CLIEN_ID = "zto-api-client";
    //  static final String CLIENT_SECRET = "zto-api-client";
        static final String CLIENT_SECRET = "$2a$04$HvD/aIuuta3B5DjXXzL08OSIcYEoFsAYK9Ys4fKpMNHTODZm.mzsq";
        static final String GRANT_TYPE_PASSWORD = "password";
        static final String AUTHORIZATION_CODE = "authorization_code";
        static final String REFRESH_TOKEN = "refresh_token";
        static final String IMPLICIT = "implicit";
        static final String SCOPE_READ = "read";
        static final String SCOPE_WRITE = "write";
        static final String TRUST = "trust";
        static final int ACCESS_TOKEN_VALIDITY_SECONDS = 5*60;
        static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 5*60;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Bean
        public BCryptPasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    
        // replace
        @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setSigningKey("as-you-like-your-key");
            return converter;
        }
    
        @Bean
        public TokenStore tokenStore() {
            return new JwtTokenStore(accessTokenConverter()); // replace
        }
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer.tokenKeyAccess("permitAll()")
                       .checkTokenAccess("isAuthenticated()");
        }
    
    
        @Override
        public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
    
            configurer
                    .inMemory() // replace
                    .withClient(CLIEN_ID)
                    .secret("{bcrypt}" + CLIENT_SECRET)
                    .authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT )
                    .scopes(SCOPE_READ, SCOPE_WRITE, TRUST)
                    .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
                    .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS);
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.tokenStore(tokenStore())
                    .authenticationManager(authenticationManager)
                    .accessTokenConverter(accessTokenConverter());// replace
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-07-21
      • 2015-03-31
      • 2016-09-06
      • 2015-05-22
      • 1970-01-01
      • 2015-07-31
      • 2016-05-21
      • 2014-07-09
      • 2013-09-22
      相关资源
      最近更新 更多