获取用户信息
// 获取安全上下文对象,就是那个保存在 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中。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 会对新的策略对象执行一次初始化,相应的该值会增1private 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_THREADLOCALstrategyName = 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 strategytry {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 + "]";}
}
SecurityContext
安全上下文,主要持有Authentication对象,如果用户未鉴权,那么Authentication对象将会是空的
public interface SecurityContext extends Serializable {/*** 获取当前经过身份验证的主体,或身份验证请求令牌*/Authentication getAuthentication();/*** 更改当前经过身份验证的主体,或删除身份验证信息*/void setAuthentication(Authentication 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;
}
GrantedAuthority
表示了当前用户所拥有的权限(或角色)信息,这些信息由授权负责对象AccessDecisionManager来使用,并决定最终用户是否可以访问某资源(URL或方法调用或域对象),鉴权使并不会使用到该对象
public interface GrantedAuthority extends Serializable {//获取当前用户所拥有的权限(或角色)信息String getAuthority();
}
UserDetailsService
提供一个接口loadUserByUsername(String username),一般通过扩展该接口显式获取我们的用户信息,用户登陆时传递的用户名和密码也是通过这里查找出来的用户名和密码进行校验,真正的校验由AuthenticationManager和AuthenticationProvider负责的。如果用户不存在时应返回NULL,而是抛出异常UsernameNotFoundException
public interface UserDetailsService {UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
UserDetails
规范了用户详细信息所拥有的字段,如用户名、密码、账号是否过期、是否锁定等,在SpringSecurity中,获取当前登录的用户的信息,一般情况是需要在该接口上面进行扩展
public interface UserDetails extends Serializable {// 返回权限集合Collection<? extends GrantedAuthority> getAuthorities();// 获取密码String getPassword();// 获取用户名String getUsername();// 判断用户是否未过期boolean isAccountNonExpired();// 判断账户是否未锁定boolean isAccountNonLocked();// 判断用户凭证是否没过期,即密码是否未过期boolean isCredentialsNonExpired();// 判断用户是否可用boolean isEnabled();
}
安全身份认证流程
过滤器链
认证流程
- Spring Security定义了一个过滤器链,当认证请求到达这个链时,该请求将会穿过这个链条用于认证和授权,这个链上可以定义1~N个过滤器,过滤器的用途是获取请求中的认证信息,根据认证方法进行路由,把认证信息传递给对应的认证处理程序进行处理,不同的过滤器处理不同的认证信息
- HTTP Basic认证通过过滤器链,到达BasicAuthenticationFilter
- HTTP Digest认证被DigestAuthenticationFilter识别,拦截并处理
- 表单登录认证被UsernamePasswordAuthenticationFilter识别,拦截并处理
- 基于用户凭证创建AuthenticationToken
如:用户在登录表单中输入用户名和密码,并点击确定,浏览器提交POST请求到服务器,穿过过滤器链,被UsernamePasswordAuthenticationFilter识别,UsernamePasswordAuthenticationFilter提取请求中的用户名和密码来创建UsernamePasswordAuthenticationToken对象
- 把组装好的AuthenticationToken传递给AuthenticationManager
如:组装好的UsernamePasswordAuthenticationToken对象被传递给AuthenticationManager的authenticate方法进行认证决策,AuthenticationManager只是一个接口,实际的实现是ProviderManager
- ProviderManager委托给AuthenticationProvider进行认证处理
AuthenticationProvider提供了不同的实现类,ProviderManager会把收到的UsernamePasswordAuthenticationToken对象传递给列表中的每一个AuthenticationProvider进行认证,那UsernamePasswordAuthenticationToken会被哪一个接收和处理呢?是由supports方法来决定的
- UserDetailsService获取用户信息
例如:DaoAuthenticationProvider通过UserDetailsService查找对应的用户信息
- 认证结果处理
例如:如果认证成功(用户名和密码完全正确),AuthenticationProvider将会返回一个完全有效的Authentication对象(UsernamePasswordAuthenticationToken),否则抛出AuthenticationException异常
认证完成后,AuthenticationManager将会返回该认证对象(UsernamePasswordAuthenticationToken)返回给过滤器
- 存储认证对象。相关的过滤器获得一个认证对象后,把他存储在安全上下文中(SecurityContext)用于后续的授权判断