背景描述

最近在写一个前端端分离的微服务项目,使用到了网关zuul,然后网关的权限控制是通过springsecurity来实现的,真是踩了很多坑。

问题描述

项目配置

  1. 因为要进行登录认证,就放行了一部分url无需认证权限控制。
  2. 然后其他的所有url都需要进行认证权限控制。
  3. 配置代码如下:
@Configuration
public class SecurityGateway extends WebSecurityConfigurerAdapter {
   
// 指定要忽略的路径
private static final String[] IGNORE_PATH = {
            "/maple_gateway/", "/maple_gateway/index", "/maple_gateway/login", "/maple_gateway/error.html",
            "/maple_gateway/api/user_api/user_common/v1/auth/get_verify_code",
            "/maple_gateway/api/user_api/user_common/v1/auth/check_verify_core"
    };


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        		// 配置忽略url
                .antMatchers(IGNORE_PATH).permitAll()
                // 其他所有请求都需要认证
                .anyRequest().authenticated()

}

抛出异常

  1. InsufficientAuthenticationException,这个异常是说权限不足,没有足够的权限访问资源。
org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
  1. 但其实这个不是真的的异常,真正的异常是 org.springframework.security.access.AccessDeniedException: Access is denied
    下面来进入到分析过程:

异常分析

由于springsecurity的异常处理和mvc的异常处理不一样,认证类异常和权限异常并不能被全局异常捕获,而是他内部自己处理的。

反向追踪链路

在发生异常时,会进入到ExceptionTranslationFilter (异常转换过滤器),将发生的异常转换为认证或者权限异常。
在这里插入图片描述
可以看到,在处理异常是,最初的异常是AccessDeniedException,然后将它转换为了InsufficientAuthenticationException异常。

然后在向上追踪,定位发生异常的过滤器。过滤器链在执行某一个过滤器方法是,发生了异常。
在这里插入图片描述

思路分析

现在已经明确是在执行某一个过滤器的方法是发生异常了,由于确实对springsecurity不太熟悉,导致debug的时候走了很多弯路。

  1. 首先,我已经将本次请求的url添加到忽略名单里了,其实这些过滤器是不会执行的。所有最开始我的debug思路错了。问题的本质是我的忽略url没有生效,而不是过滤器产生异常。
  2. 所有正确的做法应该是先去定位过滤器忽略指定url的逻辑在那里,然后去看是什么问题。
  3. 还要说明的一点是,正常来说,指定了正确忽略的url,内置的一些过滤器不会走,但是你自定义的,实现了OncePerRequestFilter的过滤器还是会走的,所有需要自己去过滤。

好了,下面先来说我的错误debug思路

错误Debug
  1. 我认为是发生异常的原因是我的权限问题,所有我定位到了发生异常的起点,如下图所属:

在这里插入图片描述
只要当访问策略投票机制返回-1,则会抛出异常。继续往下
‘’
‘’
‘’
在这里插入图片描述
到达三元表达式,这里也就是前面的表达式返回false就会抛出异常。继续:
‘’
‘’
‘’
在这里插入图片描述
这里最关键的就是框中的代码,其实这里已经将结果求出来了,为false,会抛出异常,下面那个只是去做类型转换的,现在我们要搞清楚的是为什么会出现false。继续,:
‘’
‘’
‘’
在这里插入图片描述
中间稍微省去了一部分,但是这里是关键,这个方法名已经很见名知意了,读取属性值,现在的属性是authenticated,就是读取他的属性,意思是否已经认证,返回true表示已经认证,也就是上面方面的返回值也就是true,也就不会抛出异常了;但是现在显然不是true,我们继续来看这个属性是怎么拿到的
‘’
‘’
‘’
在这里插入图片描述
可以看到,这里已经求出来是否认证了,false,未认证;这里其实就是用反射去调用了一个方法,isAuthenticated,是否通过身份验证,继续,为什么会返回false。
‘’
‘’
‘’
在这里插入图片描述
结果很清晰,是否认证过的依据是是否是匿名访问,我这里是忽略url,就是要在登录之前访问,所以肯定就是匿名的访问,所以返回了true,是否认证返回了false,然后投票返回了-1,然后跑出来异常。

正确Debug
  1. 之所以说上面的分析是错误的,是因为这里并没有忽略url的逻辑判断,并不是我想要的东西,所以其实问题不是出在这里,而是在更前面。
  2. 不知道有没有有没有人注意到上面的分析过程,其实就是取属性authenticated的值,表示是不是已经认证过了,那这不是很奇怪吗,我配置了过滤指定url,还是走了过滤器,需要判断认证。
  3. 其实根本不需要,一开始去获取authenticated属性的值就是错的。

重新查看投票方法org.springframework.security.access.vote.AffirmativeBased.decide
在这里插入图片描述
可以看到,configAttributes这里list里面存放的就是我们要求的属性值,仔细去看,有一个key是authenticated,也就是我们要求查用户是否认证,但是不对啊,我请求的url配置了忽略认证的,怎么他的匹配模式是anyRrequest,这明显和我的配置不对啊。现在可以知道,是这个配置出了问题,我们去看这个list是怎么来的:
‘’
‘’
‘’
在这里插入图片描述
向上一层,可以发现是这里获取到的配置,继续:
‘’
‘’
‘’
在这里插入图片描述
现在是不是很清晰了,这个map是不是很熟悉,这就是我们开始配置的过滤url,然后这里就是当前请求嫩能不能和配置的忽略请求匹配的上,如果能配置上,就获取他对应的value。但是很奇怪的时,我当前请求居然和配置的对不上。

在这里插入图片描述
不能说完全一样吧,也就是基本相同,这居然匹配不上,去看看他的matches方法怎么实现的。
‘’
‘’
‘’在这里插入图片描述
水落石出了,我靠,他里面居然是用的getRequestPath,获取到的uri是没有context前缀的,因为我这个项目是微服务的,所有我加了网关加了前缀,结果没有匹配上。

解决

  1. 配置的忽略url去掉网关前缀。
    在这里插入图片描述
    现在可以看到,获取到的属性就正确了,不会在去验证权限。也就不会抛出权限不足的异常。

然后在说明一点,自己定义的过滤器还是会走,比如我这里的AuthFilter,当前的url也是忽略url,但是也还是走了过滤器,也能是我的方法不对,知道的可以告诉我。
在这里插入图片描述

总结

  1. springsecurity认证忽略的url是不能包含context前缀的,否则匹配不上。
  2. 自定义的放行策略逻辑在org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource#getAttributes.
  3. 实现OncePerRequestFilter的自定义过滤器不会忽略配置的url,还是会执行过滤器。
  4. 针对于【3】的解决方案:SpringSecurity 配置permitAll之后仍然会走自定义过滤器Filter的问题
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