搭建新版security-oauth2协议,流程代码详解,源码分析

news2024/11/27 10:33:31

前言:最近在学习搭建oauth2协议的开放平台,把搭建框架时的思路以及遇到的问题记录下来。

文章会持续更新,前期可能会比较零碎,最后会整合一起,写一篇从部署到使用、踩坑、依赖版本解决等完整文章。

使用的是Spring Security Oauth2的新版框架,官网地址:点此跳转

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-authorization-server</artifactId>
            <version>0.3.1</version>
        </dependency>

下面分享开始,今天先记录下授权服务器的授权过程:

授权服务器的授权过程

在授权服务器中会定义两个filterChain过滤器链,一个是系统的认证定制,一个是授权服务器的认证定制。

  • 授权服务器的定制:如官方文档一样,声明了未从授权端点进行身份验证时重定向到登录页面
  • 原系统的认证授权定制:加了一些基础的系统定制,如登录失败处理器、鉴权、端点白名单、跨域、访问拒绝处理器等
    // 授权服务器的认证授权定制
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
            throws Exception {
        // 配置当前 FilterChain 只会拦截oauth2相关的请求
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                .exceptionHandling((exceptions) -> exceptions
                        .authenticationEntryPoint(
                                new LoginUrlAuthenticationEntryPoint("/login"))
                )
                // Accept access tokens for User Info and/or Client Registration
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        return http.build();
    }

    // 原系统的认证授权定制
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .cors()
                .configurationSource(corsConfigurationSource);
        http
                .exceptionHandling()
                .accessDeniedHandler(new DefaultAccessDeniedHandler());
        http
                .logout()
                .clearAuthentication(true)
                .logoutUrl(SecurityConstants.AUTH_LOGOUT)
                .logoutSuccessHandler(new DefaultLogoutSuccessHandler());
        http
//                 Form login handles the redirect to the login page from the
//                 authorization server filter chain
                .formLogin()
                .loginProcessingUrl(SecurityConstants.LOGIN_PROCESSING_URL)
                .failureHandler(new DefaultAuthenticationFailureHandler())
//                .successHandler(new DefaultAuthenticationSuccessHandler())
        ;

        http.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> {
            authorizationManagerRequestMatcherRegistry
                    .antMatchers(SecurityConstants.URL_IMAGE_CAPTCHA, "/oauth2/**", "/login/**")
                    .permitAll()
                    .anyRequest()
                    .access(defaultSecurityExpressionRoot);
        });
        return http.build();
    }

三方客户端拉起授权码模式的授权流程:

  1. 点击授权链接发起请求(包含颁发的三方客户端信息:key、secret)
  2. 携带用户在开放平台系统(例如微信授权,则需要在微信系统中先登录)所需的token作为header参数
  3. 如果授权服务器的sessionFilter未发现有开放平台系统的授权信息(token),则会重定向到开放平台系统的登录页面
  4. 登录成功后重定向到开放平台系统的授权页(如配置无需确认授权,则会跳过该流程)
  5. 用户确认授权后再次重定向到三方客户端在开放平台配置的重定向地址,并且带上授权服务器的code参数

这里有点绕,给大家画个图帮助下理解

授权码模式流程图

