使用正确的访问令牌访问 OAuth2 保密资源时遇到问题

Posted

技术标签:

【中文标题】使用正确的访问令牌访问 OAuth2 保密资源时遇到问题【英文标题】:Trouble accessing OAuth2 secred resource using correct access token 【发布时间】:2015-09-21 13:25:05 【问题描述】:

我正在使用 Spring 4.1.5.RELEASE、Spring Security 3.2.5.RELEASE 和 OAuth2 2.0.7.RELEASE。我正在使用 JdbcTokenStore 并将其保存在我的 oauth_client_details 表中……

client_id = dave
resource_ids = /dashboard
scope = read,write,delete
authorized_grant_types = implicit,password,client_credentials
web_server_redirect_uri = /redirect
authorities = ROLE1,ROLE2,ROLE3
access_token_validity = 3600
refresh_token_validity = 3600

但我无法访问我的安全资源 /dashboard。下面我提出获取访问令牌的请求……

Daves-MacBook-Pro:biv2 davea$ curl -v -d grant_type=password \
        -d username=ROLE3 \
        -d password=dave \
        -u dave:davesecret \
        http://localhost:8080/mycontext-path/oauth/token
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'dave'
> POST /mycontext-path/oauth/token HTTP/1.1
> Authorization: Basic YmltOmJpbXNlY3JldA==
> User-Agent: curl/7.40.0
> Host: localhost:8080
> Accept: */*
> Content-Length: 50
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 50 out of 50 bytes
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Cache-Control: no-store
< Pragma: no-cache
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 03 Jul 2015 16:33:39 GMT
< 
* Connection #0 to host localhost left intact
"access_token":"2648069d-607d-449d-af10-cb38d5fd7614","token_type":"bearer","expires_in":470,"scope":"delete read write"

然后我尝试使用该访问令牌来获取我的资源 /dashboard,但我正在重定向回我的登录页面……

Daves-MacBook-Pro:biv2 daveacurl -v -H 'Authorization: Bearer 2648069d-607d-449d-af10-cb38d5fd7614' http://localhost:8080/mycontext-path/dashboard
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /mycontext-path/dashboard HTTP/1.1
> User-Agent: curl/7.40.0
> Host: localhost:8080
> Accept: */*
> Authorization: Bearer 2648069d-607d-449d-af10-cb38d5fd7614
> 
< HTTP/1.1 302 Found
< Server: Apache-Coyote/1.1
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Set-Cookie: JSESSIONID=1EF45E8864F5A03CA0D2895E8E851E87; Path=/mycontext-path/; HttpOnly
< Location: http://localhost:8080/mycontext-path/login
< Content-Length: 0
< Date: Fri, 03 Jul 2015 16:34:06 GMT
< 
* Connection #0 to host localhost left intact

我错过了什么?下面是我如何配置我的 OAuth2Config 以及我的 Spring 安全配置。

@Configuration
@ComponentScan
@EnableAutoConfiguration
@RestController
public class Application 

    public static void main(String[] args) 
        SpringApplication.run(Application.class, args);
    

    @Configuration
    @EnableAuthorizationServer
    // [1]
    protected static class OAuth2Config extends
            AuthorizationServerConfigurerAdapter 

        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private DataSource dataSource;
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Autowired
        private ClientDetailsService clientDetailsService;
        @Autowired
        private TokenStore tokenStore;

        @Override
        // [2]
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                throws Exception 
            endpoints.tokenStore(tokenStore)
            .userApprovalHandler(userApprovalHandler())
            .authenticationManager(authenticationManager);
        

        @Override
        // [3]
        public void configure(ClientDetailsServiceConfigurer clients)
                throws Exception 
            // @formatter:off
            clients.jdbc(dataSource);
        

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

        @Bean
        public TokenApprovalStore tokenApprovalStore() 
            TokenApprovalStore tokenApprovalStore = new TokenApprovalStore();
            tokenApprovalStore.setTokenStore(tokenStore);
            return tokenApprovalStore;
        

        @Bean
        public OAuth2RequestFactory getOauth2RequestFactory()
        
            return new DefaultOAuth2RequestFactory(clientDetailsService);
           // getOauth2RequestFactory

        @Bean
        public UserApprovalHandler userApprovalHandler() 
            final TokenStoreUserApprovalHandler userApprovalHandler = new TokenStoreUserApprovalHandler();
            userApprovalHandler.setRequestFactory(getOauth2RequestFactory());
            userApprovalHandler.setTokenStore(tokenStore);
            return userApprovalHandler;
           // userApprovalHandler

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) 
            oauthServer.realm("abcdefgh/client");
               
    

