SecurityContextHolder.getContext().getAuthentication()为null的情况

问题描述

在登录的时候,用如下方法获取输入的用户名:

/**
 * 获得当前用户名称
 */
private String getUsername() {
    String username = SecurityContextHolder.getContext().getAuthentication().getName();
    System.out.println("username= " + username);
    return username;
}

但是实际却不能取到用户名,错误日志如下:
错误异常情况

Spring Security 的基本组件 SecurityContextHolder

Spring Security 中最基本的组件应该是SecurityContextHolder了。这是一个工具类,只提供一些静态方法。这个工具类的目的是用来保存应用程序中当前使用人的安全上下文。

SecurityContextHolder的工作原理

缺省工作模式 MODE_THREADLOCAL

我们知道,一个应用同时可能有多个使用者,每个使用者对应不同的安全上下文,那么SecurityContextHolder是怎么保存这些安全上下文的呢 ?缺省情况下,SecurityContextHolder使用了ThreadLocal机制来保存每个使用者的安全上下文。这意味着,只要针对某个使用者的逻辑执行都是在同一个线程中进行即使不在各个方法之间以参数的形式传递其安全上下文,各个方法也能通过SecurityContextHolder工具获取到该安全上下文。只要在处理完当前使用者的请求之后注意清除ThreadLocal中的安全上下文,这种使用ThreadLocal的方式是很安全的。当然在Spring Security中,这些工作已经被Spring Security自动处理,开发人员不用担心这一点。
这里提到的SecurityContextHolder基于ThreadLocal的工作方式天然很适合Servlet Web应用,因为缺省情况下根据Servlet规范,一个Servlet request的处理不管经历了多少个Filter,自始至终都由同一个线程来完成。

注意: 这里讲的是一个Servlet request的处理不管经历了多少个Filter,自始至终都由同一个线程来完成;而对于同一个使用者的不同Servlet request,它们在服务端被处理时,使用的可不一定是同一个线程(存在由同一个线程处理的可能性但不确保)。

其他工作模式

有一些应用并不适合使用ThreadLocal模式,那么还能不能使用SecurityContextHolder了呢?答案是可以的。SecurityContextHolder还提供了其他工作模式。
比如有些应用,像Java Swing客户端应用,它就可能希望JVM中所有的线程使用同一个安全上下文。此时我们可以在启动阶段将SecurityContextHolder配置成全局策略MODE_GLOBAL。
还有其他的一些应用会有自己的线程创建,并且希望这些新建线程也能使用创建者的安全上下文。这种效果,可以通过将SecurityContextHolder配置成MODE_INHERITABLETHREADLOCAL策略达到。

使用SecurityContextHolder

获取当前用户信息

在SecurityContextHolder中保存的是当前访问者的信息。Spring Security使用一个Authentication对象来表示这个信息。一般情况下,我们都不需要创建这个对象,在登录过程中,Spring Security已经创建了该对象并帮我们放到了SecurityContextHolder中。从SecurityContextHolder中获取这个对象也是很简单的。比如,获取当前登录用户的用户名,可以这样:

String username = "";
// 获取安全上下文对象,就是那个保存在 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) {
    username = ((UserDetails) principal).getUsername();
} else {
    username = principal.toString();
}
修改SecurityContextHolder的工作模式

综上所述,SecurityContextHolder可以工作在以下三种模式之一:

  • MODE_THREADLOCAL (缺省工作模式)
  • MODE_GLOBAL
  • MODE_INHERITABLETHREADLOCAL
    修改SecurityContextHolder的工作模式有两种方法 :
    • 设置一个系统属性(system.properties) : spring.security.strategy; SecurityContextHolder会自动从该系统属性中尝试获取被设定的工作模式
    • 调用SecurityContextHolder静态方法setStrategyName(): 程序化方式主动设置工作模式的方法

解决方案

暂无

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