Bootstrap

Spring Security 6

简介

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。

WEB 安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。

历史

“Spring Security 开始于 2003 年年底,““spring 的 acegi 安全系统”。 起因是 Spring 开发者邮件列表中的一个问题,有人提问是否考虑提供一个基于 spring 的安全实现。

Spring Security 以“The Acegi Secutity System for Spring” 的名字始于 2013 年晚些时候。一个问题提交到 Spring 开发者的邮件列表,询问是否已经有考虑一个机遇 Spring 的安全性社区实现。那时候 Spring 的社区相对较小(相对现在)。实际上 Spring 自己在 2013 年只是一个存在于 ScourseForge 的项目,这个问题的回答是一个值得研究的领域,虽然目前时间的缺乏组织了我们对它的探索。

6 上新增的功能

  • Core

    • gh-12233 - 安全授权管理器允许自定义底层授权管理器
    • GH-12231 - 添加颁发机构集合授权管理器
  • OAuth 2.0

    • gh-12604 - 支持 AuthnRequestSigned 元数据属性
    • gh-12846 - 元数据支持多个实体和实体描述符
    • gh-11828 - (文档) - 将 saml2 元数据添加到 DSL
    • gh-12843 - (文档) - 允许从注销请求中推断信赖方
    • gh-10243 - (文档) - 允许从 SAML 响应推断信赖方
    • gh-12842 - 添加信赖方注册占位符解析组件
    • gh-12845 - 支持在已注销后发出注销响应
  • Observability

    • gh-12534 - 自定义身份验证和授权观察约定
  • Web

    • gh-12751 - 添加请求匹配器工厂类
    • gh-12847 - 通过 And 和 OrRequestMatcher 传播变量
  • Docs

    • GH-13088 - (文档) - 重新访问授权文档
    • gh-12681 - (文档) - 重新访问会话管理文档
    • gh-13062 - (文档) - 重新访问注销文档
    • gh-13089 - 重温 CSRF 文档

    相关概念

  • 主体
    英文单词:principal

    使用系统的用户或设备或从其他系统远程登录的用户等等。简单说就是谁使用系统谁就是主体。

  • 认证

    英文单词:authentication

    权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是“主体”证明自己是谁。
    笼统的认为就是以前所做的登录操作。

  • 授权

    英文单词:authorization

    将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统中特定功能的能力。

注解使用

  • @Secured:判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
  • @PreAuthorize 注解适合进入方法前的权限验证,
  • @PostAuthorize 在方法执行后再进行权限验证,适合验证带有返回值的权限
  • @PostFilter 过滤注解,权限验证之后对数据进行过滤 留下满足要求的数据
  • @PreFilter 进入控制器之前对数据进行过滤

https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html

责任链

spring security 采用的时责任链的设计模式,再了解他之前,先了解下此种设计模式

责任链设计模式是一种行为型设计模式,它将请求的发送者和接收者解耦,将多个处理对象连成一条责任链,依次处理请求,直到请求被处理或者到达责任链的末尾。该模式常用于日志记录、权限验证、请求过滤等场景。

角色介绍

  • 抽象处理者(Handler)

抽象处理者定义了一个处理请求的接口,并保存下一个处理对象的引用。它可以是抽象类或接口。

public abstract class Handler {
    protected Handler nextHandler;

    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    public abstract void handleRequest(Request request);
}
  • 具体处理者(ConcreteHandler)

具体处理者实现了抽象处理者的接口,并处理它所负责的请求。如果不能处理该请求,则将请求传递给下一个处理对象。

public class ConcreteHandlerA extends Handler {
    @Override
    public void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE_A) {
            // 处理请求
        } else {
            // 无法处理该请求,将请求传递给下一个处理对象
            if (nextHandler != null) {
                nextHandler.handleRequest(request);
            } else {
                // 如果没有下一个处理对象,则输出日志
                System.out.println("No handler available for request.");
            }
        }
    }
}
  • 客户端(Client)

客户端创建请求对象,并将其发送给第一个处理对象。客户端可以根据需要自定义处理对象的顺序,也可以动态地增加或删除处理对象。

