Spring Security filter探究

2022-08-05 13:48:52

Spring Security架构

在这里插入图片描述

从官方这张图可以看出,Spring Security是通过内部的过滤器链实现认证和授权逻辑的。Spring Security内部的过滤器是有先后顺序的。比如UsernamePasswordAuthenticationFilter如果认证成功,那么AnonymousAuthenticationFilter肯定就不需要设置匿名者了。所以UsernamePasswordAuthenticationFilter在AnonymousAuthenticationFilter之前。具体顺序可看官网说明。

最后两个filter

  先说倒数第一个Filter:SecurityInterceptor。这个filter是最终做出抉择的地方,是通过还是拒绝会在filter中做出决定。如果通过则完成了Spring Security内部的过滤器链,继续执行web容器的其它过滤器。如果拒绝则会抛出异常。抛出的异常则会被前一个filter也就是ExceptionTranslationFilter拦截到。
  ExceptionTranslationFilter就是倒数第二个filter。该filter的doFilter方法只是调用过滤器链的下一个filter也就是FilterSecurityInterceptor,但是会捕获所有异常,下面是简化版的异常处理方法,当然还有其它类型异常,Spring Security直接就抛出了不会做处理。

if(exceptioninstanceofAuthenticationException){}elseif(exceptioninstanceofAccessDeniedException){}

可以看出就是针对认证异常和授权异常。对于这两者都有默认的处理器,当然Spring Security也提供了覆盖的方式。就是对于httpSecurity的配置时做如下的配置

               httpSecurity.exceptionHandling().authenticationEntryPoint(xx).accessDeniedHandler(xx)

常用的UsernamePasswordAuthenticationFilter浅析

  当配置httpSecurity.loginForm的时候,就开启了UsernamePasswordAuthenticationFilter。但是否使用还要看登录url是否为该filter处理的url。默认配置为/login。当然也可以修改,就是使用如下配置指定。

httpSecurity.formLogin().loginProcessingUrl("xxx")

该filter默认就会执行配置的UserDetailsService的loadUserByUsername方法。该filter的dofilter在其父类中,下面是dofilter最后几句代码

catch(AuthenticationException failed){// Authentication failedunsuccessfulAuthentication(request, response, failed);return;}// Authentication successif(continueChainBeforeSuccessfulAuthentication){
   		chain.doFilter(request, response);}successfulAuthentication(request, response, chain, authResult);

可以看到认证失败则走unsuccessfulAuthentication处理。成功时如果continueChainBeforeSuccessfulAuthentication为true,才会继续走其它过滤器,而默认值为false。所以如果使用UsernamePasswordAuthenticationFilter处理登录,则默认是不走controller的,而是通过successfulAuthentication处理登录成功的响应。
为啥要这样设置呢?以下为个人见解:

  1. 我们知道filter的dofilter方法中chain.doFilter(request, response)后的代码是要等到servlet中的逻辑处理完成之后才会触发的。而在UserDetailsService的loadUserByUsername方法中已经完成了类似用户登录的校验及响应操作,所以没必要走controller层。

  2. 如果我们使用json格式提交,自定义一个类似UsernamePasswordAuthenticationFilter的filter,这里就叫JsonUsernamePasswordAuthenticationFilter。如果要提取用户名,则需要从request的输入流中读取,类似如下代码,然后使用json解析该字符串。

String body=StreamUtils.copyToString(request.getInputStream(),StandardCharsets.UTF_8);

因为流只能读取一次,所以如果继续让代码走到controller层定义的登录接口,那么就会报出400Bad Request错误,因为request中已无数据。

  1. 没找到如何设置UsernamePasswordAuthenticationFilter的该参数为true,也没必要。(^o^)

successfulAuthentication方法内部的默认successHandler会将context保存到session中,如果重写了其中的successHandler也没用,因为SecurityContextPersistenceFilter会在dofilter最后再次执行一次保存到session中如果之前没有保存的话。如下所示。

try{
	chain.doFilter(holder.getRequest(), holder.getResponse());}finally{
	repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());

当其它请求过来时,SecurityContextPersistenceFilter这个过滤器会从session中取出context设置到SecurityContextHolder中。
当然如果关闭了session的话

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

SecurityContextPersistenceFilter中的repo变量类型会发生变化,所以不会存入到session中。

  • 作者:sinat_33472737
  • 原文链接:https://blog.csdn.net/sinat_33472737/article/details/124462400
    更新时间:2022-08-05 13:48:52