SpringSecurity Oauth2实战 - 06 获取用户登录信息并存储到本地线程

news2024/11/27 20:35:38

文章目录

    • 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<UserInfo> 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层方法之前调用
     */
    @Override
    public 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;
    }
    
    @Override
    public 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 {

    @Bean
    public UserInfoInterceptor userInfoInterceptor() {
        return new UserInfoInterceptor();
    }

    @Override
    public 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;
	}

	@Override
	public 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 {

    @Bean
    public UserInfoInterceptor userInfoInterceptor() {
        return new UserInfoInterceptor();
    }

    @Override
    public 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 {
    @Autowired
    private TokenStore tokenStore;

    @Value("${spring.application.name}")
    private String appName;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(appName);
        resources.tokenStore(tokenStore);
    }
    @Override
    public 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 {
      @Override
      public 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 {
    @Autowired
    private TokenStore tokenStore;

    @Value("${spring.application.name}")
    private String appName;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(appName);
        resources.tokenStore(tokenStore);
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            // 所有请求必须认证才能访问
            .anyRequest().authenticated()
            .and()
            .csrf().disable();
    }
}

在这里插入图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/3881.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

源码分析:Transport 开发

有关 transport 相关队列的调用过程: 【T ransportService 】 TransportService.java 的所有接口由 DefaultTransportService.java 实现,里面包含四种接口: (1)发送到 ruleEngine 发送 TbProtoQueueMsg<ToRuleEngineMsg> 消息。 由 DefaultTbRuleEngineConsumerServic…

【攻破css系列——附加篇】vscode自动格式化

文章目录1. 快速格式化1.1 格式化的定义1.2 vscode的格式化组合键2. 自动格式化2.1 定义2.2 设置自动格式化的步骤1. 快速格式化 1.1 格式化的定义 格式化会让我们的代码正确缩进&#xff0c;同级标签的缩进空格一致&#xff0c;最后使我们代码更好看且易懂。 没有格式化我们…

Nginx学习

Nginx学习 nginx的基本概念 nginx是什么&#xff1f;做什么事情&#xff1f; Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器&#xff0c;特点是占用内存少&#xff0c;并发能力强&#xff0c;事实上nginx的并发能力确实在同类型的网页服务器中表现较好。nginx专为…

gcc编程4步编译、调试c程序实操详解(Linux系统编程)

gcc编译可以执行程序4步骤:预处理、编译、汇编、链接 一、知识储备&#xff08;想看实战往下翻&#xff09; 在linux程序种&#xff0c;c程序需要用gcc进行编译&#xff0c;链接用ld程序&#xff0c;ggc编译完成后可自动调用ld程序完成链接。 调用gcc程序的语法格式&#xff…

Android Studio APP开发入门之对话框Dialog的讲解及使用(附源码 包括提醒对话框,日期对话框,时间对话框)

运行有问题或需要源码请点赞关注收藏后评论区留言~~~ 一、提醒对话框 AlertDialog名为提醒对话框&#xff0c;它是Android中最常用的对话框&#xff0c;可以完成常见的交互操作&#xff0c;例如提示&#xff0c;确认&#xff0c;选择等功能&#xff0c;由于AlertDialog没有公开…

CLRNet: Cross Layer Refinement Network for Lane Detection——论文简述

一、简介 CLRNet充分利用了低层次特征和高层次特征&#xff0c;因为两者是互补的&#xff0c;先基于高层次特征侦测道路&#xff0c;再基于底层次特征进行调优&#xff1b;由于遮挡的存在&#xff0c;使用ROIGather进行全局信息的收集&#xff0c;在ROI道路特征和全局特征图之…

SaaS 架构基础理论(一)

SaaS架构基础理论1、背景2、SaaS商业模式2.1、什么是SaaS2.2、SaaS软件的优势&#xff1a;2.3、SaaS劣势&#xff1a;3.SaaS应用架构3.1、SaaS成熟度模型3.2、SaaS成熟模型分级3.2.1、Level1 定制开发3.2.2、Level2 可配置3.2.3、Level3 高性能的多租户架构3.2.4、Level4 可伸缩…

【Docker】Docker安装与入门

Docker入门与基础命令 Docker简介 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux或Windows操作系统的机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xf…

Zookeeper几种应用

分布式锁 master-worker模式 涉及三种角色&#xff1a; 1. master&#xff0c;用于检测新的task、worker的添加&#xff0c;将新的task分配给worker处理 2. worker&#xff0c;将自己注册到系统&#xff0c;被master发现后&#xff0c;监控task 3. client&#xff0c;客户…

ARM pwn 入门 (3)

由于网上的ARM pwn题很少很难找&#xff0c;因此这里拿ROP Emporium的8道题做练习&#xff0c;这个网站有包含x86-64、x86-32、MIPS、ARM共4种架构的elf文件可以做。 1. ret2win 注意&#xff1a;在执行需要动态链接库加载的ARM elf文件时&#xff0c;如果直接使用qemu-arm x…

(免费分享)基于javaweb,ssm旅游景点预定系统

源码获取&#xff1a;关注文末gongzhonghao&#xff0c;输入003领取下载链接 IDEA开发工具,数据库&#xff1a;mysql&#xff0c;Tomcat8 采用&#xff1a;springmvcspringmybatis框架 &#xff08;1&#xff09;用户信息管理模块 用户信息管理模块分为后台管理员信息的维护…

zlMediaKit 3 socket模块--怎么封装socket,怎么connect listen/bind write read

socket.cpp socket.h socket SockInfo类&#xff0c;有四个获取四元组信息的虚函数一个获取自身标识符的虚函数 shared_from_this 原理关于boost中enable_shared_from_this类的原理分析 - 阿玛尼迪迪 - 博客园 (cnblogs.com) shared_ptr<Tp> shared_from_this() { re…

RabbitMQ学习(一)

目录&#xff1a; &#xff08;1&#xff09;什么是消息队列 &#xff08;2&#xff09;为什么要使用消息队列 &#xff08;3&#xff09;RabbitMQ特点 &#xff08;4&#xff09;RabbitMQ的安装 &#xff08;5&#xff09;RabbitMQ常用命令 &#xff08;6&#xff09;Ra…

【HTML】标签简单融合运用

&#x1f60a;博主页面&#xff1a;鱿年年 &#x1f449;博主推荐专栏&#xff1a;《WEB前端》&#x1f448; ​&#x1f493;博主格言&#xff1a;追风赶月莫停留&#xff0c;平芜尽处是春山❤️ 一、目录文件夹 1.在vscode建立一个新的目录文件夹如15-综合案例 2.将imag…

图的存储结构

图的存储结构 1.邻接矩阵表示法 设图G (V, E)是具有n个顶点的图&#xff0c;顶点顺序依次为{v1,v2,v3.......} 设a[N][N]为 n 阶方阵 G 的邻接矩阵具有此种性质&#xff1a; 若a[i][j]1&#xff0c;则存在边(vi, vj)或者弧<vi, vj> (即两点之间存在边或弧)若a[i][j]0…

day01 计算机基础和环境搭建

day01 计算机基础和环境搭建 课程目标&#xff1a;让大家了解计算机基础知识并完成python的环境搭建 课程概要&#xff1a; 计算机基础 编程的本质 python的介绍 python环境的搭建 1.计算机基础 1.1 基本概念 计算机的组成 计算机的组计算机是由多个硬件组合而成&#…

快2023了你不会还没学uni-app吧?(uniapp开发快速上手,uniapp项目创建,基础目录介绍)

uniapp新人上手指南前言开发工具快速尝鲜—创建uni-app项目项目基础目录介绍最后前言 uni-app 是一个使用 Vue.js (opens new window)开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布到14个平台&#xff0c;听起来是不是非常厉害&#xff0c;如果你后…

【论文阅读】Semi-supervised Sequence Learning半监督序列学习

【论文阅读】Semi-supervised Sequence Learning半监督学习 前言 半监督学习(Semi-Supervised Learning&#xff0c;SSL) 是模式识别和机器学习领域研究的重点问题&#xff0c;是监督学习与无监督学习相结合的一种学习方法。半监督学习使用大量的未标记数据&#xff0c;以及同…

别让 Linux 成为拿offer的阻碍

文章目录前言目录结构VI/VIM 编辑器是什么一般模式编辑模式&#xff08;插入模式&#xff09;命令模式模式间转换常用基础命令&#xff08;重要&#xff09;帮助命令man 获得帮助信息help 获得 shell 内置命令的帮助信息type 查看某命令是内置命令还是外部命令常用快捷键文件目…

Java 之 ElasticSearch8.x.x 【一篇文章精通系列】【ES的基本操作,ES安装,ES head + Kibana】

Java 之 ElasticSearch8.x.x 【一篇文章精通系列】【上&#xff1a;ES的基本操作&#xff0c;ES安装&#xff0c;ES head Kibana】一、ElasticSearch的安装1、解压安装ES2、熟悉目录3、启动ES4、安装可视化界面&#xff08;elasticsearch head&#xff09;5、了解ELK6、安装Ki…