Spring Security系列之Handler

Spring Security系列之Handler

码农世界 2024-06-12 后端 91 次浏览 0个评论

概述

与Spring、Spring MVC、Spring Boot一样,Spring Security里也有很多Handler接口、可以分为两大类,一类是普通的XxxHandler(见名知意),另一类是对应的ServerXxxHandler(RequestRejectedHandler除外)。

以AuthenticationSuccessHandler为例,Spring Security中用于处理认证成功事件的接口。通常与基于Servlet API的应用程序一起使用,如Spring MVC应用程序。当用户成功认证后,接口的实现类负责处理接下来的操作,如重定向到其他页面、生成认证成功的日志等。主要实现类是SavedRequestAwareAuthenticationSuccessHandler,它会将用户重定向到之前被拦截的原始请求地址。

而对应的ServerAuthenticationSuccessHandler,在Spring Security 5引入的新接口,用于处理WebFlux中的认证成功事件。WebFlux是Spring Framework 5中引入的反应式编程模型,用于构建响应式的、非阻塞的、事件驱动的应用程序。该接口的实现类负责发送响应给客户端,例如返回 JSON 格式的认证成功消息。

Spring Security里定义的Handler接口具体如下:

  • AuthenticationSuccessHandler
  • AuthenticationFailureHandler
  • LogoutHandler
  • LogoutSuccessHandler
  • AccessDeniedHandler
  • CsrfTokenRequestHandler
  • RequestRejectedHandler
  • ServerAuthenticationSuccessHandler
  • ServerAuthenticationFailureHandler
  • ServerLogoutHandler
  • ServerLogoutSuccessHandler
  • ServerAccessDeniedHandler
  • ServerCsrfTokenRequestHandler

    AuthenticationSuccessHandler

    AuthenticationSuccessHandler接口用来设置验证成功后的处理动作,源码如下:

    public interface AuthenticationSuccessHandler {
        default void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
            this.onAuthenticationSuccess(request, response, authentication);
            chain.doFilter(request, response);
        }
        void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException;
    }
    

    有两个同名onAuthenticationSuccess方法:default方法,用于处理特定的认证请求AuthenticationFilter;非default方法,则用来处理登录成功的具体事项。目前有3个实现类:

    • ForwardAuthenticationSuccessHandler:实现服务端跳转
    • SimpleUrlAuthenticationSuccessHandler:同时继承AbstractAuthenticationTargetUrlRequestHandler,通过其中的handle方法实现请求重定向
    • SavedRequestAwareAuthenticationSuccessHandler:继承自SimpleUrlAuthenticationSuccessHandler,在其基础上增加请求缓存的功能,可以记录之前请求的地址,进而在登录成功后重定向到一开始访问的地址。

      Spring Security系列之Handler

      开发者也可以配置自己的SavedRequestAwareAuthenticationSuccessHandler,方法如下:

      SavedRequestAwareAuthenticationSuccessHandler successHandler() {
      	SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler;
      	handler.setDefaultTargetUrl("/index");
      	handler.setTargetUrlParameter("target");
      	return handler;
      }
      

      ForwardAuthenticationSuccessHandler的onAuthenticationSuccess方法就一行:request.getRequestDispatcher(this.forwardUrl).forward(request, response);,即调用getRequestDispatcher方法进行服务端转发

      AuthenticationFailureHandler

      AuthenticationFailureHandler接口,用来设置用户验证失败后的处理动作,源码如下:

      public interface AuthenticationFailureHandler {
          void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException;
      }
      

      实现类有:

      • SimpleUrlAuthenticationFailureHandler:默认,如果指定failureUrl,则跳转到该URL,未指定则返回401错误代码。也可以通过配置forwardToDestination属性将重定向改为服务器端跳转
      • ForwardAuthenticationFailureHandler:不管报哪种AuthenticationException,总是重定向到指定的URL
      • DelegatingAuthenticationFailureHandler:代理类,可根据不同的AuthenticationException类型,设置不同的Handlers
      • ExceptionMappingAuthenticationFailureHandler:可以根据不同的AuthenticationException类型,设置不同的跳转URL
      • AuthenticationEntryPointFailureHandler:可通过AuthenticationEntryPoint来处理登录异常

        类继承图

        Spring Security系列之Handler

        DelegatingAuthenticationFailureHandler处理方法handle如下:

        // 维护一个map,key是具体的异常类型,value是特定的Handler
        private final LinkedHashMap, AuthenticationFailureHandler> handlers;
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        	for (Map.Entry, AuthenticationFailureHandler> entry : this.handlers.entrySet()) {
        		Class handlerMappedExceptionClass = entry.getKey();
        		if (handlerMappedExceptionClass.isAssignableFrom(exception.getClass())) {
        			AuthenticationFailureHandler handler = entry.getValue();
        			handler.onAuthenticationFailure(request, response, exception);
        			return;
        		}
        	}
        	this.defaultHandler.onAuthenticationFailure(request, response, exception);
        }
        

        值得注意的是,Delegating,代理的意思。此实现类里维护一个Map(此Map集合支持通过构造函数的入参这种方式来实例化Handler类),然后在核心方法里对Map里的key进行遍历,与方法入参里的exception对比,比对成功则加以处理。最后使用默认的Handler加以处理。这种思想,在下面的几个Handler里,几乎都是如此。不同的是Map的key不一样,核心方法名不一样(一般都命名为handle()),对比项不一样(有的是对比request)。不再赘述。

        LogoutHandler

        LogoutHandler接口,设置logout过程中必须处理动作,logout后的重定向建议使用LogoutSuccessHandler,源码如下:

        public interface LogoutHandler {
            void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication);
        }
        

        有如下几个实现类:

        • AbstractRememberMeServices:
        • CompositeLogoutHandler:
        • CookieClearingLogoutHandler:清除Cookie
        • CsrfLogoutHandler:通过CsrfTokenRepository清除CsrfToken
        • HeaderWriterLogoutHandler:
        • LogoutSuccessEventPublishingLogoutHandler:同时实现ApplicationEventPublisherAware,发布一个事件通知
        • OidcBackChannelLogoutHandler:
        • PersistentTokenBasedRememberMeServices
        • SecurityContextLogoutHandler
        • SpringSessionRememberMeServices
        • TokenBasedRememberMeServices

          CompositeLogoutHandler接口源码如下:

          private final List logoutHandlers;
          public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
          	for (LogoutHandler handler : this.logoutHandlers) {
          		handler.logout(request, response, authentication);
          	}
          }
          

          分析下来,与Delegating比较类似,不同的是,维护一个List,构造函数同样支持传入指定的List,然后在具体的handle方法里(此处是logout方法)for循环遍历list,分别使用具体的Handler来一一处理。类似地,下文也有几个CompositeXxxHandler,思想与此非常相似。

          LogoutSuccessHandler

          LogoutSuccessHandler接口,设置logout完成后需要处理动作,在LogoutHandler后被执行,LogoutHandler完成必要的动作(该过程不应该抛异常),LogoutSuccessHandler定位是处理后续更多的步骤,如重定向等,源码如下:

          public interface LogoutSuccessHandler {
              void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException;
          }
          

          有如下几个实现类:

          • SimpleUrlLogoutSuccessHandler:和上面的AuthenticationSuccessHandler非常类似,继承AbstractAuthenticationTargetUrlRequestHandler,通过其中的handle方法实现请求重定向
          • DelegatingLogoutSuccessHandler:
          • HttpStatusReturningLogoutSuccessHandler:设置状态码200
          • ForwardLogoutSuccessHandler:通过构造函数传入的目标URL,即targetUrl参数实现转发。

            AccessDeniedHandler

            AccessDeniedHandler接口,用来设置访问拒绝后的处理动作,源码如下:

            public interface AccessDeniedHandler {
                void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException;
            }
            

            有如下几个实现类:

            • NoOpAccessDeniedHandler:熟悉的NoOp。空实现,不做任何处理
            • AccessDeniedHandlerImpl:设置403错误码,然后转发请求,让下一个Handler来处理
            • DelegatingAccessDeniedHandler:
            • CompositeAccessDeniedHandler:组合模式,for循环遍历各个Handler实现类,依次调用其handle方法进行处理
            • InvalidSessionAccessDeniedHandler:清除缓存
            • ObservationMarkingAccessDeniedHandler:
            • RequestMatcherDelegatingAccessDeniedHandler:代理模式,维护一个map映射集合。根据具体的RequestMatcher匹配对应的Handler实现类,匹配成功则加以处理。最后使用defaultHandler加以处理。
            • OAuth2AccessDeniedHandler:spring-security-oauth2包里面的实现类,同时继承AbstractOAuth2SecurityExceptionHandler类,直接调用父类里的handle方法。

              CsrfTokenRequestHandler

              CSRF,Cross-Site Request Forgery,跨站请求伪造是一种攻击技术,攻击者利用用户在已登录的状态下发起的请求来执行非法操作。

              CsrfTokenRequestHandler是Spring Security种用于处理CSRF攻击的处理程序。具体来说,浏览器向服务器发送请求时,服务器会在响应中返回一个CSRF令牌。在后续的请求中浏览器将该令牌包含在请求中,服务器会验证该令牌的有效性,从而防止CSRF攻击。

              函数式接口,源码如下:

              @FunctionalInterface
              public interface CsrfTokenRequestHandler extends CsrfTokenRequestResolver {
                  void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken);
                  default String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
                      Assert.notNull(request, "request cannot be null");
                      Assert.notNull(csrfToken, "csrfToken cannot be null");
                      String actualToken = request.getHeader(csrfToken.getHeaderName());
                      if (actualToken == null) {
                          actualToken = request.getParameter(csrfToken.getParameterName());
                      }
                      return actualToken;
                  }
              }
              

              实现类包括:

              • CsrfTokenRequestAttributeHandler
              • XorCsrfTokenRequestAttributeHandler:执行异或xor运算

                RequestRejectedHandler

                位于org.springframework.security.web.firewall包路径下,可知与防火墙策略相关,用于处理请求被拒绝的场景。当请求因为安全策略而被拒绝时,其实现类负责向用户发送相应的错误信息或执行其他定制的行为。

                源码如下:

                public interface RequestRejectedHandler {
                    void handle(HttpServletRequest request, HttpServletResponse response, RequestRejectedException requestRejectedException) throws IOException, ServletException;
                }
                

                实现类包括:

                • DefaultRequestRejectedHandler:不做处理,抛出异常
                • HttpStatusRequestRejectedHandler:通过response.sendError(this.httpError);发送错误响应状态码,默认400,支持构造方法传参
                • ObservationMarkingRequestRejectedHandler:
                • CompositeRequestRejectedHandler:组合模式,for循环遍历列表里的所有实现类,一一加以处理。

                  ObservationMarkingRequestRejectedHandler的handle方法如下:

                  private final ObservationRegistry registry;
                  public void handle(HttpServletRequest request, HttpServletResponse response, RequestRejectedException exception) throws IOException, ServletException {
                  	Observation observation = this.registry.getCurrentObservation();
                  	if (observation != null) {
                  		observation.error(exception);
                  	}
                  }
                  

                  有点复杂。

                  ServerAuthenticationSuccessHandler

                  与AuthenticationSuccessHandler接口定义几乎一致,不同的是,只有一个非default方法,

                  HttpServletRequest request, HttpServletResponse response两个参数变成一个参数WebFilterExchange webFilterExchange,返回参数void也变成响应式的Mono

                  public interface ServerAuthenticationSuccessHandler {
                      Mono onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication);
                  }
                  

                  实现类:

                  • DelegatingServerAuthenticationSuccessHandler
                  • RedirectServerAuthenticationSuccessHandler
                  • WebFilterChainServerAuthenticationSuccessHandler

                    ServerAuthenticationFailureHandler

                    同上面的ServerAuthenticationSuccessHandler非常类似。实现类:

                    • RedirectServerAuthenticationFailureHandler
                    • ServerAuthenticationEntryPointFailureHandler

                      ServerLogoutHandler

                      与上面的非常相似,实现类:

                      • CsrfServerLogoutHandler
                      • DelegatingServerLogoutHandler
                      • HeaderWriterServerLogoutHandler
                      • OidcBackChannelServerLogoutHandler
                      • SecurityContextServerLogoutHandler
                      • WebSessionServerLogoutHandler:将WebFilterExchange里保存的WebSession做失效处理:exchange.getExchange().getSession().flatMap(WebSession::invalidate);

                        ServerLogoutSuccessHandler

                        源码比较简单,实现类:

                        • HttpStatusReturningServerLogoutSuccessHandler
                        • RedirectServerLogoutSuccessHandler

                          ServerAccessDeniedHandler

                          ServerAccessDeniedHandler接口源码如下:

                          public interface ServerAccessDeniedHandler {
                              Mono handle(ServerWebExchange exchange, AccessDeniedException denied);
                          }
                          

                          实现类:

                          • HttpStatusServerAccessDeniedHandler
                          • ServerWebExchangeDelegatingServerAccessDeniedHandler

                            ServerCsrfTokenRequestHandler

                            接口定义如下:

                            @FunctionalInterface
                            public interface ServerCsrfTokenRequestHandler extends ServerCsrfTokenRequestResolver {
                                void handle(ServerWebExchange exchange, Mono csrfToken);
                                default Mono resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) {
                                    Assert.notNull(exchange, "exchange cannot be null");
                                    Assert.notNull(csrfToken, "csrfToken cannot be null");
                                    return exchange.getFormData().flatMap((data) -> {
                                        return Mono.justOrEmpty((String)data.getFirst(csrfToken.getParameterName()));
                                    }).switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(csrfToken.getHeaderName())));
                                }
                            }
                            

                            `

                            参考

转载请注明来自码农世界,本文标题:《Spring Security系列之Handler》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,91人围观)参与讨论

还没有评论,来说两句吧...

Top