问题描述:项目的权限是用SpringSecurity实现的,项目中有个需求,实现需求的逻辑如下:
启动SpringBoot后启动一个线程去监听redis消息队列,对数据进行加工并保存到库里,需要保存创建人(createBy),而创建人正是当前登录用户,但一直获取不到当前登录用户。
解释如下:
我们将当前应用security上下文的所有数据保存在SecurityContextHolder里面,这些数据包括应用系统中使用的principle数据。SecurityContextHolder默认使用ThreadLocal局部变量保存这些数据,这意味着security上下文对于相同的正在执行的线程的方法是可用的,即使security上下文没有显式地将参数传递给这些方法,如果希望在当前principal请求处理完毕后要清理这些线程,这样使用ThreadLocal局部变量会是非常安全的。这样就保证了本线程内所有的方法都可以获得SecurityContext对象,当然,Spring Security会自动的处理这些问题,所以你不需要有任何的担心。
SecurityContextHolder还有其他两种模式,分别为SecurityContextHolder.MODE_GLOBAL和SecurityContextHolder.MODE_INHERITABLETHREADLOCAL,前者表示SecurityContextHolder对象的全局的,应用中所有线程都可以访问;后者用于线程有父子关系的情境中,线程希望自己的子线程和自己有相同的安全性。
大部分情况下我们不需要修改默认的配置,ThreadLocal是最常用也是最合适大部分应用的。
获得认证主体信息
我们可以用下面的代码段获得认证的主体信息
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
第一行代码返回的是一个UserDetails类型的实例,其中包含了username,password和权限等信息,当然,我们也可以通过实现这个接口自定义我们自己的UserDetails实例,给我们自己的应用使用,以符合需要的业务逻辑。
UserDetailsService
上面说到可以自定义UserDetails实例,我们怎么获得这个实例呢,就需要通过UserDetailsService来实现,这个接口只有一个方法
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
这个方法接受一个用户名参数,返回UserDetails实例。
认证成功之后,UserDetails对象用来构建Authentication对象并存放在SecurityContextHolder中,所以,我们需要的用户信息都可以通过SecurityContextHolder拿到。
GrantedAuthority
Authentication对象还提供了getAuthorities方法,获取用户被赋予的权限,权限通常对应着角色,什么角色对应着什么样的访问权限,例如ADMIN_ROLE可以访问/admin下的内容,其他角色则无权访问。
GrantedAuthority对象也通常由UserDetailsService实例获取。
总结
我们上面提到了如下几个对象:
SecurityContextHolder:提供对SecurityContext的访问
SecurityContext,:持有Authentication对象和其他可能需要的信息
Authentication:Spring Security方式的认证主体
GrantedAuthority:对认证主题的应用层面的授权
UserDetails:构建Authentication对象必须的信息,可以自定义,可能需要访问DB得到
UserDetailsService:通过username构建UserDetails对象