11

引言

在试题系统开发过程中,认证方式越来越完善,也对Spring Security有了更加深刻的理解。

本文,我们一起来领略Spring Security的设计原理。

原理

必备基础

ServletJava Web领域中的软件开发规范,Tomcat是实现Servlet规范的Java Web服务器。

package javax.servlet;

public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();
}

Servlet长这样,生命周期十分简单,初始化init、业务逻辑service、销毁destroy

简单来说:我们根据Servlet接口开发我们的应用,服务器开发商根据Servlet接口开发Servlet服务器。

image.png

前端HTTP请求,Tomcat选择路由匹配的Servlet,如果已经实例化,直接调用service;如果未实例化,实例化后调用service,处理完之后返回响应。

图中示例的url统一采用小写,关于url大小写的问题,以下是结论,虽然有些url是大小写不敏感的,但是这使得表示唯一性变得困难,我们通常应该认为大小写是敏感的。

There may be URLs, or parts of URLs, where case doesn't matter, but identifying these may not be easy. Users should always consider that URLs are case-sensitive.

image.png

为了在Servlet前后执行其他逻辑,定义了Filter接口,在请求前后都会执行。

原理分析

Spring Security官方文档摘抄的一张图:

image.png

Spring Security的原理其实就是通过Servlet中的Filter技术进行实现的,通过一系列内置的或自定义的安全Filter,实现接口认证与授权。

image.png

Spring Security官方也给出了默认Filter的执行顺序,先执行认证的过滤器,后执行授权的过滤器,其中就有我们常用的UsernamePasswordAuthenticationFilterBasicAuthenticationFilter等等。

阅读源码一个Filter源码即可理解整个认证架构设计。

注:以下代码中部分无关代码已被删减。

public class BasicAuthenticationFilter extends OncePerRequestFilter {

     @Override
     protected void doFilterInternal(HttpServletRequest request,
               HttpServletResponse response, FilterChain chain) {
          try {
               /** 从请求中获取用户名密码信息 */
               UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
               /** 如果没有相关信息,说明不是此种认证方式,执行后续过滤器 */
               if (authRequest == null) {
                    chain.doFilter(request, response);
                    return;
               }
               /** 获取用户名 */
               String username = authRequest.getName();
               /** 判断该用户是否需要认证 */
               if (authenticationIsRequired(username)) {
                    /** 尝试使用 token 进行认证 */
                    Authentication authResult = this.authenticationManager
                              .authenticate(authRequest);
                    /** 认证成功,将认证结果置入上下文 */
                    SecurityContextHolder.getContext().setAuthentication(authResult);
                    /** 认证成功相关回调 */
                    this.rememberMeServices.loginSuccess(request, response, authResult);
                    onSuccessfulAuthentication(request, response, authResult);
               }
          }
          catch (AuthenticationException failed) {
               /** 认证失败,清空当前上下文信息 */
               SecurityContextHolder.clearContext();
               /** 认证失败相关回调 */
               this.rememberMeServices.loginFail(request, response);
               onUnsuccessfulAuthentication(request, response, failed);
               /** 如果需要忽略失败,则继续执行后续过滤器 */
               if (this.ignoreFailure) {
                    chain.doFilter(request, response);
               }
               /** 否则开始执行新的认证方案 */
               else {
                    this.authenticationEntryPoint.commence(request, response, failed);
               }
               return;
          }
          /** 本过滤器执行完毕,执行后续过滤器 */
          chain.doFilter(request, response);
     }
}

其实很简单是不是?

添加自定义认证逻辑

如果默认的验证方式不满足要求,要怎么添加自定义验证方式呢?其实只需要添加自定义的Filter即可。

就比如说常见的短信验证码登录方式:

image.png

默认不支持短信方式,我们可以在过滤器链中植入一个自定义的短信验证过滤器,认证成功后设置认证信息即可。

SecurityContextHolder.getContext().setAuthentication(authResult);

Reactive

这个是上个月遇到的问题,尝试了一下OAuth 2.0认证架构。

image.png

其实这个架构很普遍,许多项目都采用该种架构。只不过都是采用Spring Cloud Netflix Zuul + Spring Security Resource Server的实现。

这里我尝试将Zuul换成Spring Cloud Gateway,因为Zuul的阻塞IO在网关层面实在太影响性能了。

前面已经说了,Spring Security中的认证与授权方式是通过Servlet技术套装中的Filter实现的,Tomcat提供了Servlet的运行环境。

Spring Cloud Netflix Zuul集成Spring Boot Starter Web也就是默认的Tomcat实现网关,所以Zuul中是有Servlet的运行环境的。

而非阻塞的Spring Cloud Gateway基于Spring WebFlux框架,不支持Servlet,底层采用高性能的Netty服务器。

Spring WebFluxSpring MVC的对比请看下图:

image.png

所以当Spring SecuritySpring Cloud Gateway集成时,就会出错,Spring Security需要的Servlet环境没有被满足。

这也是之前对Reactive的理解不到位而引起的错误,WebFlux环境下,应该集成Spring Security Reactive,而非默认的Spring Security

总结

经资料查阅,高性能的 Netty十分受各大互联网企业欢迎,TwitterFacebook、苹果、微博都在使用Netty,具体Netty为什么更适合高并发?以后我们一起学习。


张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。