一、前言
在spring boot整合Spring Security 之后,启动项目的时候就会默认进入Security的登录界面,默认访问所有请求都需要先登录,如下图所示:
在访问其他接口就会403的错误
二、原因
通过分析SpringSecurity请求与响应的一个流程可以得知其实在后台就是将请求经过一系列的过滤链之后在返回响应结果。如下图所示:
因此本次主要解释异常处理,所有我们进入到ExceptionTranslationFilter类中找到doFilter方法
这里可以很好的理解首先是对chain.doFilter(request, response) 进行一个异常捕获,然后在看是那种异常进行响应处理,通过断点分析可以得出
1 .Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex):
是获得所有异常对象causeChain 。
2. RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain):
根据指定类型AuthenticationException(认证)查找第一个异常对象,如果找到返回该对象,否则返回null
3.如过securityException是null,则进行根据AccessDeniedException查找异常对象并赋给securityException。
所以看最后handleSpringSecurityException(request, response, chain, securityException)这一行
很明显直接进入执行handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception)这条语句
进入这个函数通过段点分析,很明显
1.SecurityContextHolder.getContext().getAuthentication():
这条语句时从SecurityContext获取认证信息,由于没有登录,所以该认证信息是anonymousUser(匿名)
2.this.authenticationTrustResolver.isAnonymous(authentication):
这条语句就是判断是不是匿名用户了,很明显是返回True
3.if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication))
进行判断是不是匿名(true)用或已记住的用户(false),很明显返回true
接下来主要看条语句传了异常进去
具体的执行方法
在定义方法参数的时候用了AuthenticationException类型进行接受,通过查看继承与实现关系可以得知InsufficientAuthenticationException继承了AuthenticationExceptsd在执行这条语句时候可以得出
它是来自AuthenticationEntryPoint接口的commence方法
该接口有6个实现类,而403异常信息来字Http403ForbiddenEntryPoint这个实现类
Http403ForbiddenEntryPoint实现类
由此可见403是在这里对response进行设置的。
三、自定义进行异常处理
通过以上原因进行分析我们得知了具体403在Http403ForbiddenEntryPoint对response进行一个设置的,而Http403ForbiddenEntryPoint实现了AuthenticationEntryPoint接口,所以我们也可以自己定义一个实现AuthenticationEntryPoint接口的一个实现类如下图所示:
重写接口中的commence方法即可。
最后在SecurityConfig类中启用即可
http.exceptionHandling(except -> except.authenticationEntryPoint(MyAccessDeniedHandler))
四、结果测试
未自定义异常前:
自定义异常后(返回了Json):
五、总结
对于异常处理:
如果用户未登录(anonymous),会先触发AuthenticationEntryPoint(认证)。
如果用户已登陆(authenticated),会触发AccessDeniedHandler(权限)。
按照以上的方式去处理AccessDeniedHandler异常,只需在自定义类上再去实现 AccessDeniedHandler,在重写handle方法即可。