• Spring Security过滤链参考文档


    一、简介

    Spring Security是一个提供身份验证,授权和保护以防止常见攻击的框架,需要Java 8或更高版本的运行环境。

    它通过使用标准的Servlet Filter来集成Servlet容器,这意味着它可以与在Servlet容器中运行的任何应用程序一起工作。

    更具体地说,您无需在基于Servlet的应用程序中使用Spring即可利用Spring Security。

    二、Spring Security 过滤链

    Spring Security对Servlet的支持是基于Servlet的Filter,因此首先了解Filter是非常有帮助的。

    下图显示了单个 HTTP 请求的处理程序的典型分层:

    客户端向应用程序发送请求,然后容器创建了一个FilterChain,这个FilterChain含许多的Filter和一个Servlet,

    这些Filter和Servlet能够对基于请求URI路径的HttpServletRequest进行处理。

    在Spring MVC应用中,Servlet是一个DispatcherServlet实例。

    最多只能有一个Servlet处理HttpServletRequest和HttpServletResponse,

    但是Filter都可以用来阻止下游的Filter或Servlet被调用,或修改下游Filter的HttpServletRequest或HttpServletResponse。

    例如:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        // do something before the rest of the application
        chain.doFilter(request, response); // invoke the rest of the application
        // do something after the rest of the application
    }

    Spring提供了一个Filter接口的实现类(DelegatingFilterProxy),它连接了Servlet容器的生命周期和Spring的ApplicationContext。

    Servlet容器允许用它自己的标准注册过滤链,但是它不知道Spring定义的Bean。

    DelegatingFilterProxy可以通过标准Servlet容器机制进行注册,但是它将所有工作委托给实现了Filter接口的Bean实例。

    下图是对上文的介绍:

    DelegatingFilterProxy从ApplicationContext中查找Filter的第一个Bean,然后调用该Bean。

    另外,DelegatingFilterProxy允许延迟加载Filter Bean实例,因为容器需要在启动前注册Filter实例。

    然而,Spring通常等到Filter注册完后才用ContextLoaderListener去加载Filter实例。

    Spring Security对Servlet的支持主要靠FilterChainProxy,它是Spring Security提供的一个特别的Filter。

    FilterChainProxy允许委派许多Filter实例通过SecurityFilterChain(它是一个Bean,通常被包裹在DelegatingFilterProxy中)。

    如图所示:

     FilterChainProxy使用SecurityFilterChain决定为请求调用哪个SpringSecurity Filter。

    在SecurityFilterChain中的Security Filter通常都是Bean,它们通常都是被FilterChainProxy注册,而不是DelegatingFilterProxy。

    FilterChainProxy为直接注册在DelegatingFilterProxy或Servlet提供了很多优势:

    1、FilterChainProxy为Spring Security 的所有Servlet支持提供了一个入口。

    2、FilterChainProxy为决定什么时候应该调用SecurityFilterChain提供了灵活性。

    因此,如果您尝试对 Spring Security的Servlet支持进行故障排除,则为FilterChainProxy打断点调试是一个很好的开始。

    在Servlet容器中,Filter的调用仅仅基于URL,但FilterChainProxy通过调用RequestMatcher接口可以决定任何调用。

    事实上,FilterChainProxy可以用于决定哪个SecurityFilterChain应该被调用:

    若有多个SecurityFilterChain,FilterChainProxy将决定那个SecurityFilterChain应该被使用。

    只有第一个匹配的SecurityFilterChain将会被调用。

    如果一个请求URL是"/api/messages"的话,那么只有SecurityFilterChain0将会被调用,甚至该URL也匹配其他的。

    三、处理安全异常

    ExceptionTranslationFilter允许将AccessDeniedException和AuthenticationException反应给HTTP响应。

    ExceptionTranslationFilter作为安全Filter之一被插入到FilterChainProxy。

    首先ExceptionTranslationFilter调用FilterChain.doFilter(request,response)去调用剩余的应用程序。

    如果用户没有认证或者发生认证异常,然后就开始认证。

    SecurityContextHolder被清空,HttpServletRequest保存在RequestCache中,

    当认证成功后,RequestCache将重放原始请求。

    AuthenticationEntryPoint用来向客户端请求凭据,他可能是重定向到登陆页面或者发送一个WWW-Authenticate头部。

    否则,如果是AccessDeniedException,那么AccessDenieHandler将会被调用。

    用伪代码表示他将会是这样:

    try {
        filterChain.doFilter(request, response); 
    } catch (AccessDeniedException | AuthenticationException ex) {
        if (!authenticated || ex instanceof AuthenticationException) {
            startAuthentication(); 
        } else {
            accessDenied(); 
        }
    }

    四、认证

    1、SecurityContextHolder:Spring Security存储认证信息的地方。

    2、SecurityContext:从SecurityContextHolder中获取,它含有当前已通过身份认证的用户。

    3、Authentication:SecurityContext中的当前用户,或者是用户提供给AuthenticationManager验证的凭证。

    4、GrantedAuthority:拥有的权限。

    5、AuthenticationManager:定义Spring Security的Filter如何执行身份验证的API。

    6、ProviderManager:AuthenticationManager最常见的实现类。

    7、AuthenticationProvider:ProviderManager用来执行特定的身份验证。

    8、AbstractAuthenticationProcessingFilter:身份验证的基础。

    Ⅰ、SecurityContextHolder是身份验证的核心:

    用户经过身份验证最简单的方法是直接设置SecurityContextHolder(存储认证信息的地方)。

    SecurityContext context = SecurityContextHolder.createEmptyContext(); 
    Authentication authentication =
        new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
    context.setAuthentication(authentication);
    SecurityContextHolder.setContext(context); 

    1、创建一个新的SecurityContext实例而不是用SecurityContextHolder.getContext().setAuthentication(authentication)

    是很重要的,它可以避免多线程下的竟态条件。

    2、Spring Security不关心在SecurityContext上关于Authentication什么类型的实现,这里使用的是TestingAuthenticationToken,

    是因为它很简单,通常使用的都是UsernamePasswordAuthenticationToken(userDetails,password,authorities)

    3、最后,在SecurityContextHolder上设置SecurityContext,Spring Security将会使用认证信息。

    如果你想要获取验证信息,你可以像下面这样做:

    SecurityContext context = SecurityContextHolder.getContext();
    Authentication authentication = context.getAuthentication();
    String username = authentication.getName();
    Object principal = authentication.getPrincipal();
    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

    默认情况下,SecurityContextHolder使用ThreadLocal去存储这些信息。

    这意味着对于相同线程下的方法,甚至不将它作为参数传递给其他方法,SecurityContext也是可用的。

    如果处理当前主体的请求后想清除线程,这种情况下也是非常安全的。

    因为Spring Security的FilterChainProxy确保SecurityContext总是被清除的。

    Ⅱ、Authentication在Spring Security中有两个主要用途:

    1、作为AuthenticationManager中的输入提供身份验证的凭据。

    2、代表当前认证通过的用户,可以从SecurityContext中获取。

    Authentication包含如下:

    principal:标识用户。使用用户名/密码进行身份验证时,这通常是UserDetails实例。

    credentials:通常是密码。在许多情况下,这将在用户经过身份验证后被清除,以确保它不被泄露。

    authorities:授予用户的高级别权限。

    Ⅲ、ProviderManager委托了一个AuthenticationProvider列表,每个AuthenticationProvider都有机会认证。

    AuthenticationProvider可以提供一个特定的身份认证,例如:

    DaoAuthenticationProvider通过用户名密码身份验证,JwtAuthenticationProvider通过JWT token身份认证。

    ProviderManager还允许配置一个可选的父级AuthenticationManager,

    如果没有一个AuthenticationProvider能够进行认证,就向它进行咨询,它通常是ProviderManager的实例。

    实际上,多个ProviderManager实例可能共享相同的父级AuthenticationManager。

    这是很常见的情况:多个SecurityFilterChain实例有相同的身份认证(相同的父级AuthenticaManager),

    但是不同的验证机制(不用的ProviderManaer实例)。

    默认情况下,ProviderManager将会试图清除认证成功的Authentication中的敏感信息,

    这样可以防止密码之类的信息被保留的时间超过HttpSession所需的时间。

     Ⅳ、AuthenticationEntryPoint用于发送从客户端请求凭据的 HTTP 响应。

    有时客户端会通过用户名密码去请求资源,在这种情况下,Spring Security不需要提供从客户端请求凭据的HTTP 响应。

    其他情况下,客户端向它们无权访问的资源发出未经身份验证的请求,它的实现类通常用于请求用户凭据。

    例如:重定向到登录页,用www-Authenticate头部进行响应。

     Ⅴ、AbstractAuthenticationProcessingFilter是最基础的用户身份凭据认证Filter。

    在用户凭据被认证之前,Spring Security通常用AuthenticationEntryPoint请求用户凭据。

    接下来,AbstractAuthenticationProcessingFilter能够验证任何提交给它的身份验证请求。

    1、当用户提交凭据时,AbstractAuthenticationProcessingFilter从HttpServletRequest创建Authentication去验证,

    被创建的Authentication依赖于AbstractAuthenticationProcessingFilter的子类。例如:UsernamePasswordAuthenticationFilter

    通过提交给HttpServletRequest的用户名和密码来创建一个UsernamePasswordAuthenticationToken。

    2、接下来这个Authentication被传递给AuthenticationManager去验证,如果验证失败:

    那么SecurityContextHolder会被清空,RememberMeServices.loginFail会被调用,AuthenticationFailureHandler会被调用。

    如果验证成功:

    SessionAuthenticationStrategy将会收到新的登录通知,这个Authentication将会被设置到SecurityContextHolder中,

    然后SecurityContextPersistenceFilter保存SecurityContext到HttpSession中。

    RememberMeServices.loginSuccess被调用,如果没有配置记住我,它将会是no-op。

    ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent,最后AuthenticationSuccessHandler被调用。

     Ⅵ、Username/Password Authentication

    Spring Security提供了以下内置机制,用于从HttpServletRequest中读取用户名和密码。

    Form Login、Basic Authentication、Digest Authentication。

    以Form Login为例:

    用户向未经授权的资源发出未经身份验证的请求"/private",

    Spring Security的FilterSecurityInterceptor抛出AccessDeniedException异常提示该请求没有经过认证。

    由于用户没有通过身份验证,ExceptionTranslationFilter通过AuthenticationEntryPoint发起身份认证并发送一个重定向到登录页。

    大多数情况下,AuthenticationEntryPoint是LoginUrlAuthenticationEntryPoint的实例。

    然后,浏览器将请求将其重定向到的登录页面,当用户名和密码被提交后:

    UsernamePasswordAuthenticationFilter就开始验证用户名和密码,

    UsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter,所以下图看起来会很相似:

    当用户提交用户名密码后,UsernamePasswordAuthenticationFilter创建一个UsernamePasswordAuthenticationToken,

    它是从一个从HttpServletRequest抽取用户名和密码创建的一个Authentication类型。

    接下来UsernamePasswordAuthenticationToken将会被传递给AuthenticationManager去认证。

     五、SpringSecurity Web 与 SpringSecurity OAuth2的区别与联系

    想要知道他们之间的区别和联系,我们可以先了解下什么是OAuth2:OAuth2

    OAuth2通常都是用于第三方登录的,那他们之间的区别通俗点说就是主和宾的关系。

  • 相关阅读:
    ASP.NET Core: What I learned!
    Entity Framework Core with GraphQL and SQL Server using HotChocolate
    Angular 9 Chart.js with NG2-Charts Demo
    POST调用WCF方法-项目实践
    项目实战-登录速度优化笔记
    MP4视频流base64数据转成Blob对象
    使用Vue+ElementUI实现前端分页
    JS端实现图片、视频时直接下载而不是打开预览
    Dynamic CRM工作流流程实战
    Dynamic CRM插件调试与单元测试
  • 原文地址:https://www.cnblogs.com/M-Anonymous/p/14261647.html
Copyright © 2020-2023  润新知