public class Client {
    public static void main(String[] args) {
        // 创建请求对象
        Request request = new Request(RequestType.TYPE_A, "Request A");

        // 创建处理对象
        Handler handlerA = new ConcreteHandlerA();
        Handler handlerB = new ConcreteHandlerB();
        Handler handlerC = new ConcreteHandlerC();

        // 构建责任链
        handlerA.setNextHandler(handlerB);
        handlerB.setNextHandler(handlerC);

        // 发送请求
        handlerA.handleRequest(request);
    }
}

SpringSecurity 的过滤器

Spring Security 的过滤器链是配置在 SpringMVC 的核心组件 DispatcherServlet 运行之前。也就是说,请求通过 Spring Security 的所有过滤器, 不意味着能够正常访问资源,该请求还需要通过 SpringMVC 的拦截器链。

Security Filter 通过 SecurityFilterChain 被插入到 FilterChainProxy 中。可以通过 FilterOrderRegistration 查看过滤器的顺序:28 个内置过滤器

FilterOrderRegistration() {
    Step order = new Step(INITIAL_ORDER, ORDER_STEP);
    put(DisableEncodeUrlFilter.class, order.next());
    put(ForceEagerSessionCreationFilter.class, order.next());
    put(ChannelProcessingFilter.class, order.next());
    order.next(); // gh-8105
    put(WebAsyncManagerIntegrationFilter.class, order.next());
    put(SecurityContextHolderFilter.class, order.next());
    put(SecurityContextPersistenceFilter.class, order.next());
    put(HeaderWriterFilter.class, order.next());
    put(CorsFilter.class, order.next());
    put(CsrfFilter.class, order.next());
    put(LogoutFilter.class, order.next());
    this.filterToOrder.put(
            "org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
            order.next());
    this.filterToOrder.put(
            "org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter",
            order.next());
    put(X509AuthenticationFilter.class, order.next());
    put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
    this.filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next());
    this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
            order.next());
    this.filterToOrder.put(
            "org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter",
            order.next());
    put(UsernamePasswordAuthenticationFilter.class, order.next());
    order.next(); // gh-8105
    put(DefaultLoginPageGeneratingFilter.class, order.next());
    put(DefaultLogoutPageGeneratingFilter.class, order.next());
    put(ConcurrentSessionFilter.class, order.next());
    put(DigestAuthenticationFilter.class, order.next());
    this.filterToOrder.put(
            "org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter",
            order.next());
    put(BasicAuthenticationFilter.class, order.next());
    put(RequestCacheAwareFilter.class, order.next());
    put(SecurityContextHolderAwareRequestFilter.class, order.next());
    put(JaasApiIntegrationFilter.class, order.next());
    put(RememberMeAuthenticationFilter.class, order.next());
    put(AnonymousAuthenticationFilter.class, order.next());
    this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
            order.next());
    put(SessionManagementFilter.class, order.next());
    put(ExceptionTranslationFilter.class, order.next());
    put(FilterSecurityInterceptor.class, order.next());
    put(AuthorizationFilter.class, order.next());
    put(SwitchUserFilter.class, order.next());
}

15 个过滤器进行说明

  • (1)WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。

  • (2)SecurityContextHolderFilter/SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在 Session 中维护一个用户的安全信息就是这个过滤器处理的。所有过滤器的入口;

  • (3)HeaderWriterFilter:用于将头信息加入响应中。

  • (4)CsrfFilter:用于处理跨站请求伪造。

  • (5)LogoutFilter:用于处理退出登录。

  • (6)UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的 usernameParameter 和 passwordParameter 两个参数的值进行修改。

  • (7)DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。

  • (8)BasicAuthenticationFilter:检测和处理 http basic 认证。

  • (9)RequestCacheAwareFilter:用来处理请求的缓存。

  • (10)SecurityContextHolderAwareRequestFilter:主要是包装请求对象 request。

  • (11)AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。

  • (12)SessionManagementFilter:管理 session 的过滤器

  • (13)ExceptionTranslationFilter:处理 AccessDeniedException 和
    AuthenticationException 异常。

  • (14)AuthorizationFilter:可以看做过滤器链的出口。

  • (15)RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的 remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。

核心组件

AuthenticationManager

AuthorizationManager

ProviderManager

Authentication

AuthenticationProvider

AbstractAuthenticationToken

UserDetails

UserDetailsService

SecurityFilterChain

SecurityContextHolder

PasswordEncoder

AuthenticationEventPublisher

AuthorizationEventPublisher

AccessDeniedHandler

AuthenticationEntryPoint

;