Spring Security OAuth2 资源服务器总是返回无效的令牌

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29596036/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 00:30:22  来源:igfitidea点击:

Spring Security OAuth2 Resource Server Always Returning Invalid Token

springoauthspring-security

提问by jyore

I am trying to get a basic in-memory OAuth2 server running using the Spring Libraries. I have been following the sparklr example.

我正在尝试使用 Spring 库运行一个基本的内存中 OAuth2 服务器。我一直在关注sparklr 示例

I currently have configured the Server and almost everything is working, however I cannot access my restricted resource from the resource server.

我目前已经配置了服务器并且几乎一切正常,但是我无法从资源服务器访问我的受限资源。

My test workflow:

我的测试工作流程:

  1. Access the oauth authorized URI to start the OAuth2 flow: http://localhost:8080/server/oauth/authorize?response_type=code&client_id=client

  2. Redirect to the login page: http://localhost:8080/server/login

  3. Handle the approval and redirect to my configured redirect page w/ a code parameter: http://localhost:8080/client?code=HMJO4K

  4. Construct a GET request using Basic Auth using the client id and secret along with the grant type and code: http://localhost:8080/server/oauth/token?grant_type=authorization_code&code=HMJO4K

  5. Receive an access_token and refresh token object in return

    { access_token: "f853bcc5-7801-42d3-9cb8-303fc67b0453" token_type: "bearer" refresh_token: "57100377-dea9-4df0-adab-62e33f2a1b49" expires_in: 299 scope: "read write" }

  6. Attempt to access a restricted resource using the access_token: http://localhost:8080/server/me?access_token=f853bcc5-7801-42d3-9cb8-303fc67b0453

  7. Receive an invalid token reply

    { error: "invalid_token" error_description: "Invalid access token: f853bcc5-7801-42d3-9cb8-303fc67b0453" }

  8. POST to the token uri again to refresh token: http://localhost:8080/server/oauth/token?grant_type=refresh_token&refresh_token=57100377-dea9-4df0-adab-62e33f2a1b49

  9. Receive a new token

    { access_token: "ed104994-899c-4cd9-8860-43d5689a9420" token_type: "bearer" refresh_token: "57100377-dea9-4df0-adab-62e33f2a1b49" expires_in: 300 scope: "read write" }

  1. 访问 oauth 授权 URI 以启动 OAuth2 流程:http://localhost:8080/server/oauth/authorize?response_type=code&client_id=client

  2. 重定向到登录页面:http://localhost:8080/server/login

  3. 使用代码参数处理批准并重定向到我配置的重定向页面:http://localhost:8080/client?code=HMJO4K

  4. 使用客户端 ID 和密码以及授权类型和代码,使用 Basic Auth 构建 GET 请求:http://localhost:8080/server/oauth/token?grant_type=authorization_code&code=HMJO4K

  5. 接收一个 access_token 并作为回报刷新令牌对象

    { access_token: "f853bcc5-7801-42d3-9cb8-303fc67b0453" token_type: "bearer" refresh_token: "57100377-dea9-4df0-adab-62e33f2a1b49" expires_in }: "29 write scopes_in

  6. 尝试使用 access_token 访问受限资源:http://localhost:8080/server/me?access_token=f853bcc5-7801-42d3-9cb8-303fc67b0453

  7. 收到无效的令牌回复

    { 错误:“invalid_token” error_description:“无效的访问令牌:f853bcc5-7801-42d3-9cb8-303fc67b0453”}

  8. 再次 POST 到令牌 uri 以刷新令牌:http://localhost:8080/server/oauth/token?grant_type=refresh_token&refresh_token=57100377-dea9-4df0-adab-62e33f2a1b49

  9. 接收新令牌

    { access_token: "ed104994-899c-4cd9-8860-43d5689a9420" token_type: "bearer" refresh_token: "57100377-dea9-4df0-adab-62e33f2a1b49" expires_in }: "read 30

I am really not sure what I am doing wrong, but it appears that everything other than accessing the restricted uri is working. Here is my configuration:

我真的不确定我做错了什么,但似乎除了访问受限制的 uri 之外的一切都在工作。这是我的配置:

@Configuration
public class Oauth2ServerConfiguration {