Spring 安全配置:

@Configuration
@EnableWebSecurity
@ComponentScan(basePackages="com.myproject", excludeFilters=@ComponentScan.Filter(Controller.class))
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    private static final String ROLE1 = "ROLE1";
    private static final String ROLE2 = "ROLE2";
    private static final String ROLE3 = "ROLE3";

    @Resource(name="userDetailsService")
    private UserDetailsService userDetailsService;

    @Resource(name="jsonAuthenticationSuccessHandler")
    private daveAuthenticationSuccessHandler authSuccessHandler;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    

    @Override
    public void configure(HttpSecurity http) throws Exception 
        http.addFilterBefore(authenticationFilter(), JSONUsernamePasswordAuthenticationFilter.class);
        http
            .authorizeRequests()
                .antMatchers("/403").permitAll()
                .antMatchers("/login/**").permitAll()
                .antMatchers("/resources/**").permitAll()
                // TODO: Add secured patterns
                .antMatchers("/ROLE3/**").hasRole(ROLE3)
                .antMatchers("/common/**").hasAnyRole(ROLE3, ROLE2, ROLE1)
                .antMatchers("/ROLE1/**").hasAnyRole(ROLE3, ROLE2, ROLE1)
                .antMatchers("/ROLE2/**").hasAnyRole(ROLE3, ROLE2)
                // Allow access to all other resources only if authenticated
                .antMatchers("/*/**").fullyAuthenticated()
            .and().formLogin()
                .loginPage("/login")
                .failureUrl("/login?error")
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(authSuccessHandler)
            .and().logout().logoutSuccessUrl("/login?logout")
            .and().exceptionHandling().accessDeniedPage("/403")
            .and().csrf().disable();
    

    @Bean(name="passwordEncoder")
    public PasswordEncoder passwordEncoder() 
        return new StandardPasswordEncoder();
    

    @Bean
    public JSONUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception 
        final JSONUsernamePasswordAuthenticationFilter authFilter = new JSONUsernamePasswordAuthenticationFilter();
        //authFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login","POST"));
        authFilter.setAuthenticationManager(authenticationManagerBean());
        authFilter.setAuthenticationSuccessHandler(authSuccessHandler);
        authFilter.setUsernameParameter("username");
        authFilter.setPasswordParameter("password");
        return authFilter;
    

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


【问题讨论】:

使用 .antMatchers("/*/**").fullyAuthenticated() 访问仪表板,并且您的令牌具有权限 ROLE1/2/3。如果添加 .antMatchers("/dashboard").hasAnyRole(ROLE3, ROLE2, ROLE1) 会发生什么(只是看看它是否真的是一个令牌问题)?另外我想知道您的 403 页面是做什么的:在配置的另一部分不会重定向到您的登录页面?是否记录了任何异常? 【参考方案1】:

在 OAuth2 中受保护的资源是 ResourceServer 的一部分。

spring-security-oauth2 使用OAuth2AuthenticationProcessingFilter 处理 OAuth2 身份验证。

@EnableWebSecurity 在您的 SecurityConfig 类中还不够。

你需要用@EnableResourceServer注释它,扩展ResourceServerConfigurerAdapter会更容易配置相应的http安全。

见Reference documentation

【讨论】:

以上是关于使用正确的访问令牌访问 OAuth2 保密资源时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

如果我的用户会话持续 30 分钟,我的 Oauth2 访问令牌应该持续的正确时间是多少?

Oauth2:资源服务器应该如何知道访问令牌是不是有效?

带有 Spring Boot REST 应用程序的 OAuth2 - 无法使用令牌访问资源

使用 oauth2 休息服务:无法找到令牌的访问令牌

Oauth2、范围和用户角色

Spring Boot 2 OAuth2 资源服务器不访问授权服务器进行访问令牌验证