视频教程地址: https://www.bilibili.com/video/BV1kT4y1F7Tc
代码地址: https://gitee.com/crazyliyang/video-teaching
1. SecurityContextHolder
public class SecurityContextHolder {
// 省略非核心代码 ...
// ...
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL"; // threadLocal
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY); // 上边的系统变量
private static SecurityContextHolderStrategy strategy; // 策略类
public static SecurityContext getContext() {
return strategy.getContext();
}
private static void initialize() {
if (!StringUtils.hasText(strategyName)) { // 如果没有系统变量的配置
// Set default
strategyName = MODE_THREADLOCAL; // 策略名称使用 MODE_THREADLOCAL就是threadLocal
}
if (strategyName.equals(MODE_THREADLOCAL)) { // mode_threadLocal
// 创建 ThreadLocal 类型的 SecurityContextHolderStrategy
strategy = new ThreadLocalSecurityContextHolderStrategy();
}
// 其实下边的就可以 不用看了, 我们没有设置, 这里其实就是 策略设计模式
// 默认就是这个ThreadLocalSecurityContextHolderStrategy
else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
}
else {
// Try to load a custom strategy
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
initializeCount++;
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
public static SecurityContextHolderStrategy getContextHolderStrategy() {
return strategy;
}
}
既然已经知道策略类是 ThreadLocalSecurityContextHolderStrategy 直接上代码
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
// 看到这里就一目了然了 ThreadLocal 线程变量, 大家一定不陌生
// ThreadLocal 中存储的变量和当前线程绑定
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
public void clearContext() {
contextHolder.remove();
}
// 获取ThreadLocal中的内容 SecurityContext
public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
public void setContext(SecurityContext context) {
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
}
public interface SecurityContext extends Serializable {
// 获取认证 Authentication
Authentication getAuthentication();
// 设置认证 Authentication
void setAuthentication(Authentication authentication);
}
// 只有一个实现类 说白了就是 SecurityContext 中就是持有一个 Authentication
public class SecurityContextImpl implements SecurityContext {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// 认证 Authentication 抽象
private Authentication authentication;
public SecurityContextImpl() {}
public SecurityContextImpl(Authentication authentication) {
this.authentication = authentication;
}
@Override
public Authentication getAuthentication() {
return authentication;
}
@Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}
// 下边再讲 Authentication 是啥!
那我们在看一下 SecurityContext 是什么
通过以上源码的分析可知, SecurityContextHolder 用于存储安全上下文 SecurityContext 信息
而这个 SecurityContext 进一步持有 Authentication 即代表的是当前操作的用户所有信息: 用户是谁,该用户是否已经被认证,他拥有哪些角色权限等待… 这些都被保存在 Authentication 中
SecurityContextHolder 默认使用 ThreadLocal 策略来存储认证信息。当然从源码可以看出策略是可以配置的,
看到 ThreadLocal 也就意味着,这是一种与线程绑定的策略。Spring Security 在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息;
但这一切的前提,是你在 web 场景下使用 Spring Security,而如果是 Swing 界面,Spring 也提供了支持,SecurityContextHolder 的策略则需要被替换,鉴于我的初衷是基于 web 来介绍 Spring Security,所以这里以及后续,非 web 的相关的内容都会忽略。
2. Authentication
直接先上代码:
package org.springframework.security.core;
import java.security.Principal; // 注意这里是 java.security 包, 可见这里等级之高
public interface Authentication extends Principal, Serializable {
// 权限s 集合中放的是 GrantedAuthority 的子类
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials(); // 获取凭证
Object getDetails(); // 获取详细信息 eg. IP address
Object getPrincipal(); // 获取主体 Principal, 注意返回的是 Object
boolean isAuthenticated(); // 是否已认证
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; // set 方法
}
Authentication 是在 org.springframework.security.core 核心包中的接口
直接继承自 Principal 类 Principal单词含义 ( adj. 主要的,首要的 ) , 而 Principal 是位于 java.security 包中的
可以见得 Authentication 在 SpringSecurity 体系中是最高级别的身份认证的抽象
由这个顶级的权限认证接口Authentication我们可以获取很多信息, 我们逐一来分析:
2.1 getAuthorities()
权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系列字符串。
2.2 getCredentials()
凭证信息, 一般是密码信息,密码就是这个用户登录的凭证,
用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
2.3 getDetails()
细节信息,web 应用中的实现接口通常为 WebAuthenticationDetails,
它记录了访问者的 ip 地址和 sessionId 的值等信息
2.4 getPrincipal()
最重要的身份信息,大部分情况下返回的是 UserDetails 接口的实现类,也是框架中的常用接口之一
下边的章节会重点讲到
3. GrantedAuthority
上边 身份认证最高级别接口 Authentication 中的 getAuthorities()
就是要获取 GrantedAuthority的子类集合, GrantedAuthority 就代表了 '权限'
其实就是一种标识, 这个标识代表了某种权限, e.g 管理员角色权限, 普通用户角色权限等, 依赖的是角色标识;
更加细粒度的权限码, e.g 对于用户模块有一个删除用户的接口, 操作该接口需要的权限码是 'sys.user.delete'
只有访问的用户具有该权限标识码时, 才能访问成功, 否则失败;
package org.springframework.security.core;
public interface GrantedAuthority extends Serializable {
String getAuthority(); // 获取权限标识, 注意这里是String 字符串
}
这里贴出 GrantedAuthority 的一个框架提供的实现类 SimpleGrantedAuthority
package org.springframework.security.core.authority;
public final class SimpleGrantedAuthority implements GrantedAuthority {
private final String role; // 属性是 字符串 角色role
public SimpleGrantedAuthority(String role) {
this.role = role;
}
@Override
public String getAuthority() {
return role;
}
}
看代码可知和我们预想的一样
解释一下, 相当于说 Authentication#getAuthorities() 得到的是一个 角色集合:
String role
Collection< ? extends GrantedAuthority> getAuthorities() -> Collection<SimpleGrantedAuthority>
4. UserDetails
表示对用户概览的抽象, 代码中的注释已经写明其含义
package org.springframework.security.core.userdetails;
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); // 上问已经分析, 权限标识集合
String getPassword(); // 密码
String getUsername(); // 用户名
boolean isAccountNonExpired(); // 是否未过期
boolean isAccountNonLocked(); // 是否未锁定
boolean isCredentialsNonExpired(); // 凭证是否未过期
boolean isEnabled(); // 是否可用
}
5. UserDetailsService
用户详细信息服务接口 通过 username 查询 UerDetails
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
// 通过 username 查询 UerDetails
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
6. AuthenticationManager
认证管理器, 代码如下:
package org.springframework.security.authentication;
import org.springframework.security.core.Authentication; // 认证接口
public interface AuthenticationManager {
// authenticate vt. 鉴定;证明…是真实的 含义: 对传入的 Authentication 进行认证
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点.
7. ProviderManager
package org.springframework.security.authentication;
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
// 省略非主要部分 ...
...
// 持有 AuthenticationProvider 的集合
private List<AuthenticationProvider> providers = Collections.emptyList();
private AuthenticationManager parent; // 父级的管理器
// 构造
public ProviderManager(List<AuthenticationProvider> providers) {
this(providers, null);
}
public ProviderManager(List<AuthenticationProvider> providers,AuthenticationManager parent) {
this.providers = providers; // AuthenticationProvider的集合
this.parent = parent; // 父级
}
public List<AuthenticationProvider> getProviders() {
return providers;
}
// 重点在这里认证方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
Authentication result = null; // 认证的结果
// 循环遍历 属性 List<AuthenticationProvider> providers
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) { // 获取每一个 AuthenticationProvider 判断是否支持
continue; // 不支持跳过继续遍历
}
try {
// 重点 调用 AuthenticationProvider 的 authenticate
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
throw lastException;
}
// 省略非主要部分 ...
...
}
8. AuthenticationProvider
接口代码如下:
package org.springframework.security.authentication;
public interface AuthenticationProvider {
// 认证 Authentication
Authentication authenticate(Authentication authentication) throws AuthenticationException;
// 是否支持传入的 authentication
boolean supports(Class<?> authentication);
}
看到这里大家可能一面懵逼, 什么东西?
一个认证而已 又是AuthenticationManager,又是 ProviderManager ,又是 AuthenticationProvider
搞这么复杂 ???! what the fu**k !
其实我一开始阅读到这里也是心里一万头 艹尼玛呼啸而过. 但冷静下来仔细想了一下, 梳理一通之后, 突然想到Spring 架构中惯用的设计模式: 委托者模式Delegate, 好像开始理解设计者的用意了 :
因为实际项目中, 我们的登录认证方式可以 用户名 + 密码, 手机号+ 密码, 邮箱+密码, 甚至人脸登录等等
现实这么多登录方式, 框架也不知道用户究竟是使用哪一种, 所以说 AuthenticationManager 一般不直接认证,
AuthenticationManager 接口的实现类 ProviderManager 内部会维护一个 List<AuthenticationProvider> 列表,列表里存放多种认证方式, 遍历这个列表哪一个支持该认证方式, 就使用哪一个AuthenticationProvider来执行认证, 真正去执行具体认证的就是是这个AuthenticationProvider的实现类;
实际上这就是 委托者模式(Delegate) 的应用。 哈哈, 是不是突然豁然开朗 !!!
这里我自己画了一个流程图供大家参考, 链接: 认证流程图
6. UML架构图
核心组件 思维导图 源码查看组件关系:
https://www.processon.com/diagraming/5f9bdcea0791295c2ac17ad9