![在这里插入图片描述](https://img-blog.csdnimg.cn/dc09b304de8148308d7d264e87f80e93.png
如果有微信h5、微信小程序等开放平台对接经验的小伙伴应该很容易理解。

模拟流程:

  1. 第一步把系统中暂时写死的三方客户端ID和secret拼接好(重定向地址先写百度官网),模拟下授权请求
    http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=messaging-client&client_secret=secret&redirect_uri=https://www.baidu.com

可以看到链接302重定向到了login页面,该页面是Oauth2的默认登录页面
在这里插入图片描述

2.由于第一步是直接申请授权,并无开放平台的token信息,来看下sessionFilter是怎么处理的:
我这里是在security中定制了sessionId的形式,使用header中的x-auth-token作为主要的session获取方式,cookie作为辅助使用

    @Bean
    public HttpSessionIdResolver httpSessionIdResolver() {
        HeaderHttpSessionIdResolver primaryHttpSessionIdResolver = HeaderHttpSessionIdResolver.xAuthToken();
        CookieHttpSessionIdResolver cookieHttpSessionIdResolver = new CookieHttpSessionIdResolver();
        return new CompositeHttpSessionIdResolver(primaryHttpSessionIdResolver, Arrays.asList(primaryHttpSessionIdResolver, cookieHttpSessionIdResolver));
    }

public final class CompositeHttpSessionIdResolver implements HttpSessionIdResolver {

    private final HttpSessionIdResolver primaryHttpSessionIdResolver;

    private final List<HttpSessionIdResolver> httpSessionIdResolvers;

    public CompositeHttpSessionIdResolver(HttpSessionIdResolver primaryHttpSessionIdResolver, List<HttpSessionIdResolver> httpSessionIdResolvers) {
        this.primaryHttpSessionIdResolver = primaryHttpSessionIdResolver;
        this.httpSessionIdResolvers = httpSessionIdResolvers;
    }
}

HttpSessionSecurityContextRepository,会先校验下是否有当前session对应的已成功认证的上下文信息

源码对应:HttpSessionSecurityContextRepository.loadContext()方法

	@Override
	public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
		HttpServletRequest request = requestResponseHolder.getRequest();
		HttpServletResponse response = requestResponseHolder.getResponse();
		HttpSession httpSession = request.getSession(false);
		SecurityContext context = readSecurityContextFromSession(httpSession);
		if (context == null) {
			context = generateNewContext();
			if (this.logger.isTraceEnabled()) {
				this.logger.trace(LogMessage.format("Created %s", context));
			}
		}
		if (response != null) {
			SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request,
					httpSession != null, context);
			requestResponseHolder.setResponse(wrappedResponse);
			requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse));
		}
		return context;
	}

getSession中源码:
getSession方法会调用getRequestedSession方法来获取session
SessionRepositoryFilter.getSession().getRequestedSession()

private S getRequestedSession() {
			if (!this.requestedSessionCached) {
				List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this);
				for (String sessionId : sessionIds) {
					if (this.requestedSessionId == null) {
						this.requestedSessionId = sessionId;
					}
					S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId);
					if (session != null) {
						this.requestedSession = session;
						this.requestedSessionId = sessionId;
						break;
					}
				}
				this.requestedSessionCached = true;
			}
			return this.requestedSession;
		}

这里可以看到当找到sessionID后会经sessionRepository属性来获取session的值,对于这里我也做了定制化,使用了RessionSessionRepository,这样做的原因是为了以后分布式部署和集群下的session信息共享

    @Bean
    @ConditionalOnBean(RedissonSessionRepository.class)
    public SessionRegistry sessionRegistry(RedissonSessionRepository sessionRepository,
                                           @Value("${spring.application.name}") String applicationName) {
        if (StrUtil.isNotBlank(applicationName)) {
            sessionRepository.setKeyPrefix(buildKeyPrefix(applicationName) + ":session:");
        }
        return new SpringSessionBackedSessionRegistry<>(sessionRepository);
    }

获取session后会校验readSecurityContextFromSession,发现是无认证信息的,会被FilterSecurityInterceptor拦截器抛出异常,源码地址在:
FilterSecurityInterceptor.invoke方法的super.beforeInvocation(filterInvocation);
3. 过滤器链继续向下执行,经ExceptionTranslationFilter捕获到异常handleSpringSecurityException方法中重定向到配置的默认登录地址:http://127.0.0.1:8080/login
4. 在登录页面填写暂时写死的平台账号进行登录,再次经过HttpSessionSecurityContextRepository.loadContext方法,不过依旧是未认证经定制认证成功后,被认证成功处理器AbstractAuthenticationProcessingFilter.onAuthenticationSuccess保存session和执行重定向操作。此时重定向地址为:http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=messaging-client&client_secret=secret&redirect_uri=https://www.baidu.com。此时因已经对sessionID做了save操作,会找到sessionID对应的认证信息。继续向下操作。
5. 经OAuth2AuthorizationEndpointFilter过滤器,并且会在OAuth2AuthorizationCodeRequestAuthenticationProvider.validate方法中校验redirectUri与系统中暂时写死的是否一致,authenticate方法查询后,再次重定向到指定uri,并且携带code参数