    private static final String SERVER_RESOURCE_ID = "oauth2-server";

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(SERVER_RESOURCE_ID);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and().requestMatchers()
                    .antMatchers("/me")
                .and().authorizeRequests()
                    .antMatchers("/me").access("#oauth2.clientHasRole('ROLE_CLIENT')")
            ;
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthotizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private ClientDetailsService clientDetailsService;

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

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                .withClient("client")
                    .resourceIds(SERVER_RESOURCE_ID)
                    .secret("secret")
                    .authorizedGrantTypes("authorization_code", "refresh_token")
                    .authorities("ROLE_CLIENT")
                    .scopes("read","write")
                    .redirectUris("http://localhost:8080/client")
                    .accessTokenValiditySeconds(300)
                    .autoApprove(true)
            ;
        }

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

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

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer.realm("oauth");
        }

        @Bean
        public ApprovalStore approvalStore() throws Exception {
            TokenApprovalStore store = new TokenApprovalStore();
            store.setTokenStore(tokenStore());
            return store;
        }

        @Bean
        public UserApprovalHandler userApprovalHandler() throws Exception {
            TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
            handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
            handler.setClientDetailsService(clientDetailsService);
            handler.setTokenStore(tokenStore());

            return handler;
        }
    }
}

Is there something I am missing or am I approaching this incorrectly? Any help would be greatly appreciated.

有什么我遗漏的还是我错误地处理了这个问题?任何帮助将不胜感激。

采纳答案by jyore

The problem ended up being that the resource server and the authorization server were not getting the same token store reference. Not sure how the wiring was not working correctly, but using a fixed object in the configuration class worked like a charm. Ultimately, I'll move to a persistence backed token store, which probably would not have had any issues.

问题最终是资源服务器和授权服务器没有获得相同的令牌存储引用。不确定接线如何不正常工作,但在配置类中使用固定对象就像一个魅力。最终,我将转向持久性支持的令牌存储,这可能不会有任何问题。

Thanks goes to @OhadR for the answer and the help!

感谢@OhadR 的回答和帮助!

Ultimately, I simplified the configuration, went thru the same workflow, and it worked out

最终,我简化了配置,通过了相同的工作流程,结果

@Configuration
public class Oauth2ServerConfiguration {

    private static final String SERVER_RESOURCE_ID = "oauth2-server";

    private static InMemoryTokenStore tokenStore = new InMemoryTokenStore();


    @Configuration
    @EnableResourceServer
    protected static class ResourceServer extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.tokenStore(tokenStore).resourceId(SERVER_RESOURCE_ID);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.requestMatchers().antMatchers("/me").and().authorizeRequests().antMatchers("/me").access("#oauth2.hasScope('read')");
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthConfig extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private AuthenticationManager authenticationManager;


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

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                .withClient("client")
                    .authorizedGrantTypes("authorization_code","refresh_token")
                    .authorities("ROLE_CLIENT")
                    .scopes("read")
                    .resourceIds(SERVER_RESOURCE_ID)
                    .secret("secret")
            ;
        }
    }
}

Anyone that stumbles upon this post, I recommend looking more at the unit tests for example rather than the full blown sparklr/tonr example, as it has a lot of extra configuration that are not necessarily needed to get started.

任何偶然发现这篇文章的人,我建议更多地查看单元测试,而不是完整的 sparklr/tonr 示例,因为它有很多额外的配置,不一定需要开始。

回答by OhadR

Your step #6 is wrong - the access token should not be sent in the URL as it is vulnerable this way. rathen than GET, use POST.

您的第 6 步是错误的 - 不应在 URL 中发送访问令牌,因为这样很容易受到攻击。而不是 GET,使用 POST。

Besides, I don't understand your step #1 - why do you call /oauth/authorize? it should be done implicitly when you try to get a protected resource. I mean, your flow should start with:

此外,我不明白你的第 1 步——你为什么要调用 /oauth/authorize?当您尝试获取受保护的资源时,它应该隐式完成。我的意思是,你的流程应该从:

Attempt to access a restricted resource using the access_token: http://localhost:8080/server/me

尝试使用 access_token 访问受限资源: http://localhost:8080/server/me

Then the negotiation will start "behind the scenes": a redirect to "/oauth/authorize" etc.

然后协商将在“幕后”开始:重定向到“/oauth/authorize”等。

In addition, in step #8, note that you are not asking for "another access token", but instead it is a request for "refresh token". As if your access-token was expired.

此外,在第 8 步中,请注意您不是在请求“另一个访问令牌”,而是请求“刷新令牌”。好像您的访问令牌已过期。

Note: The identity provider and the resource server should share the tokenStore! Read here: Spring Security OAuth2 pure resource server

注意:身份提供者和资源服务器应该共享 tokenStore!在这里阅读:Spring Security OAuth2 纯资源服务器

HTH

HTH

回答by fywe

This works for me:

这对我有用:

@Configuration
public class Oauth2ServerConfiguration {

    private static final String SERVER_RESOURCE_ID = "oauth2-server";

    @Autowired
    private TokenStore tokenStore;

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

    @Configuration
    @EnableResourceServer
    protected static class ResourceServer extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.tokenStore(tokenStore).resourceId(SERVER_RESOURCE_ID);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // ... Not important at this stage
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthConfig extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private AuthenticationManager authenticationManager;


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

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            //... Not important at this stage
        }
    }
}