Spring Security Oauth2 无法访问到资源服务接口问题分析

1.情况说明

在搭建认证服务器,完成Spring Security Oauth2的四种模式的获取token的测试后,预示着我们已经将认证服务器搭建成功,紧接着我们搭建了一个用于测试的资源服务。相关的配置和测试接口如下。

1.1.资源服务配置

  1. 配置当前资源服务的信息;
  2. 配置当前资源服务验证token的方式。
@Configuration
@EnableResourceServer
//激活方法上的PreAuthorize注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    //公钥
    private static final String RESOURCE_ID = "eden-life";


    /**
     * 资源服务的安全配置
     *
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
                .resourceId(RESOURCE_ID)
                .tokenServices(tokenServices())
                .stateless(true);
    }

    /***
     * 配置资源服务的令牌验证服务
     * @return
     */
    @Bean
    public ResourceServerTokenServices tokenServices() {
        RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setCheckTokenEndpointUrl("http://localhost:9949/oauth/check_token");
        tokenServices.setClientId("eden");
        tokenServices.setClientSecret("eden");
        return tokenServices;
    }
}

这里采用RemoteTokenService请求远端认证服务器的方式进行token的校验。因为我们资源服务和认证服务并不是在同一个服务里,而是分开的;我们也没有使用JWT来生成token,token只是随机的UUID字符串,没有包含用户信息等。

如果在一起,那我们可以使用DefaultTokenService的token服务方式

1.2.资源服务的测试接口

我们提供两个测试接口,第一个是需要经过鉴权才可以访问的;第二个没有设置访问权限。

@RestController
@RequestMapping
public class TestController {

    @GetMapping("/life/test/test1/{str}")
    @PreAuthorize("hasAnyAuthority('admin')")
    public String test(@PathVariable String str) {
        return str;
    }

    @GetMapping("/life2/test/test2/{str}")
    public String test2(@PathVariable String str) {
        return str;
    }
}

1.3.问题

按照正常理解,上面两个接口应该分别如此访问:

  1. 第一个接口,请求头需要携带token,且用户的权限里应该包含admin 权限;
  2. 第二个接口,可以直接访问,无需任何授权。

我们来测试一下。

2.测试

2.1.接口一

 和猜测的一样,我们携带token顺利请求到了资源接口。

2.2.接口二

 是不是有些意外?我们被Oauth2拦截了

2.2.1.增加安全访问的限制策略

这里增加的配置的意思是,当前资源服务只有与/life/**路径相匹配的请求才会由Oauth2的过滤器链进行处理,其他请求路径不由Oauth2的过滤器链进行处理。

    /**
     * Http安全配置,对每个到达系统的http请求链接进行校验
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                //限制资源服务直接管/life/**匹配的路径
                .requestMatchers().antMatchers("/life/**")
                .and()
                .authorizeRequests()
                .antMatchers("/life/**").authenticated()
                .anyRequest().permitAll();
    }

再次进行测试:

因为经过我们上一步的配置,Oauth2的过滤器链并不处理/life2的请求,所以这次使用的过滤器链其实是Security的过滤器链,在这个过滤器链中,抛出了AccessDeniedException。所以这一次, 我们是被Security拦截了。既然如此,那我们就增加Security的配置。

正常配置资源服务一般是需要Security配置的,我们只是为了引出来这个知识点,所以在一开始并未做这个配置。

2.2.2.增加Security配置

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class LifeWebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 配置http安全拦截机制
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                // /life/**的请求必须认证通过
                .antMatchers("/life/**").authenticated()
                .anyRequest().permitAll();
    }
}

再次测试,我们发现已经可以访问到资源接口了。

为什么加了一个Security的配置就可以访问资源接口了呢?我们就来分析这个。

3.Debug分析

我们配置security和Oauth2的配置时,分别继承了一个Adapter,即WebSecurityConfigurerAdapter与ResourceServerConfigurerAdapter。下面借用一下一篇博文中的片段。

他们其实本质就是Adapter,只是二者所属的功能模块不同,而且二者的加载顺序是不一样的。

ResourceServerConfigurerAdapter是优先于WebSecurityConfigurerAdapter(这个特点要记住后面会用到,当然了这个顺序也是可以修改的!这个默认加载顺序情况下先加载的Adapter在处理相同路径情况下会覆盖后续加载的路径,造成后加载的失效!),那么我们这里WebSecurityConfigurerAdapter和ResourceServerConfigurerAdapter都存在那么我们系统中就会有两个Adapter,我们每声明一个*Adapter类,都会产生一个filterChain。

一个request(匹配url)只能被一个filterChain处理,这就解释了为什么二者Adapter同时在的时候处理相同请求路径时,后者默认为什么会失效的原因。我们可以在FilterChainProxy中的getFilters(HttpServletRequest request)方法中可以看到有哪些filter chain,并处理哪些url。

当发出接口二的请求时,我们可以在FilterChainProxy中看到有两个过滤器链。第一个是Oauth2的,第二个是security的,并且第一个可以拦截处理的路径为/life/**,第二个是拦截所有请求。Oauth2的过滤器链是在前面的。

 我们的请求路径是/life2,经过匹配,第一个过滤器链肯定是不符合条件的,因此最终执行的是Security的过滤器链。

由于Security配置了对/life/**之外的请求全部放行,我们也就能顺利请求到资源接口。

 

4.Oauth2的过滤器链

对于Oauth2来说,他的过滤器链中最重要的过滤器是OAuth2AuthenticationProcessingFilter。他的主要操作我们可以看一下,doFilter方法中一进来就是要获取请求头中的的token,但是请求头中并没有,继续放行。

直至最终到了FilterSecurityIntercepter,抛出了AccessDeniedException

 ExceptionTranslationFilter捕获AccessDeniedException,并最做了最终的响应处理。

如果请求头中没有token,走Oauth2的过滤器链是根本走不通的。

那么什么情况下,才会不走OAuth2的过滤器链呢?也就是在请求和过滤器链不能match的时候。

5.结论

  1. 资源服务要配置需要保护的、明确的、合理范围的资源路径,不写或者写/**都可能造成问题。

如下图圈出的位置:

因为Oauth2的过滤器链的顺序在前,所以如果你不在ResourceServerConfig中设置当前资源服务所保护的路径,那Oauth2就会对所有资源都进行拦截,但有些资源确实是不需要让这个资源服务进行处理的,所以会造成异常的授权判断。

        2.当资源服务的过滤器链都不能处理时,才会轮到Security的过滤器链,当然得是能match上才行。

所以,如果你发现自己的资源服务的接口控制不符合预期,或者感觉配置的Security配置好像没有生效似的,那么很有可能就是因为没有用到Security的过滤器链。建议你将Security的安全访问策略的配置挪到资源服务的配置上去,或者两个config都配置上。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值