在这里插入图片描述

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

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

相关文章

【深度学习】深度强化学习初学者指南

一、说明 GAN&#xff08;Generative Adversarial Networks&#xff09;是一种深度学习模型&#xff0c;它由两个神经网络组成&#xff1a;一个生成网络和一个判别网络。生成网络学习如何生成类似于给定数据集的新数据&#xff0c;而判别网络则学习如何区分生成网络生成的数据和…

Linux下的基本指令

Linux下的基本指令 操作系统的概念01.adduser指令02.userdel指令03.pwd指令04.ls指令05.tree指令06.cd指令07.touch指令08.mkdir指令&#xff08;重要&#xff09;09.rmdir指针&&rm指令&#xff08;重要&#xff09;10.man指针&#xff08;重要&#xff09;11.cp指令&a…

【macOS 系列】如何调整启动台图标大小和行数

1、使用指令&#xff0c;这是隐藏的技巧&#xff0c;在控制台输入如下指令 defaults write com.apple.dock springboard-rows -int 6 defaults write com.apple.dock springboard-columns -int 8 defaults write com.apple.dock ResetLaunchPad -bool TRUE killall Dock以上表…

基于Web的智慧交通3D可视化系统

前言 城市交通是城市社会活动、经济活动的纽带和动脉&#xff0c;智慧交通系统对城市经济发展和人民生活水平起着极其重要的作用。 背景 随着我国城市化进程不断加快&#xff0c;现代城市交通问题日益受到人们的关注。特别是汽车数量的与日俱增&#xff0c;给城市带来了大量…

Socket介绍及使用Java实现socket通信

一、Socket概述 Socket&#xff08;套接字&#xff09;是计算机网络编程中用于实现网络通信的一种机制。它提供了一种编程接口&#xff0c;允许应用程序通过网络进行数据传输&#xff0c;实现不同主机之间的通信。 Socket可以看作是一种抽象的概念&#xff0c;用于描述网络通信…

性能监控软件的软件特色有哪些?

性能监控软件是一种专门用于监测和分析系统性能的软件工具。它可以帮助开发人员、运维人员和系统管理员追踪系统的性能瓶颈&#xff0c;优化系统性能&#xff0c;提供稳定可靠的服务&#xff0c;那性能监控软件的软件特色有哪些&#xff1f; 实时监控&#xff1a;性能监控软件可…

3、CCesium 第一个地图

1、将下载的CCsium库解压到指定目录如D:/install&#xff0c;里面有几个文件 bin&#xff1a;导出的dll examples&#xff1a;一个简单的示例 include&#xff1a;ccesium和第三方库的头文件 lib&#xff1a;搜索cceisum库的一些信息 share&#xff1a;一些说明 我们主要用…

allatori-JAVA代码混淆工具

Allatori是第二代Java混淆器&#xff0c;它为您的知识产权提供全方位的保护。 虽然大多数第二代混淆器都能提供值得信赖的保护&#xff0c;但我们在Allatori中开发了一些额外的功能&#xff0c;使代码的反向工程几乎不可能。 Allatori不仅仅是混淆&#xff0c;它还能最大限度…

为什么你感觉中层管理一直闲着没事干?

点击下方“JavaEdge”&#xff0c;选择“设为星标” 第一时间关注技术干货&#xff01; 免责声明~ 切记&#xff0c;任何文章不要过度深思&#xff08;任何东西都无法经得起审视&#xff0c;因为这世上没有同样的成长环境&#xff0c;也没有同样的认知水平同时也「没有适用于所…

eNSP的使用

