SpringSecurity认证和授权流程详解

打印 上一主题 下一主题

主题 872|帖子 872|积分 2616

什么是SpringSecurity

Spring Security是一个Java框架,用于掩护应用步伐的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用步伐中。它支持多种身份验证选项和授权策略,开辟人员可以根据需要选择适合的方式。别的,Spring Security还提供了一些附加功能,如集成第三方身份验证提供商和单点登录,以及会话管理和密码编码等。
Spring Security是一个强盛且易于利用的框架,可以帮助开辟人员进步应用步伐的安全性和可靠性。而我们最常用的两个功能就是认证和鉴权,因此作为入门文章本文也只介绍这两个功能的利用。
❝Spring Security可以用于Servlet应用和Reactive应用,本文主要介绍基于Servlet应用的场景
如需更具体的利用方式请参考官方文档:https://spring.io/projects/spring-security
架构

上图是Spring Security官方提供的架构图。我们先看图的左边部分,就是一个典型Servlet Filter (过滤器)处置惩罚流程,我们依次讲解流程涉及的组件。
FilterChain

FilterChain :过滤器链,是Servlet容器在接收到客户端发送的哀求时创建的,一个FilterChain可以包罗多个Filter和一个Servlet,Servlet容器根据哀求URI的路径来处置惩罚HttpServletRequest。
在Spring MVC中,Servlet 就是 DispatcherServlet实例。一个 Servlet 最多只能处置惩罚一个 HttpServletRequest 和 HttpServletResponse。然而,可以利用多个 Filter 来完成如下工作。

  • 防止下游的 Filter 或 Servlet 被调用。在这种阻断哀求的情况下,Filter 通常会利用 HttpServletResponse 对客户端写入响应内容。
  • 修改下游的 Filter 和 Servlet 所利用的 HttpServletRequest 或 HttpServletResponse。
DelegatingFilterProxy

DelegatingFilterProxy :Spring Security 对 Servlet 的支持是基于Servlet Filter的,而DelegatingFilterProxy 就是Spring Security的Filter实现。
DelegatingFilterProxy允许在 Servlet 容器的生命周期和 Spring 的 ApplicationContext 之间建立桥梁。Servlet容器允许通过利用自己的标准来注册 Filter 实例,但Servlet容器不知道 Spring 定义的 Bean。因此大多数情况下我们通过标准的Servlet容器机制来注册 DelegatingFilterProxy,但将全部工作委托给实现 Filter 的Spring Bean。
❝Spring Security会自动向Servlet容器机制注册 DelegatingFilterProxy ,无需我们手动去注册
FilterChainProxy

FilterChainProxy :是 Spring Security 提供的一个特殊的 Filter,允许通过 SecurityFilterChain 委托给很多 Filter 实例。由于 FilterChainProxy 是一个Spring Bean,因此它被包罗在 DelegatingFilterProxy 中。
SecurityFilterChain

SecurityFilterChain :是FilterChainProxy用来确定当前哀求应该调用哪些Spring Security Filter 实例的过滤器链。
SecurityFilterChain 中的 Security Filter 一般都是Spring Bean,但这些Security Filter是用 FilterChainProxy 举行注册,而不是通过DelegatingFilterProxy注册。与直接向Servlet容器或 DelegatingFilterProxy 注册相比,FilterChainProxy 有很多优势。

  • 首先,由于 FilterChainProxy 是 Spring Security 利用的核心,它可以处置惩罚一些必须要做的事情。 例如:

    • 清除 SecurityContext 以避免内存泄漏。
    • 应用Spring Security的 HttpFirewall 来掩护应用步伐免受某些类型的攻击。

  • 其次,它在确定何时应该调用 SecurityFilterChain 方面提供了更大的灵活性。在Servlet容器中,Filter 实例仅基于URL被调用。 然而,FilterChainProxy 可以通过利用 RequestMatcher 接口,根据 HttpServletRequest 中的任何内容确定调用。
