本文内容来自王松老师的《深入浅出Spring Security》,自己在学习的时候为了加深理解顺手抄录的,有时候还会写一些自己的想法。
登录用户数据获取
登录成功之后,在后续的业务逻辑中开发者可能还需要获取到登录成功的用户对象,如果不是用任何安全管理框架那么可以将用户信息保存在HttpSession中,如果以后需要的话可以从HttpSession中获取数据。在Spring Security汇总,用户登录信息本质上还是保存在HttpSession中的,但是为了方便使用Spring Security对HttpSession中的用户信息进行了封装。
获取登录用户的信息离不开一个重要的对象:Authentication。在Spring Security中,Authentication对象主要由两方面功能:
- 作为AuthenticationManager的输入参数,提供用户身份认证的凭证。当它作为一个入参是,它的isAuthenticated方法返回false,表示用户还未认证。
- 代表已经认证过的用户,此时的Authentication可以从SecurityContext中获取。
一个Authentication对象主要包含三个方面的信息:
- principal:定义认证的用户。如果用户使用用户名、密码的方式登录,principal通常就是一个UserDetails对象。
- credentials:登录凭证,一般是指密码。当用户登录成功之后,登录凭证就会自动擦数,以防泄漏。
- authorities:用户被授予的权限信息
Java中本身提供了一个Principal接口来描述认证主体,Principal可以代表一个公司、个人或者登陆者ID。Spring Security中定义了Authentication接口用来规范登录用户信息,Authentication继承自Principal:
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
这里接口中定义的方法都很好理解:
- getAuthorities方法:用来获取用户权限
- getCredentials方法:用来获取用户凭证,一般来说是密码
- getDetails方法:用来获取用户详细信息,可能是当前请求之类
- getPrincipal方法:用来获取当前用户信息,可能是一个用户名,也可能是一个用户对象
- isAuthenticated方法:当前用户是否认证成功
可以看到在Spring Security中,只要获得Authentication对象就能获取到登录用户的详细信息。
不同的认证方式对应不同的Authentication实例。在Spring Security中的Authentication实例类如下图:
这些实现类看起来可能比较陌生,在后续的学习中这些实现类基本上都会涉及。在这些Authentication的实例中,最常用的的两个类:UsernamePasswordAuthenticationToken和RememberMeAuthentication。这里我们先混个眼熟,后面的学习中我们会学习到。大致了解了Authentication对象之后,接下来我们看下如何在登录成功后获取用户登录信息,即Authentication对象。
从SecurityContextHolder中获取
在前面的学习项目中增加一个UserController,内容如下:
/**
* @author tlh
* @date 2022/11/17 21:11
*/
@RestController
public class UserController {
@GetMapping("/user")
public Object userInfo() throws JsonProcessingException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String name = authentication.getName();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
ObjectMapper mapper = new ObjectMapper();
Map userMap = new HashMap(2);
userMap.put("userName", name);
userMap.put("authorities", authorities);
return mapper.writeValueAsString(userMap);
}
}
配置成功后启动项目,登录成功后访问/user接口,会返回用户名字和用户所拥有的的权限信息:
这里看到SecurityContextHolder.getContext( )是一个静态方法,也就意味着我们随时可以获取到登录的用户信息,在service层中也可以轻松获取到。获取用户登录信息的代码很简单,那么SecurityContextHolder到底是什么?它里面的数据又是从何而来的?
SecurityContextHolder
SecurityContextHolder中存储的是SecurityContext,SecurityContext中存储的是Authentication。三者的关系如下图:
在SecurityContextHolder中存储SecurityContext定义了三种存储模式。使用的典型的策略模式。
- MODE_THREADLOCAL:该种模式存放策略是将SecurityContext存放在ThreadLocal中。大家都知道ThreadLocal的特点就是在哪个线程中存储就只能在对应的线程中读取到,这个模式非常合适Web应用。应为一个请求无论经过多少个Filter到达Servlet都是有一个线程来处理。该模式也是Spring Security默认的存储SecurityContext的模式。
- MODE_INHERITABLETHREADLOCAL:该种存储模式适用于多线程环境,如果希望子线程也能获取到登录用户的数据,就可以使用该模式。
- MODE_GLOBAL:该模式实际使用了一个静态变量来存储SecurityContext。
我们看到SecurityContextHolder的源码大致上就看明白:
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";
private static final String MODE_PRE_INITIALIZED = "MODE_PRE_INITIALIZED";
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty("spring.security.strategy");
private static SecurityContextHolderStrategy strategy;
private static int initializeCount = 0;
public SecurityContextHolder() {
}
private static void initialize() {
initializeStrategy();
++initializeCount;
}
//根据 strategyName 来加载对应的SecurityContextHolderStrategy(定义了各种保存SecurityContext的策略接口)
private static void initializeStrategy() {
if ("MODE_PRE_INITIALIZED".equals(strategyName)) {
Assert.state(strategy != null, "When using MODE_PRE_INITIALIZED, setContextHolderStrategy must be called with the fully constructed strategy");
} else {
//默认采用ThreadLocal保存SecurityContext
if (!StringUtils.hasText(strategyName)) {
strategyName = "MODE_THREADLOCAL";
}
if (strategyName.equals("MODE_THREADLOCAL")) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
//InheritableThreadLocal不同于ThreadLoca在于在创建子线程时会将父线程中的数据复制到子线程中
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals("MODE_GLOBAL")) {
//采用一个静态变量保存SecurityContext
strategy = new GlobalSecurityContextHolderStrategy();
} else {
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
} catch (Exception var2) {
ReflectionUtils.handleReflectionException(var2);
}
}
}
}
//省略。。。
}