spring-security 项目实战(一)个人健康档案
- 项目说明
- 项目地址
- 框架信息
- 代码分析
- 配置类解析
- 默认登录页登录接口执行逻辑
- 登录认证成功之后重定向到main页面过程
- 未登录之前访问 /main
- 生成默认登录页
- 点击登录
- 登录之后访问 /main
- 执行流程
- 清空认证信息
项目来源于《Spring Security原理与实战》,这本书对spring security介绍比较详细的,很多原理性的内容讲解的比较清晰。
项目说明
项目地址
https://gitee.com/3281328128/spring-security-basic
master 分支的代码原来书里面的代码,boot_3.2.2 分支是修改之后的代码。项目来源于书籍的第5章-实现自定义的用户认证体系,原来的项目用的spring security版本比较低,改成了 spring-security-6.2.1
版本的,该项目前后端不分离的,基于 session 来认证用户是否登录
框架信息
boot_3.2.2 分支使用的框架信息
名称 | 版本信息 | 说明 |
---|---|---|
JDK | 17 | LTS 长期支持版本 |
spring-boot-starter-web | 3.2.2 | 内嵌web服务器, jar包启动 |
spring-boot-starter-security | 3.2.2 | 安全框架 |
mybatis-plus-spring-boot3-starter | 3.5.5 | ORM框架 |
mybatis-plus-generator | 3.5.5 | 根据数据表自动生产代码 |
spring-boot-starter-thymeleaf | 3.2.2 | 处理静态资源,模板数据填充 |
代码分析
启动项目之后,访问 http://localhost:8080/main地址,会重定向到默认登录页, 登录认证之后会重定向到main页面
重定向到默认页面的过程可以查看之前的博客spring-security 默认登录页面(一)。整个过程还有几个问题需要分析
- 默认登录页登录接口的执行逻辑
- 登录认证成功之后重定向到main页面过程
配置类解析
配置类代码如下
package com.lagou.springsecurity.config;
import com.lagou.springsecurity.service.AuthenticationProviderService;
import com.lagou.springsecurity.service.CustomUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
private AuthenticationProviderService authenticationProvider;
private CustomUserDetailsService customUserDetailsService;
public SecurityConfig(AuthenticationProviderService authenticationProvider,
CustomUserDetailsService customUserDetailsService) {
this.authenticationProvider = authenticationProvider;
this.customUserDetailsService = customUserDetailsService;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin(httpSecurityFormLoginConfigurer ->
httpSecurityFormLoginConfigurer.defaultSuccessUrl("/main", true));
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests.anyRequest().authenticated());
http.authenticationProvider(authenticationProvider);
http.userDetailsService(customUserDetailsService);
return http.build();
}
}
http.formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.defaultSuccessUrl("/main", true));
配置登录成功之后的跳转页面,本章续会分析这个过程http.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests.anyRequest().authenticated());
配置所有的地址都需要认证,这个配置会在AuthorizationFilter
中读取到,与当前的用户权限对比,看能否访问对应的资源,如果不能抛出异常给ExceptionTranslationFilter
过滤器处理http.authenticationProvider(authenticationProvider)
配置自定义的登录验证处理逻辑,验证用户名、密码,设置用户权限等http.userDetailsService(customUserDetailsService)
配置自定义的用户信息接口,根据用户名加载用户信息
默认登录页登录接口执行逻辑
- 在
org.springframework.security.web.FilterChainProxy.VirtualFilterChain#doFilter
方法中增加断点,方便调试每个过滤器
可以看到当前项目配置的过滤器详情
登录认证成功之后重定向到main页面过程
未登录之前访问 /main
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
过滤器会将请求缓存org.springframework.security.web.access.intercept.AuthorizationFilter
过滤去认证抛出异常
- 异常处理过滤器
org.springframework.security.web.access.ExceptionTranslationFilter
处理异常
生成重定向url http://localhost:8080/login
生成默认登录页
https://blog.csdn.net/modelmd/article/details/135982664
点击登录
点击登录会调用 /login 接口,在 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
中进行用户名、密码的验证,UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
,大多数处理流程在 AbstractAuthenticationProcessingFilter
中执行
流程图如下
-
用户名密码校验详细过程解析
在方法org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#attemptAuthentication
执行用户认证的逻辑
从请求头中获取用户名、密码
AuthenticationManager
类负责执行用户认证的逻辑,具体实现逻辑在类org.springframework.security.authentication.ProviderManager
核心认证逻辑方法org.springframework.security.authentication.ProviderManager#authenticate
最终会进入用户自定义的用户名密码逻辑,在认证方法中可以将用户的权限加载到认证对象,这样AuthorizationFilter
就可以验证用户否有权限访问对应的url
-
修改会话ID、CSRF token
org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy
修改请求的 sessionId, 防止固定会话攻击,org.springframework.security.web.csrf.CsrfAuthenticationStrategy
更新seesion对应的 CSRF token -
设置认证成功,重定向到登录成功页
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#successfulAuthentication
方法执行认证成功之后的相关逻辑。安全上下文SecurityContext
设置认证成功的对象,安全上下文基于ThreadLocal实现的,在同一线程的其它filter中可以比较容易获取到认证成功的对象数据
在SecurityContext
中设置上下文对象之后,也会在 httpSeesion、httpRequest属性中存储认证对象,可以查看org.springframework.security.web.context.SecurityContextRepository#saveContext
方法。HttpSessionSecurityContextRepository
将认证对象存储在 seesion ,RequestAttributeSecurityContextRepository
将认证对象存储在http请求头属性
存储在 session的 key 默认值是SPRING_SECURITY_CONTEXT
存储在 httpRequest 属性中的 key 默认值是org.springframework.security.web.context.RequestAttributeSecurityContextRepository.SPRING_SECURITY_CONTEXT
重定向到缓存页。首先从session中获取缓存信息,然后重定向到具体的地址,可以查看org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler#onAuthenticationSuccess
方法
从session缓存中获取 url
session缓存中未获取到 url, 去目标url处理器中查找org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler#handle
AbstractAuthenticationTargetUrlRequestHandler
中目标defaultTargetUrl如何初始化的?
在配置类中初始化配置登录成功的地址
登录之后访问 /main
执行流程
-
AnonymousAuthenticationFilter
中设置认证对象
-
认证过滤器
AuthorizationFilter
中会获取 session 中的已认证的用户
校验用户权限org.springframework.security.authorization.AuthenticatedAuthorizationManager#check
获取已认证用户对象,org.springframework.security.web.access.intercept.AuthorizationFilter#getAuthentication
从session 中获取认证对象org.springframework.security.web.context.HttpSessionSecurityContextRepository#loadDeferredContext
,
org.springframework.security.web.context.HttpSessionSecurityContextRepository#readSecurityContextFromSession
获取之前登录的时候设置到 session 中的用户,属性key 和之前登录一致的SPRING_SECURITY_CONTEXT
清空认证信息
如果将cookie 清除之后,页面就会重新跳转到登录页
- 清除 cookie,在开发者工具中,删除对应的 session
- 访问 http://localhost:8080/main 地址,丛session中获取认证信息失败,会重定向到登录页