spring + oauth2 /api/oauth/token is `Unauthorized` after Tomcat/server is restart

风格不统一 提交于 2019-12-25 02:26:44

问题


I am using spring-security-5, spring-boot 2.0.5 and oauth2. I have checked and test by online reference.

Like :

Spring Security and OAuth2 to protect REST API endpoints

Spring Boot 2 Applications and OAuth 2

Everything is fine in my project.

When I request this URL , http://localhost:8080/api/oauth/token, I get response as

And I restart the server(Tomcat), I request that URL again, I get response as

So my question is, how the client-app can get access_token again after Tomcat or spring-boot application is restart?

One thing For that kind of situation, if I delete the record of OAUTH_CLIENT_DETAILS table in database, It is OK to request again. I also get access_token again.

Update

Please don't miss understanding response json format, every response I wrap by custom object like as below.

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

Update 2

I use JDBCTokenStore, all of oauth information keep in database

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

回答1:


You need to set the tokenStore to something different than InMemory.

I tend to use redis, as it scales very well, is fast as hell and once it's there you can use it as cache:

@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

And to get if up and running with docker:

version: '3'
services:

  cache:
    image: redis:latest
    ports:
      - "6380:6379"

  db:
    image: postgres:latest
    ports:
      - "5454:5432"
    environment:
      - POSTGRES_DB=mydb

The JWTTokenStore would enable you to go without 3rd party software and scales good as well, but makes it harder to revoke token.

For smaller applications, tokens may be ok to store in the database (see JdbcTokenStore).




回答2:


In my question, even I use JdbcTokenStore, it is still getting Unauthorized response after restart the sever.

Now, I get a solution for my issue which is using JwtTokenStore. It is stateless. I just need to modify my AuthorizationServerConfig class as below. I don't need any oauth related table in my database now.

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


来源:https://stackoverflow.com/questions/53828014/spring-oauth2-api-oauth-token-is-unauthorized-after-tomcat-server-is-restar

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!