diff --git a/spring/Spring Security/Spring Security.md b/spring/Spring Security/Spring Security.md index 51421a3..a35b548 100644 --- a/spring/Spring Security/Spring Security.md +++ b/spring/Spring Security/Spring Security.md @@ -1,3 +1,48 @@ +- [Spring Security](#spring-security) + - [Spring Security简介](#spring-security简介) + - [Spring Security自动配置](#spring-security自动配置) + - [Spring Security结构](#spring-security结构) + - [DelegatingFilterProxy](#delegatingfilterproxy) + - [FilterChainProxy](#filterchainproxy) + - [SecurityFilterChain](#securityfilterchain) + - [SecurityFilters](#securityfilters) + - [添加自定义filter到SecurityFilterChain](#添加自定义filter到securityfilterchain) + - [处理Security异常](#处理security异常) + - [在多次认证之间保存request](#在多次认证之间保存request) + - [RequestCache](#requestcache) + - [Spring Security身份认证的结构](#spring-security身份认证的结构) + - [SecurityContextHolder](#securitycontextholder) + - [SecurityContext](#securitycontext) + - [Authentication](#authentication) + - [GrantedAuthority](#grantedauthority) + - [AuthenticationManager](#authenticationmanager) + - [ProviderManager](#providermanager) + - [AuthenticationProvider](#authenticationprovider) + - [AuthenticationEntryPoint](#authenticationentrypoint) + - [AbstractAuthenticationProcessingFilter](#abstractauthenticationprocessingfilter) + - [认证流程](#认证流程) + - [用户名/密码认证](#用户名密码认证) + - [Form Login](#form-login) + - [用户被重定向到登录页面的过程](#用户被重定向到登录页面的过程) + - [认证用户名和密码过程](#认证用户名和密码过程) + - [基本Authentication](#基本authentication) + - [基本Authentication的认证流程](#基本authentication的认证流程) + - [Digest Authentication(摘要认证,***不安全***)](#digest-authentication摘要认证不安全) + - [摘要认证中的随机数](#摘要认证中的随机数) + - [密码存储方式](#密码存储方式) + - [内存中存储密码](#内存中存储密码) + - [内存中存储密码时使用defaultPasswordEncoder](#内存中存储密码时使用defaultpasswordencoder) + - [JDBC Authentication](#jdbc-authentication) + - [User Schema](#user--schema) + - [Group Schema](#group-schema) + - [配置Datasource](#配置datasource) + - [创建JdbcUserDetailsManager Bean对象](#创建jdbcuserdetailsmanager-bean对象) + - [UserDetails](#userdetails) + - [UserDetailsService](#userdetailsservice) + - [PasswordEncoder](#passwordencoder) + - [DaoAuthenticationProvider](#daoauthenticationprovider) + + # Spring Security ## Spring Security简介 Spring Security作为一个安全框架,向使用者提供了用户认证、授权、常规攻击保护等功能。 @@ -29,15 +74,83 @@ Spring Security支持FilterChainProxy,FilterChainProxy是一个由Spring Secur ***FilterChainProxy是一个bean对象,通过被包含在DelegatingFilterProxy中。*** ### SecurityFilterChain -SecurityFilterChain通常被FilterChainProxy使用,用来决定在该次请求中调用那些Spring Security Filters。 +SecurityFilterChain通常被FilterChainProxy使用,用来决定在该次请求中调用哪个Spring Security Filters。 ### SecurityFilters Security Filters通过SecurityFilterChain API被插入到FilterChainProxy中。 + +### 添加自定义filter到SecurityFilterChain +大多数情况下,默认的security是足够的,但是,也允许向security filter chain中添加自定义过滤器。 + +如下是一个自定义过滤器的示例,示例中自定义filter会在认证后校验用户对于请求的租户是否拥有权限: +```java +import java.io.IOException; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.security.access.AccessDeniedException; + +public class TenantFilter implements Filter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + String tenantId = request.getHeader("X-Tenant-Id"); //(1) + boolean hasAccess = isUserAllowed(tenantId); //(2) + if (hasAccess) { + filterChain.doFilter(request, response); //(3) + return; + } + throw new AccessDeniedException("Access denied"); //(4) + } + +} +``` +如下是自定义过滤器链的流程: +1. 步骤从请求header中获取了tenantId +2. 步骤校验了用户对于该tenant id代表的租户资源是否拥有访问权限 +3. 只有当用户对租户资源拥有访问权限时,才会继续调用filterChain中剩余的部分 +4. 如果用户没有访问权限,将会抛出AccessDeniedException + +在定义完自定义过滤器后,还需要将自定义filter添加到Security Filter Chain中,添加代码如下所示例: +```java +@Bean +SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + // ... + .addFilterBefore(new TenantFilter(), AuthorizationFilter.class); + return http.build(); +} +``` +> 由于权限校验操作在身份认证之后,故而将TenantFilter加到AuthorizationFilter之前可以保证在执行TenantFilter时,所有的身份认证操作都已经执行完成。 + +> 在向SecurityFilterChain注册filter时,不应该将自定义filter注册为bean,因为spring会将注册为bean的filter对象都添加到内置容器中,这会造成注册为bean的filter被调用两次:一次被Spring Seurity调用,一次被container调用。 + + + ### 处理Security异常 -ExceptionTranslationFilter将认证异常和权限异常翻译为http response。 -> ExceptionTranslationFilter被插入到FilterChainProxy中,作为SecurityFilterChain中的一个。 +ExceptionTranslationFilter将`AccessDeniedException`和`AuthenticationException`翻译为http response。 + +> ExceptionTranslationFilter被插入到FilterChainProxy中,作为SecurityFilterChain中的一个filter。 > 如果应用程序没有抛出AccessDeniedException或AuthenticationException,那么ExceptionTranslationFilter并不会做任何事情。 +ExceptionTranslationFilter的处理流程: +1. ExceptionTranslationFilter首先会调用`filterChain.doFilter`来执行后续的逻辑 +2. 在执行后续请求时,如果抛出了AuthenticationException,那么则会开启认证流程 + 1. 清空SecurityContextHolder + 2. 保存HttpServletRequest,当认证通过时,还能重新发送原始请求 + 3. `AuthenticationEntryPoint`用于重新请求用户的凭证,其可能会重定向到登录页面或是发送 `www-authenticate`header +3. 在执行后续请求时,如果抛出了AccessDeniedException,那么会调用`AccessDeniedHandler`用于处理访问拒绝。 +4. 如果执行后续逻辑时,抛出了非`AuthenticationException`和`AccessDeniedException`的异常,那么异常将会被重新抛出,ExceptionTranslationFilter并不会针对其进行处理 + ```java // ExceptionTranslationFilter的伪代码 try { @@ -51,6 +164,46 @@ try { } ``` +### 在多次认证之间保存request +如果用户在发送请求访问需要认证的资源时,尚未经过身份认证,那么需要将本次请求保存起来,并且对用户进行认证,待用户认证通过之后,再重新执行保存的原始请求。在Spring Security中,是通过RequestCache来实现保存请求的。 + +#### RequestCache +HttpServletRequest被保存在RequestCache中,当用户经过重新认证后,RequestCache将会用于重新发送原始请求。 + +在Spring Security中,在sendStartAuthentication时会像requestCache中存储request,并且重新执行认证流程,认证通过后,将会重新发送原始请求。 + +RequestCacheAwareFilter负责请求的重新发送。 + +默认情况下,会使用`HttpSessionRequestCache`,如下代码展示了HttpSessionRequestCache的自定义使用: +```java +@Bean +DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception { + HttpSessionRequestCache requestCache = new HttpSessionRequestCache(); + // 匹配本次request时,需要确保请求url参数中具有continue参数 + requestCache.setMatchingRequestParameterName("continue"); + http + // ... + .requestCache((cache) -> cache + .requestCache(requestCache) + ); + return http.build(); +} +``` + +由于在认证失败时存储原始请求会占用内存,如果想要禁止在抛出AuthenticationException后,保存原始请求到RequestCache的行为,可以为SecurityFilterChain指定`NullRequestCache`: +```java +@Bean +SecurityFilterChain springSecurity(HttpSecurity http) throws Exception { + RequestCache nullRequestCache = new NullRequestCache(); + http + // ... + .requestCache((cache) -> cache + .requestCache(nullRequestCache) + ); + return http.build(); +} +``` + ## Spring Security身份认证的结构 ### SecurityContextHolder Spring Security身份认证的核心模型,SecurityContextHolder包含有SecurityContext。