SpringSecurity Oauth2实战 - 06 获取用户登录信息并存储到本地线程
创始人
2024-04-04 13:59:09
0

文章目录

    • 1. 获取用户登录信息
      • 1. 用户信息共享的ThreadLocal类 UserInfoShareHolder
      • 2. 写一个拦截器 UserInfoInterceptor
      • 3. 配置拦截器 CommonWebMvcAutoConfiguration
    • 2. 源码分析
      • 1. 认证用户通过access_token访问受限资源
      • 2. 进入过滤器 OAuth2AuthenticationProcessingFilter#doFilter方法
      • 3. 进入拦截器 UserInfoInterceptor#preHandle方法
      • 4. 进入HttpServletRequest#getUserPrincipal方法
      • 5.进入SecurityContextHolderAwareRequestWrapper#getUserPrincipal方法
      • 6. 进入控制器 HelloController#hello方法
    • 3. 源码分析
      • 1. 未认证用户获取access_token
      • 2. 进入过滤器 OAuth2AuthenticationProcessingFilter#doFilter方法
      • 3. 进入拦截器 UserInfoInterceptor#preHandle 方法
      • 4. 进入 HttpServlet3RequestFactory#authenticate方
    • 4. 源码分析

上一讲已经我们分析了/oauth/token认证流程,明白了整个认证过程核心做了哪些事情,这一讲看一下如何配置拦截器判断用户是否登录并获取用户登录信息,同时将获取的登录信息存储到本地线程中,主要分为两点展开说明:

  • 首先,获取用户登录信息并存储到本地线程功能实现;
  • 其次,debug断点分析整个源码流程;

1. 获取用户登录信息

1. 用户信息共享的ThreadLocal类 UserInfoShareHolder

/*** 用户信息共享的ThreadLocal类*/
public class UserInfoShareHolder {private static final ThreadLocal USER_INFO_THREAD_LOCAL = new TransmittableThreadLocal<>();/*** 存储用户信息*/public static void setUserInfo(UserInfo userInfo) {USER_INFO_THREAD_LOCAL.set(userInfo);}/*** 获取用户相关信息*/public static UserInfo getUserInfo() {return USER_INFO_THREAD_LOCAL.get();}/*** 清除ThreadLocal信息*/public static void remove() {USER_INFO_THREAD_LOCAL.remove();}
}

2. 写一个拦截器 UserInfoInterceptor

/*** 拦截器:用户信息本地线程存储*/
public class UserInfoInterceptor extends HandlerInterceptorAdapter {/*** 拦截所有请求,在Controller层方法之前调用*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 判断用户是否被认证,如果没有认证不放行 boolean isAuthenticated = request.authenticate(response);if (!isAuthenticated) {return false;}// 存储用户信息到本地线程Principal userPrincipal = request.getUserPrincipal();OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) userPrincipal;AuthUser ngsocUser = (AuthUser) oAuth2Authentication.getUserAuthentication().getPrincipal();UserInfo userInfo = ngsocUser.getUserInfo();UserInfoShareHolder.setUserInfo(userInfo);// 放行,继续执行Controller层的方法return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserInfoShareHolder.remove();super.afterCompletion(request, response, handler, ex);}
}

3. 配置拦截器 CommonWebMvcAutoConfiguration

/*** 配置拦截器*/
@Configuration
@EnableWebMvc
public class CommonWebMvcAutoConfiguration implements WebMvcConfigurer {@Beanpublic UserInfoInterceptor userInfoInterceptor() {return new UserInfoInterceptor();}@Overridepublic void addInterceptors(@NonNull InterceptorRegistry registry) {// 注意:这里的excludePath不需要自己再加上contextPath, spring会自动加// 添加存储用户信息的拦截器,配置拦截请求路径// 拦截器会拦截所有请求,需要配置放行的请求registry.addInterceptor(userInfoInterceptor())// 放行的请求.excludePathPatterns("/api/v1/login");}
}

2. 源码分析

1. 认证用户通过access_token访问受限资源

@RestController
@RequestMapping("/api/v1")
public class HelloController {@GetMapping("/hello")public String hello(HttpServletRequest request){String username = UserInfoShareHolder.getUserInfo().getUsername();return username;}
}

在这里插入图片描述

在这里插入图片描述

2. 进入过滤器 OAuth2AuthenticationProcessingFilter#doFilter方法

当一个认证用户访问系统的受限资源时,请求首先被OAuth2AuthenticationProcessingFilter过滤器拦截,在该过滤器的doFilter方法中主要做了以下事情:

