springboot + spring security验证token进行用户认证

r囧r小猫 2022-04-14 02:50 956阅读 0赞

核心组件

SecurityContextHolder

SecurityContextHolder是spring security最基本的组件。用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限等这些都被保存在SecurityContextHolder中。SecurityContextHolder默认是使用ThreadLocal实现的(ThreadLocal相关原理分析),这样就保证了本线程内所有的方法都可以获得SecurityContext对象。

可以通此方法过来获取当前操作用户信息:

  1. SecurityContextHolder.getContext().getAuthentication().getPrincipal();

默认返回的对象是UserDetails实例,其中包含了username,password和权限等信息,当然,我们也可以通过实现这个接口自定义我们自己的UserDetails实例,给我们自己的应用使用,以符合需要的业务逻辑。比如下面只对token进行操作就可以吧token作为属性放入UserDetails实现类中。

Authentication

Authentication是Spring Security方式的认证主体。

<1> Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于java.security包中的。可以见得,Authentication在spring security中是最高级别的身份/认证的抽象。

<2> 由这个顶级接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。
authentication.getPrincipal()返回了一个Object,我们将Principal强转成了Spring Security中最常用的UserDetails,这在Spring Security中非常常见,接口返回Object,使用instanceof判断类型,强转成对应的具体实现类。接口详细解读如下:

  • getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
  • getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
  • getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
  • getPrincipal(),最重要的身份信息,大部分情况下返回的是UserDetails接口的实现类,也是框架中的常用接口之一。

AuthenticationManager

AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中身份认证的方式有多种,一般不使用AuthenticationManager,而是使用AuthenticationManager的实现类ProviderManager ,ProviderManager内部会维护一个List列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式对应不同的AuthenticationProvider。

总结:

SecurityContextHolder:存放身份信息的容器

Authentication:用户信息的抽象

AuthenticationManager:身份认证器

认证流程

1、通过过滤器过滤到用户请求的接口,获取身份信息(假如有多个认证方式会配置provider的顺序)

2、一般将身份信息封装到封装成Authentication下的实现类UsernamePasswordAuthenticationToken中

3、通过AuthenticationManager 身份管理器(通过配置找到对应的provider)负责验证这个UsernamePasswordAuthenticationToken

4、认证成功后(认证逻辑一般在service中),AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。

5、SecurityContextHolder安全上下文容器将第2步填充了信息的UsernamePasswordAuthenticationToken,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中来建立安全上下文(security context)。

结合springboot实现对token验证

1、场景

拦截api/的所有接口进行验证,验证token用户与id用户是否一致,不一致或token过期则没有权限访问

2、实现

1、添加security相关依赖:spring-boot-starter-security spring-security-oauth2

2、全局配置类,根据不同需求配置不同的过滤器和provider(代码片段)

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  4. @Autowired
  5. private RedisTemplate redisTemplate;
  6. @Autowired
  7. private SecurityOrgPeopleMapper securityOrgPeopleMapper;
  8. @Autowired
  9. private ImCheckTokenFactory imCheckTokenFactory;
  10. // oauth2 server
  11. @Override
  12. protected void configure(AuthenticationManagerBuilder auth) {
  13. addProvider(auth);
  14. }
  15. //指定provider
  16. private void addProvider(AuthenticationManagerBuilder auth) {
  17. auth.authenticationProvider(imAuthenticationProvider());
  18. }
  19. @Override
  20. protected void configure(HttpSecurity http) throws Exception {
  21. // 请求过滤 对api/对所有接口都验证
  22. http
  23. .authorizeRequests()
  24. .antMatchers("/api/**").access("@permissionChecker.hasPermission(authentication,request)")
  25. .anyRequest().authenticated();
  26. registerFilter(http);
  27. }
  28. //指定filter过滤器
  29. private void registerFilter(HttpSecurity http) throws Exception {
  30. http
  31. .addFilterBefore(new ImAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
  32. }
  33. private ImAuthenticationProvider imAuthenticationProvider() {
  34. return new ImAuthenticationProvider(imCheckTokenFactory, securityOrgPeopleMapper);
  35. }
  36. }

