定义与当前执行线程关联的最小安全信息的接口,安全上下文存储在SecurityContextHolder中,SecurityContext从SecurityContextHolder获取,并包含当前经过身份验证的用户的Authentication。
SecurityContext接口源码:
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
SecurityContext接口只有一个实现类,即SecurityContextImpl类。
SecurityContext接口的基本实现。
public class SecurityContextImpl implements SecurityContext {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// 身份验证请求令牌
private Authentication authentication;
// 无参构造方法
public SecurityContextImpl() {}
// 接受Authentication实例的构造方法
public SecurityContextImpl(Authentication authentication) {
this.authentication = authentication;
}
// 重写equals方法
@Override
public boolean equals(Object obj) {
// obj也需要是SecurityContextImpl类型
if (obj instanceof SecurityContextImpl) {
SecurityContextImpl test = (SecurityContextImpl) obj;
// 它们的authentication属性都为null
if ((this.getAuthentication() == null) && (test.getAuthentication() == null)) {
return true;
}
// 它们的authentication属性都不为null,并且调用equals方法返回true
if ((this.getAuthentication() != null) && (test.getAuthentication() != null)
&& this.getAuthentication().equals(test.getAuthentication())) {
return true;
}
}
return false;
}
// 返回authentication属性
@Override
public Authentication getAuthentication() {
return authentication;
}
// 重写hashCode方法
@Override
public int hashCode() {
if (this.authentication == null) {
return -1;
}
else {
return this.authentication.hashCode();
}
}
// 设置authentication属性
@Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
// 重写toString方法
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
if (this.authentication == null) {
sb.append(": Null authentication");
}
else {
sb.append(": Authentication: ").append(this.authentication);
}
return sb.toString();
}
}
Debug分析
项目结构图:
pom.xml:
4.0.0 com.kaven security 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent 2.3.6.RELEASE 8 8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security org.projectlombok lombok
application.yml:
spring:
security:
user:
name: kaven
password: itkaven
roles: USER
logging:
level:
org:
springframework:
security: DEBUG
MessageController(定义接口):
package com.kaven.security.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MessageController {
@GetMapping("/message")
public String getMessage() {
return "hello spring security";
}
}
启动类:
package com.kaven.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
SecurityConfig(Spring Security的配置类,不是必须的,因为有默认的配置):
package com.kaven.security.config;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 任何请求都需要进行验证
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
// 记住身份验证
.rememberMe(Customizer.withDefaults())
// 基于表单登陆的身份验证方式
.formLogin(Customizer.withDefaults());
}
}
Debug方式启动应用,访问http://localhost:8080/message,Spring Security会通过SecurityContextHolder创建空SecurityContextImpl实例(实例的authentication属性为空)。
ThreadLocalSecurityContextHolderStrategy是基于ThreadLocal的SecurityContextHolderStrategy(针对线程存储安全上下文信息的策略,SecurityContextHolder使用它管理SecurityContext)实现。SecurityContextHolder使用ThreadLocalSecurityContextHolderStrategy实例创建空SecurityContextImpl实例(实例的authentication属性为空)。
然后AnonymousAuthenticationFilter会处理请求(当前是匿名访问资源),该过滤器会设置该空SecurityContextImpl实例的authentication属性为AnonymousAuthenticationToken实例。
而通过身份验证(authenticated属性为true)的AnonymousAuthenticationToken实例(身份验证请求令牌),也是没有权限访问/message接口的,所以请求被拒绝访问了。
之后请求会被重定向到表单登陆页面,需要填写用户名和密码进行身份验证。
点击登陆后,又会创建一个空SecurityContextImpl实例(实例的authentication属性为空)。
之后会设置该空SecurityContextImpl实例的authentication属性为UsernamePasswordAuthenticationToken实例,并且该UsernamePasswordAuthenticationToken实例的authenticated属性为true(通过身份验证)。
控制台的日志也能说明身份验证成功了。
请求也成功访问到了/message接口。
安全上下文SecurityContext介绍与Debug分析就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。



