// 获取安全上下文对象,就是那个保存在 ThreadLocal 里面的安全上下文对象
// 总是不为null(如果不存在,则创建一个authentication属性为null的empty安全上下文对象)
SecurityContext securityContext = SecurityContextHolder.getContext();
// 获取当前认证了的 principal(当事人),或者 request token (令牌)
// 如果没有认证,会是 null,该例子是认证之后的情况
Authentication authentication = securityContext.getAuthentication()
// 获取当事人信息对象,返回结果是 Object 类型,但实际上可以是应用程序自定义的带有更多应用相关信息的某个类型。
// 很多情况下,该对象是 Spring Security 核心接口 UserDetails 的一个实现类,你可以把 UserDetails 想像
// 成我们数据库中保存的一个用户信息到 SecurityContextHolder 中 Spring Security 需要的用户信息格式的
// 一个适配器。
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
SecurityContextHolder持有的是安全上下文的信息,当前操作的用户是谁,用户是否已经被认证,他拥有哪些角色权限等,这些都被保存在SecurityContextHolder中。SecurityContextHolder默认使用ThreadLocal策略来存储认证信息,在web环境下,SpringSecurity在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息
public class SecurityContextHolder {
// 三种工作模式的定义,每种工作模式对应一种策略
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";
// 类加载时首先尝试从环境属性中获取所指定的工作模式
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
private static SecurityContextHolderStrategy strategy;
// 初始化计数器,初始为0,
// 1. 类加载过程中会被初始化一次,此值变为1
// 2. 此后每次调用 setStrategyName 会对新的策略对象执行一次初始化,相应的该值会增1
private static int initializeCount = 0;
static {
initialize();
}
/**
* 清除上下文
*/
public static void clearContext() {
strategy.clearContext();
}
/**
* 获取上下文
*/
public static SecurityContext getContext() {
return strategy.getContext();
}
/**
* 获取计数器的值
*/
public static int getInitializeCount() {
return initializeCount;
}
private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
// Set default, 设置缺省工作模式/策略 MODE_THREADLOCAL
strategyName = MODE_THREADLOCAL;
}
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new 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 void setStrategyName(String strategyName) {
SecurityContextHolder.strategyName = strategyName;
initialize();
}
/**
* 获取对应工作模式的策略
*/
public static SecurityContextHolderStrategy getContextHolderStrategy() {
return strategy;
}
/**
* 创建空的上下文信息
*/
public static SecurityContext createEmptyContext() {
return strategy.createEmptyContext();
}
public String toString() {
return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount="
+ initializeCount + "]";
}
}
安全上下文,主要持有Authentication对象,如果用户未鉴权,那么Authentication对象将会是空的
public interface SecurityContext extends Serializable {
/**
* 获取当前经过身份验证的主体,或身份验证请求令牌
*/
Authentication getAuthentication();
/**
* 更改当前经过身份验证的主体,或删除身份验证信息
*/
void setAuthentication(Authentication authentication);
}
鉴权对象,该对象主要包含了用户的详细信息(UserDetails)和用户鉴权所需要的信息,如用户提交的用户名密码、Remember-me Token或digest hash值等,按不同鉴权方式使用不同的Authentication实现
public interface Authentication extends Principal, Serializable {
//用来获取用户的权限。
Collection<? extends GrantedAuthority> getAuthorities();
//用来获取用户凭证,一般来说就是密码。
Object getCredentials();
//用来获取用户携带的详细信息,可能是当前请求之类的东西。
Object getDetails();
//用来获取当前用户,可能是一个用户名,也可能是一个用户对象。
Object getPrincipal();
//判断当前用户是否认证成功。
boolean isAuthenticated();
//设置用户是否认证成功
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
表示了当前用户所拥有的权限(或角色)信息,这些信息由授权负责对象AccessDecisionManager来使用,并决定最终用户是否可以访问某资源(URL或方法调用或域对象),鉴权使并不会使用到该对象
public interface GrantedAuthority extends Serializable {
//获取当前用户所拥有的权限(或角色)信息
String getAuthority();
}
提供一个接口loadUserByUsername(String username),一般通过扩展该接口显式获取我们的用户信息,用户登陆时传递的用户名和密码也是通过这里查找出来的用户名和密码进行校验,真正的校验由AuthenticationManager和AuthenticationProvider负责的。如果用户不存在时应返回NULL,而是抛出异常UsernameNotFoundException
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
规范了用户详细信息所拥有的字段,如用户名、密码、账号是否过期、是否锁定等,在SpringSecurity中,获取当前登录的用户的信息,一般情况是需要在该接口上面进行扩展
public interface UserDetails extends Serializable {
// 返回权限集合
Collection<? extends GrantedAuthority> getAuthorities();
// 获取密码
String getPassword();
// 获取用户名
String getUsername();
// 判断用户是否未过期
boolean isAccountNonExpired();
// 判断账户是否未锁定
boolean isAccountNonLocked();
// 判断用户凭证是否没过期,即密码是否未过期
boolean isCredentialsNonExpired();
// 判断用户是否可用
boolean isEnabled();
}
如:用户在登录表单中输入用户名和密码,并点击确定,浏览器提交POST请求到服务器,穿过过滤器链,被UsernamePasswordAuthenticationFilter识别,UsernamePasswordAuthenticationFilter提取请求中的用户名和密码来创建UsernamePasswordAuthenticationToken对象
如:组装好的UsernamePasswordAuthenticationToken对象被传递给AuthenticationManager的authenticate方法进行认证决策,AuthenticationManager只是一个接口,实际的实现是ProviderManager
AuthenticationProvider提供了不同的实现类,ProviderManager会把收到的UsernamePasswordAuthenticationToken对象传递给列表中的每一个AuthenticationProvider进行认证,那UsernamePasswordAuthenticationToken会被哪一个接收和处理呢?是由supports方法来决定的
例如:DaoAuthenticationProvider通过UserDetailsService查找对应的用户信息
例如:如果认证成功(用户名和密码完全正确),AuthenticationProvider将会返回一个完全有效的Authentication对象(UsernamePasswordAuthenticationToken),否则抛出AuthenticationException异常
认证完成后,AuthenticationManager将会返回该认证对象(UsernamePasswordAuthenticationToken)返回给过滤器