图的右边部分是存在多个SecurityFilterChain, FilterChainProxy 的匹配策略则是匹配第一个满足的 SecurityFilterChain。
比如,哀求的URL是 /api/messages/,它首先与 /api/** 的 SecurityFilterChain 0 模式匹配,所以只有 SecurityFilterChain0 被调用;虽然它也与 SecurityFilterChain n 匹配。
如果哀求的URL是 /messages/,它与 /api/** 的 SecurityFilterChain 0 模式不匹配,所以 FilterChainProxy 继续尝试每个 SecurityFilterChain。如果没有其他 SecurityFilterChain 实例相匹配,则调用 SecurityFilterChain n。
SecurityFilter

SecurityFilter:是指通过SecurityFilterChain 插入 FilterChainProxy 中的 Filter。这些 Filter 可以用于很多不同的目的,如 认证、 授权、 毛病掩护等。Filter 是按照特定的顺序实行的,以包管它们在正确的时间被调用。
例如,实行认证的 Filter 应该在实行授权的 Filter 之前被调用。如果想要知道 Spring Security 的 Filter 的顺序,可以检察 FilterOrderRegistration 源码。
❝如果想检察你应用中注册了哪些SecurityFilter 的话可以将org.springframework.security的日志级别调到info,这样在你应用启动的时候就会在控制台打印出当前应用注册的全部SecurityFilter 。结果如下:
  1. 2023-06-14T08:55:22.321-03:00  INFO 76975 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [<br>org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,<br>org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,<br>org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,<br>org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,<br>org.springframework.security.web.csrf.CsrfFilter@c29fe36,<br>org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,<br>org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,<br>org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,<br>org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,<br>org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,<br>org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,<br>org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,<br>org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,<br>org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,<br>org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]<br>
复制代码
至此,Spring Security官方架构图中涉及的组件就基本介绍完了,大家先对团体架构和实行流程有一个了解,只有先了解了团体架构,才方便接下来我们去理解Spring Security是如何去实现认证和授权的。
常用Spring Security开启的SecurityFilter


  • CsrfFilter:防止Csrf攻击的SecurityFilter
  • AuthorizationFilter:授权SecurityFilter
  • ExceptionTranslationFilter:处置惩罚认证和授权异常的SecurityFilter
异常处置惩罚

Spring Security中有一个ExceptionTranslationFilter ,ExceptionTranslationFilter 作为 Security Filter 之一被插入到 FilterChainProxy 中。
ExceptionTranslationFilter可以处置惩罚AuthenticationException或AccessDeniedException,其逻辑大概是这样:
  1. try {<br> filterChain.doFilter(request, response);<br>} catch (AccessDeniedException | AuthenticationException ex) {<br> if (!authenticated || ex instanceof AuthenticationException) {<br>  startAuthentication();<br> } else {<br>  accessDenied();<br> }<br>}<br>
复制代码
❝这段代码的逻辑大致就是,拦截AccessDeniedException 或 AuthenticationException,如果不是这两个异常则不处置惩罚。
ExceptionTranslationFilter流程如下:
因此如果我们想自己处置惩罚AuthenticationException大概AccessDeniedException,分别实现AuthenticationEntryPoint大概AccessDeniedHandler 即可
  1. @Component<br>public class AccessDeniedHandlerImpl implements AccessDeniedHandler {<br>    @Override<br>    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {<br>        log.error("AccessDeniedException 请求URI:{}", request.getRequestURI(), accessDeniedException);<br>        response.setCharacterEncoding("UTF-8");<br>        HashMap<String, String> result = new HashMap();<br>        result.put("code", "401");<br>        result.put("message", "权限不足");<br>        // 处理没有权限的错误错误<br>        response.getWriter().write(JsonUtil.toString(result));<br>    }<br>}<br>
复制代码
  1. @Component<br>public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {<br>    @Override<br>    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {<br>        log.error("AccessDeniedException 请求URI:{}", request.getRequestURI(), authException);<br>        response.setCharacterEncoding("UTF-8");<br>        // 处理认证失败的错误<br>        HashMap<String, String> result = new HashMap();<br>        result.put("code", "500");<br>        result.put("message", "用户名或密码错误");<br>        // 处理没有权限的错误错误<br>        response.getWriter().write(JsonUtil.toString(result));<br>    }<br>}<br>
复制代码
  1. @Configuration<br>public class SecurityConfig {<br><br>    @Bean<br>    public SecurityFilterChain apiFilterChain(HttpSecurity httpSecurity,<br>                                              AuthenticationEntryPoint authenticationEntryPoint,<br>                                              AccessDeniedHandler accessDeniedHandler) throws Exception {<br>        // 配置异常处理<br>        httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler);<br>        httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);<br>        return httpSecurity.build();<br>    }<br> }<br>
复制代码
上面是Spring Security对于认证和鉴权异常的处置惩罚机制,但是如果我们自定义了一个Filter。如果这个Filter抛出异常,Spring的全局异常处置惩罚机制是无法处置惩罚的(原因自行搜索)。所以我们还需要自己做一个Filter异常的处置惩罚流程。
首先,我们自定义一个Filter,要在FilterChain中的位置比较靠前,没有其他逻辑就是拦截后面filter抛出的异常,然后转发到指定Controller去处置惩罚,然后再用全局异常去处置惩罚Filter抛出的异常。
  1. @Component<br>public class ExceptionFilter extends OncePerRequestFilter {<br>    @Override<br>    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {<br>        try{<br>            filterChain.doFilter(request, response);<br>        }catch (Exception e){<br>            // 将异常信息写入请求<br>            request.setAttribute("filterException", e);<br>            // 重定向到处理异常的controller<br>            request.getRequestDispatcher("/exception").forward(request, response);<br>        }<br>    }<br>}<br>
复制代码
  1. @RestController<br>public class ExceptionController {<br><br>    @RequestMapping("/exception")<br>    public void handleException(HttpServletRequest request) throws Exception {<br>        Object attribute = request.getAttribute("filterException");<br>        if (ObjectUtil.isNotEmpty(attribute) && attribute instanceof Exception e){<br>            throw e;<br>        }<br>    }<br>}<br>
复制代码
  1. @Configuration<br>public class SecurityConfig {<br><br>    @Bean<br>    public SecurityFilterChain apiFilterChain(HttpSecurity httpSecurity,<br>                                              ExceptionFilter exceptionFilter) throws Exception {<br>        // 配置异常处理过滤器 <br>        // 这里我们配置在ExceptionTranslationFilter之前 让ExceptionTranslationFilter先处理AuthenticationException或者AccessDeniedException<br>     // 剩下的其他Exception再交由我们自定义的ExceptionFilter处理<br>        httpSecurity.addFilterBefore(exceptionFilter, ExceptionTranslationFilter.class);<br>        return httpSecurity.build();<br>    }<br> }<br>
复制代码
认证

上面我们已经把Spring Security团体流程讲完了,接下来我们就看一下具体认证的流程是怎样的。Spring Security有提供一套基于标准页面的流程,但是不适用于基于前后端分离的开辟模式。地址给大家贴这,有需要的可自行去看看:认证 :: Spring Security Reference (springdoc.cn)
接下来介绍基于前后端分离的流程:

  • 先配置AuthenticationManager(认证管理器),此中最常用的AuthenticationManager实现是ProviderManager
  1. @Configuration<br>public class SecurityConfig {<br><br>   /**<br>     * 配置AuthenticationManager(认证管理器)<br>     * @return<br>     */<br>    @Bean<br>    public AuthenticationManager authenticationManager(AuthenticationProvider authenticationProvider){<br>       // ProviderManager 是 AuthenticationManager 最常用的实现<br>        return new ProviderManager(authenticationProvider);<br>    }<br>}<br>
复制代码

  • 配置AuthenticationProvider,这里我们的认证方案是利用数据库存储的密码和登录哀求的密码举行匹配验证,因此我们选择DaoAuthenticationProvider 。DaoAuthenticationProvider 是一个 AuthenticationProvider 的实现,它利用 UserDetailsService 和 PasswordEncoder 来验证一个用户名和密码。
  1. @Configuration<br>public class SecurityConfig {<br><br>   /**<br>     * 配置PasswordEncoder(密码编码器)<br>     * @return<br>     */<br>    @Bean<br>    public PasswordEncoder passwordEncoder(){<br>        return new BCryptPasswordEncoder();<br>    }<br><br>    /**<br>     * 配置AuthenticationProvider<br>     * @return<br>     */<br>    @Bean<br>    public AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService, <br>                                   PasswordEncoder passwordEncoder){<br>        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();<br>        daoAuthenticationProvider.setUserDetailsService(userDetailsService);<br>        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);<br>        return daoAuthenticationProvider;<br>    }<br>}<br>
复制代码

  • 配置UserDetails 和UserDetailsService
[code]import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

@Setter
@EqualsAndHashCode
@ToString
public class SecurityUserDetail implements UserDetails {

    @Getter
    private Long userId;

    private String userName;

    private String password;

    private List authorities;

    @Override
    public Collection

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

罪恶克星

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表