SpringSecurity Oauth2实战 - 06 获取用户登录信息并存储到本地线程
创始人
2024-01-20 06:49:11
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方法。

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...