过滤器:

  1. public class ImAuthenticationFilter extends GenericFilterBean {
  2. private AuthenticationManager authenticationManager;
  3. public ImAuthenticationFilter(AuthenticationManager authenticationManager) {
  4. this.authenticationManager = authenticationManager;
  5. }
  6. @Override
  7. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  8. // 其他过滤器已经认证通过了
  9. Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  10. if (authentication != null && authentication.isAuthenticated()) {
  11. chain.doFilter(request, response);
  12. return;
  13. }
  14. HttpServletRequest httpRequest = asHttp(request);
  15. HttpServletResponse httpResponse = asHttp(response);
  16. //获取接口中都用户信息
  17. String userId = obtainUserId(httpRequest);
  18. String token = obtainToken(httpRequest);
  19. String client = obtainClient(httpRequest);
  20. try {
  21. checkToken(token);
  22. imProcessTokenAuthentication(Integer.parseInt(userId), token, client);
  23. chain.doFilter(request, response);
  24. } catch (UserAuthenticationException userAuthenticationException) {
  25. logger.warn(userAuthenticationException.getMessage());
  26. httpResponse.setStatus(userAuthenticationException.getStatus());
  27. } catch (AuthenticationException authenticationException) {
  28. chain.doFilter(request, response);
  29. }
  30. }
  31. private String obtainToken(HttpServletRequest request) {
  32. String tokenParameter = "F-Session";
  33. String token = request.getHeader(tokenParameter);
  34. if (Objects.isNull(token)) {
  35. token = request.getParameter(tokenParameter);
  36. }
  37. return token;
  38. }
  39. private String obtainUserId(HttpServletRequest request) {
  40. String userIdParameter = "userId";
  41. return request.getParameter(userIdParameter);
  42. }
  43. private String obtainClient(HttpServletRequest request) {
  44. String clientParameter = "client";
  45. return request.getParameter(clientParameter);
  46. }
  47. private HttpServletRequest asHttp(ServletRequest request) {
  48. return (HttpServletRequest) request;
  49. }
  50. private HttpServletResponse asHttp(ServletResponse response) {
  51. return (HttpServletResponse) response;
  52. }
  53. private void checkToken(String token) {
  54. if (StringUtils.isEmpty(token)) {
  55. throw new UserAuthenticationException(SecurityHttpServletResponse.TOKEN_INVALID, "authenticate.fail");
  56. }
  57. }
  58. //im//将用户信息封装到ImTokenAuthentication(自定义用户信息类)中
  59. private void imProcessTokenAuthentication(Integer userId, String token, String client) {
  60. Authentication resultOfAuthentication = imTryToAuthenticateWithToken(userId, token, client);
  61. SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
  62. }
  63. private Authentication imTryToAuthenticateWithToken(Integer userId, String token, String client) {
  64. ImTokenAuthentication imTokenAuthentication = new ImTokenAuthentication(userId, token, client);
  65. return tryToAuthenticate(imTokenAuthentication);
  66. }
  67. private Authentication tryToAuthenticate(Authentication requestAuthentication) throws AuthenticationException {
  68. //找到配置的authenticationManager实现类provider进行验证返回充满信息的Authentication
  69. Authentication responseAuthentication = authenticationManager.authenticate(requestAuthentication);
  70. if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
  71. throw new InternalAuthenticationServiceException("Unable to authenticate for provided credentials");
  72. }
  73. logger.debug("User successfully authenticated");
  74. return responseAuthentication;
  75. }
  76. }

自定义authentication(一般继承UsernamePasswordAuthenticationToken,此项目是在前任的项目基础上写的)

  1. public class ImTokenAuthentication extends TokenAuthenticationToken {
  2. private Integer userId;
  3. private String client;
  4. public ImTokenAuthentication(Integer userId, String token, String client) {
  5. super(token);
  6. this.userId = userId;
  7. this.client = client;
  8. }
  9. public ImTokenAuthentication(Integer userId, String token, String client, SecurityUserDetails details) {
  10. super(token);
  11. this.userId = userId;
  12. this.client = client;
  13. setDetails(details);
  14. }
  15. public Integer getUserId() {
  16. return userId;
  17. }
  18. public String getClient() {
  19. return client;
  20. }
  21. }

provider

  1. public class ImAuthenticationProvider implements AuthenticationProvider {
  2. private SecurityOrgPeopleMapper securityOrgPeopleMapper;//根据项目需求注入
  3. private ImCheckTokenFactory imCheckTokenFactory;//根据项目需求注入
  4. public ImAuthenticationProvider(ImCheckTokenFactory imCheckTokenFactory, SecurityOrgPeopleMapper securityOrgPeopleMapper) {
  5. this.securityOrgPeopleMapper = securityOrgPeopleMapper;
  6. this.imCheckTokenFactory = imCheckTokenFactory;
  7. }
  8. @Override
  9. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  10. //自定义的装载用户信息的类
  11. ImTokenAuthentication imTokenAuthentication = (ImTokenAuthentication) authentication;
  12. //获取在过滤器中放入authentication的用户信息
  13. String token = authentication.getPrincipal().toString();
  14. Integer userId = Integer.parseInt(imTokenAuthentication.getUserId().toString());
  15. String client = imTokenAuthentication.getClient();
  16. //获取验证token所在的sevice
  17. ImCheckTokenService imCheckTokenService = imCheckTokenFactory.getService(client);
  18. if (Objects.isNull(imCheckTokenService)) {
  19. authentication.setAuthenticated(false);
  20. throw new UserAuthenticationException(SecurityHttpServletResponse.TOKEN_INVALID, "authenticate.fail");
  21. }
  22. //验证token逻辑
  23. Object object = imCheckTokenService.checkToken(userId, token);
  24. if (Objects.isNull(object)) {
  25. throw new BadCredentialsException("");
  26. }
  27. OrgPeople orgPeople = securityOrgPeopleMapper.getPeopleBySystemUserId(userId);
  28. imTokenAuthentication.setDetails(new SecurityUserDetails((Account)
  29. //在servcice中验证不通过就已经抛出异常了,此处正常运行则设置验证通过
  30. authentication.setAuthenticated(true);
  31. return authentication;
  32. }
  33. @Override
  34. public boolean supports(Class<?> authentication) {
  35. return (ImTokenAuthentication.class.isAssignableFrom(authentication));
  36. }
  37. }

参考:

spring security架构

https://www.cnblogs.com/shiyu404/p/6530894.html

https://blog.csdn.net/ro_wsy/article/details/44341547

官方文档

发表评论

表情:
评论列表 (有 0 条评论,956人围观)

还没有评论,来说两句吧...

相关阅读