  • 从请求中提取 token 并获取待认证的Authentication 对象: Authentication authentication = tokenExtractor.extract(request);
  • request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
  • 通过待认证的Authentication对象倒TokenStore中获取完成的Authentication对象:Authentication authResult = authenticationManager.authenticate(authentication);
  • 发布认证成功的事件通知:eventPublisher.publishAuthenticationSuccess(authResult);
  • SecurityContextHolder.getContext().setAuthentication(authResult);
  • 进行过滤器链中的下一个过滤器;

所以在请求一开始就把完成的认证对象的放在SecurityContextHolder.getContext()中,我们就可以从SecurityContextHolder.getContext()中获取Authentication对象了。

在这里插入图片描述

3. 进入拦截器 UserInfoInterceptor#preHandle方法

经过SpringSecurity的一系列过滤器链后,会进入UserInfoInterceptor#preHandle方法

在这里插入图片描述

4. 进入HttpServletRequest#getUserPrincipal方法

public interface HttpServletRequest extends ServletRequest {Principal getUserPrincipal();
}

在这里插入图片描述

5.进入SecurityContextHolderAwareRequestWrapper#getUserPrincipal方法

public class SecurityContextHolderAwareRequestWrapper extends HttpServletRequestWrapper {private Authentication getAuthentication() {//从SecurityContextHolder.getContext()中Authentication对象Authentication auth = SecurityContextHolder.getContext().getAuthentication();if (!trustResolver.isAnonymous(auth)) {return auth;}return null;}@Overridepublic Principal getUserPrincipal() {Authentication auth = getAuthentication();if ((auth == null) || (auth.getPrincipal() == null)) {return null;}return auth;}
}

在这里插入图片描述

6. 进入控制器 HelloController#hello方法

@RestController
@RequestMapping("/api/v1")
public class HelloController {@GetMapping("/hello")public String hello(HttpServletRequest request){String username = UserInfoShareHolder.getUserInfo().getUsername();return username;}
}

在这里插入图片描述

3. 源码分析

假如我们在配置拦截器时,拦截所有请求,不放行/api/v1/login请求会如何?

/*** 配置拦截器*/
@Configuration
@EnableWebMvc
public class CommonWebMvcAutoConfiguration implements WebMvcConfigurer {@Beanpublic UserInfoInterceptor userInfoInterceptor() {return new UserInfoInterceptor();}@Overridepublic void addInterceptors(@NonNull InterceptorRegistry registry) {// 注意:这里的excludePath不需要自己再加上contextPath, spring会自动加// 拦截器会拦截所有请求,需要配置放行的请求registry.addInterceptor(userInfoInterceptor());}
}

1. 未认证用户获取access_token

注意:资源服务器配置类 ResourceServerAutoConfiguration 中已经配置了该请求接口不需要认证就能访问

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {@Autowiredprivate TokenStore tokenStore;@Value("${spring.application.name}")private String appName;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.resourceId(appName);resources.tokenStore(tokenStore);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 配置不需要认证就可以访问的请求.antMatchers("/api/v1/login").permitAll()// 其他请求必须认证才能访问.anyRequest().authenticated().and().csrf().disable();}
}

在这里插入图片描述

2. 进入过滤器 OAuth2AuthenticationProcessingFilter#doFilter方法

在这里插入图片描述

在这里插入图片描述

3. 进入拦截器 UserInfoInterceptor#preHandle 方法

在这里插入图片描述

结论:资源服务器配置类 ResourceServerAutoConfiguration 中已经配置了该请求接口不需要认证就能访问,但是拦截器UserInfoInterceptor#preHandle 方法会在AuthController#authority方法执行之前执行,在该拦截器中从请求中获取Authentication对象判断用户是否认证,如果用户未认证则不放行,因此就不会执行AuthController#authority方法,所以拦截器必须配置不拦截的请求路径;

4. 进入 HttpServlet3RequestFactory#authenticate方

final class HttpServlet3RequestFactory implements HttpServletRequestFactory {@Overridepublic boolean authenticate(HttpServletResponse response)
throws IOException, ServletException {AuthenticationEntryPoint entryPoint = HttpServlet3RequestFactory.this.authenticationEntryPoint;if (entryPoint == null) {return super.authenticate(response);}if (isAuthenticated()) {return true;}// 异常处理entryPoint.commence(this, response,new AuthenticationCredentialsNotFoundException( "User is not Authenticated"));return false;}}
}

在这里插入图片描述

4. 源码分析

假如我们在配置拦截器时放行了/api/v1/login请求,但是资源服务器中没有放行/api/v1/login请求的认证会如何?

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {@Autowiredprivate TokenStore tokenStore;@Value("${spring.application.name}")private String appName;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.resourceId(appName);resources.tokenStore(tokenStore);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 所有请求必须认证才能访问.anyRequest().authenticated().and().csrf().disable();}
}

在这里插入图片描述

经过debug发现,请求首先会进入过滤器OAuth2AuthenticationProcessingFilter#doFilter方法,然后判断用户未登录,但是最终不会进入AuthController#authority方法。

相关内容

热门资讯

保存时出现了1个错误,导致这篇... 当保存文章时出现错误时,可以通过以下步骤解决问题:查看错误信息:查看错误提示信息可以帮助我们了解具体...
汇川伺服电机位置控制模式参数配... 1. 基本控制参数设置 1)设置位置控制模式   2)绝对值位置线性模...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
不一致的条件格式 要解决不一致的条件格式问题,可以按照以下步骤进行:确定条件格式的规则:首先,需要明确条件格式的规则是...
本地主机上的图像未显示 问题描述:在本地主机上显示图像时,图像未能正常显示。解决方法:以下是一些可能的解决方法,具体取决于问...
表格列调整大小出现问题 问题描述:表格列调整大小出现问题,无法正常调整列宽。解决方法:检查表格的布局方式是否正确。确保表格使...
表格中数据未显示 当表格中的数据未显示时,可能是由于以下几个原因导致的:HTML代码问题:检查表格的HTML代码是否正...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...