定义与当前执行线程关联的最小安全信息的接口,安全上下文存储在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 security1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent2.3.6.RELEASE 8 8 org.springframework.boot spring-boot-starter-weborg.springframework.boot spring-boot-starter-securityorg.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分析就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)