看源码可知
package org.springframework.security.core.userdetails; public interface UserDetailsService { UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException; } Spring Security安全身份认证流程原理
用户名和密码被过滤器获取到,封装成Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
AuthenticationManager 身份管理器负责验证这个Authentication
认证成功后,AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。
SecurityContextHolder安全上下文容器将第3步填充了信息的Authentication,通过SecurityContextHolder.getContext().setAuthentication()方法,设置到其中。
AuthenticationManager
初次接触Spring Security的朋友相信会被AuthenticationManager,ProviderManager ,AuthenticationProvider …这么多相似的Spring认证类搞得晕头转向,但只要稍微梳理一下就可以理解清楚它们的联系和设计者的用意。
AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录(还有这样的操作?没想到吧),所以说AuthenticationManager一般不直接认证,
AuthenticationManager接口的常用实现类ProviderManager 内部会维护一个List<AuthenticationProvider>列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。
也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式:用户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,手机号码+密码登录则对应了三个AuthenticationProvider。这样一来就好理解多了
UserDetails和UserDetailsServiceUserDetails
上面不断提到了UserDetails这个接口,它代表了最详细的用户信息,这个接口涵盖了一些必要的用户信息字段,我们一般都需要对它进行必要的扩展。
它和Authentication接口很类似,比如它们都拥有username,authorities,区分他们也是本文的重点内容之一。
Authentication的getCredentials()与UserDetails中的getPassword()需要被区分对待,前者是用户提交的密码凭证,后者是用户正确的密码,认证器其实就是对这两者的比对。Authentication中的getAuthorities()实际是由UserDetails的getAuthorities()传递而形成的。还记得Authentication接口中的getUserDetails()方法吗?其中的UserDetails用户详细信息便是经过了AuthenticationProvider之后被填充的。
UserDetailsService
UserDetailsService和AuthenticationProvider两者的职责常常被人们搞混,UserDetailsService只负责从特定的地方加载用户信息,可以是数据库、redis缓存、接口等
全局获取用户信息方式通过注入 Principal 接口获取用户信息
在运行过程中,Spring 会将 Username、Password、Authentication、Token 注入到 Principal 接口中,我们可以直接在controller获取使用
@GetMapping("/home") @ApiOperation("用户中心") public Result getUserHome(Principal principal) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=(UsernamePasswordAuthenticationToken)principal; return ResultResponse.success(usernamePasswordAuthenticationToken.getPrincipal()); }使用 @AuthenticationPrincipal 注解参数的方式
@GetMapping("/home") @ApiOperation("用户中心") public Result getUserHome(@AuthenticationPrincipal cn.soboys.kmall.security.entity.User user ) { return ResultResponse.success(user); }全局上下文获取
由于获取当前用户的用户名是一种比较常见的需求,其实 Spring Security 在 Authentication 中的实现类中已经为我们做了相关实现,所以获取当前用户的用户名有如下更简单的方式
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "当前登录用户:" + SecurityContextHolder.getContext().getAuthentication().getName(); } }获取当前登录用户的 UserDetails 实例,然后再转换成自定义的用户实体类 User,这样便能获取用户的 ID 等信息
@RestController public class HelloController { @GetMapping("/hello") public String hello() { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); User user = (User)principal; return "当前登录用户信息:" + user.toString(); } }异步方法中获取用户信息
Spring Security在默认情况下无法在使用@Async注解的方法中获取当前登录用户的。若想在@Async方法中获取当前登录用户,则需要调用SecurityContextHolder.setStrategyName方法并设置相关的策略
参考
Spring Security在@Async异步方法中获取登录用户