分类
Spring Security

Spring Security如何获取当前登录用户

Retrieve User Information in Spring Security

1. 前言

本文将展示几种在Spring Security中获取当前登录用户的方法。

在Spring中有多种获取当前登录用户的方法,下面我们开始介绍几种较常见的方法。

2. 直接获取

最直接的获取方法是调用SecurityContextHolder的静态方法:

/**
 * 获取当前登录用户
 * @return
 */
String getCurrentLoginUser() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    return authentication.getName();
}

上述代码并不完美:如果当前没有用户登录,则获取到的authentication的值为null,上述代码将发生RuntimeException异常。所以如果我们不能保证代码运行时肯定有用户已登录,则需要加入null判断:

/**
 * 获取当前登录用户
 * @return
 */
String getCurrentLoginUser() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication == null) {
        throw new RuntimeException("当前无用户登录");
    } else {
        return authentication.getName();
    }
}

本方案虽然可行,但对单元测试的支持并不友好。所以在大多时候,我们并不推荐直接这么使用。

3. 在控制器中获取

如果一个类使用了@Controller注解被声明为了控制器,那么在该类的方法中可以直接声明Principal来获取当前认证用户:

@Controller
public class SecurityController {
    
    @GetMapping("/username")
    @ResponseBody
    public String currentUserName(Principal principal) {
        return principal.getName();
    }
}

当然了,也可以声明为Authentication:

    @RequestMapping(value = "/username1", method = RequestMethod.GET)
    @ResponseBody
    public String currentUserName1(Authentication authentication) {
        return authentication.getName();
    }

在Authentication中将认证用户类型声明为了 Object,这使得我们可以将任意的类型传入到Authentication中。这充分的保障了系统的灵活性,但同时也要求我们要使用时进行必要的类型转换,比如Spring Security中设置的类型为UserDeatils,则需要进行如下转换:

UserDetails userDetails = (UserDetails) authentication.getPrincipal();
System.out.println("User has authorities: " + userDetails.getAuthorities());

或者还可以通过注入的HttpServletRequest来直接获取认证用户:

    @RequestMapping(value = "/username2", method = RequestMethod.GET)
    @ResponseBody
    public String currentUserName2(HttpServletRequest request) {
        Principal principal = request.getUserPrincipal();
        return principal.getName();
    }

4. 使用自定义接口获取

充分的利用Spring的依赖注入功能将能够使得在应用的任意位置非常方便的获取当前登录用户信息,更重要的:这同时使得依赖于登录用户的单元测试变的异常容易。

public interface IAuthenticationFacade {
    Authentication getAuthentication();
}
@Service
public class IAuthenticationFacadeImpl implements IAuthenticationFacade {
    @Override
    public Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

如上代码对SecurityContextHolder的静态方法进行了封装,使得其它依赖于SecurityContextHolder的类与SecurityContextHolder解耦,同时在单元测试中可以轻松的Mock掉真实的getAuthentication方法,进而降低其它依赖于当前登录用户方法的测试难度:

@Controller
public class SecurityController {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private IAuthenticationFacade authenticationFacade;

    //...

    @RequestMapping(value = "/username3", method = RequestMethod.GET)
    @ResponseBody
    public String currentUserName3() {
        Authentication authentication = authenticationFacade.getAuthentication();
        return authentication.getName();
    }

5. 在JSP中获取登录用户

注意:由于笔者的能力原因,JSP中获取登录用户加入到code demo中。同时,将部分内容也未经验证,可能会有一定的偏差。

当前登录用户可以轻构在JSP页面中获取,你需要做的仅仅是在JSP页面中加入spring security标签库的支持:

<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

此时,便可以获取到当前的认证用户了:

<security:authorize access="isAuthenticated()">
    authenticated as <security:authentication property="principal.username" /> 
</security:authorize>

6. 在Thymeleaf中获取登录用户

Themeleaf是一款现代的服务端渲染模板引擎,它非常完美的与Spring MVC集成在一起。接下来我们展示在Themeleaf中如何获取当前登录用户。

首先我们加入 thymeleaf-spring5 以及 thymeleaf-extras-springsecurity5 依赖:

<dependency>
	<groupId>org.thymeleaf.extras</groupId>
	<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

<dependency>
	<groupId>org.thymeleaf</groupId>
	<artifactId>thymeleaf-spring5</artifactId>
</dependency>

现在便可以在HTML模板中使用sec:authorize来获取当前登录用户了:

<html xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<body>
<div sec:authorize="isAuthenticated()">
    当前登录用户是: <span sec:authentication="name"></span></div>
</body>
</html>

7. 总结

本文展示了几种获取当前登录用户的方法。如果您想了解更多关于获取当前登录用户的细节,请点击文章顶部的code demo链接来获取一份详尽的代码示例。同时我们在该示例为您准备了充分的测试以更好的观察代码示例的运行结果。