目录 配置路由器 DHCP——动态主机配置协议 工作过程&#xff1a; 第一种场景——PC首次获取IP地址的情况 第二种场景——pc&#xff08;客户都&#xff09;再次获取IP地址的情况 DHCP配置&#xff1a; Telnet 搭建好图后 1.看广播域&#xff08;路由器有几个接口就有几…

在树莓派上搭建web站点并发布互联网上线【无需公网IP】

文章目录 概述使用 Raspberry Pi Imager 安装 Raspberry Pi OS设置 Apache Web 服务器测试 web 站点安装静态样例站点将web站点发布到公网安装 Cpolar内网穿透cpolar进行token认证生成cpolar随机域名网址生成cpolar二级子域名将参数保存到cpolar配置文件中测试修改后配置文件配…

JUC--CompletableFuture下

对计算速度进行选用 import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit;public class Test4 {public static void main(String[] args) {CompletableFuture<String> a CompletableFuture.supplyAsync(() -> {try { TimeUnit.SE…

list和vector容器的插入与访问操作区别

文章目录 list插入元素和vector插入元素对比案例vector的做法list优化的做法为什么时间复杂度相同还会有性能差异 std::list和std::vector是C中的两种常见数据结构&#xff0c;它们在不同的使用场景下各有优势。 std::vector的内部实现是动态数组&#xff0c;它在连续的内存块…

weak_ptr 智能指针的使用

目录 一&#xff0c;weak_ptr 变量的定义 二&#xff0c;expired() 成员函数 三&#xff0c;lock() 成员函数 四&#xff0c;use_count() 成员函数 五&#xff0c;为什么要用 weak_ptr 一&#xff0c;weak_ptr 变量的定义 weak_ptr 对象的构造有3种方法&#xff1a; 1&am…

Python基础综合案例-数据可视化(折线图)

今天给大家带来的是Python综合实战开发的数据可视化操作 通过python实现对数据的分析、可视化 数据来源: 系统开发环境&#xff1a; 操作系统&#xff1a;win11 Python解释器版本&#xff1a;python3.10 开发工具&#xff1a;Pychram Python内置模块&#xff1a;pyecharts 1…

HarmonyOS/OpenHarmony应用开发-Stage模型应用/组件级配置

在开发应用时&#xff0c;需要配置应用的一些标签&#xff0c;例如应用的包名、图标等标识特征的属性。本文描述了在开发应用需要配置的一些关键标签。图标和标签通常一起配置&#xff0c;可以分为应用图标、应用标签和入口图标、入口标签&#xff0c;分别对应app.json5配置文件…

二进制算术运算

当两个数码表示数量大小时&#xff0c;可以进行加减乘除等算术运算。 一&#xff1a;两数绝对值之间的运算 加法运算&#xff1a; 二进制的每一位只有0和1两个数&#xff0c;低位向高位的进位关系是“逢二进一”&#xff0c;所以中的每一位的运算规则为&#xff1a; 减法运算&…

【无标题】企业数字化转型需要什么样的生产力工具?

前言 企业的数字化转型是一个很大的话题&#xff0c;从宏观到微观&#xff0c;涉及到目标、战略、方案、路径、计划、组织、流程等方方面面。我们今天聊一个非常落地非常具体的问题——企业数字化转型需要什么样的生产力工具&#xff1f; 需要哪些类型的生产力工具&#xff1…

论文摘要写什么内容

一、引言 论文摘要是我们在学术研究领域当中最常见的一种形式&#xff0c;它扮演着关键的角色&#xff0c;为研究者在学术圈子中传播自己思想提供了重要途径。然而&#xff0c;如何编写一份既简洁而易读&#xff0c;又充满深度和洞察的摘要呢&#xff1f;这篇文章将提供一些基…

Scala特证/特质【6.7 特质(Trait)】

Scala特证/特质【6.7 特质&#xff08;Trait&#xff09;】 6.7 特质&#xff08;Trait&#xff09;Java 的接口接口的作用抽象类的作用 6.7.1 特质声明6.7.2 特质基本语法6.7.3 特质叠加6.7.4 特质叠加执行顺序6.7.5 特质自身类型6.7.6 特质和抽象类的区别 &#xff08;任意内…