Pig4Cloud之登陆验证(二)发放token

news2024/9/9 6:02:57

上一篇介绍了客户端认证处理,那是令牌颁发的前提。这篇开始,我们就来研究下令牌颁发。

令牌颁发

授权服务器提供令牌颁发接口(/oauth2/token),由客户端发起请求,授权服务器生成访问令牌(access_token)返回,客户端使用此令牌才能去调用资源服务器的接口。

Spring Authorization Server 目前支持如下三种令牌颁发策略:Authorization CodeClient CredentialsRefresh Token,分别对应 授权码模式、客户端凭证模式、刷新令牌模式。

Authorization Code(授权码模式)

客户端访问授权链接,用户授权,客户端获得授权码code,通过code获取令牌

  • 传参

    • grant_type:固定值 authorization_code
    • code:授权码
  • 核心类

    • OAuth2AuthorizationCodeAuthenticationConverter
    • OAuth2AuthorizationCodeAuthenticationProvider

Client Credentials(客户端凭证模式)

  • 传参
    • grant_type:固定值 client_credentials
  • 核心类
    • OAuth2ClientCredentialsAuthenticationConverter
    • OAuth2ClientCredentialsAuthenticationProvider

Refresh Token(刷新令牌模式)

当客户端支持刷新令牌时,授权服务器颁发访问令牌(access_token)时会同时颁发刷新令牌(refresh_token),客户端可以使用刷新令牌重新获取访问令牌。(由于访问令牌时效比较短,刷新令牌时效比较长,通过刷新令牌获取访问令牌,避免多次授权)

  • 传参
    • grant_type:固定值 refresh_token
    • refresh_token:刷新令牌
  • 核心类
    • OAuth2RefreshTokenAuthenticationConverter
    • OAuth2RefreshTokenAuthenticationProvider

OAuth2TokenEndpointFilter

实现令牌颁发功能的拦截器就是 OAuth2TokenEndpointFilterOAuth2TokenEndpointFilter 会接收通过上文 OAuth2ClientAuthenticationFilter 客户端认证的请求,其核心代码如下:

@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
        //step1
		if (!this.tokenEndpointMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}

		try {
			String[] grantTypes = request.getParameterValues(OAuth2ParameterNames.GRANT_TYPE);
			if (grantTypes == null || grantTypes.length != 1) {
				throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.GRANT_TYPE);
			}
        //step2
			Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request);
			if (authorizationGrantAuthentication == null) {
				throwError(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, OAuth2ParameterNames.GRANT_TYPE);
			}
			if (authorizationGrantAuthentication instanceof AbstractAuthenticationToken) {
				((AbstractAuthenticationToken) authorizationGrantAuthentication)
						.setDetails(this.authenticationDetailsSource.buildDetails(request));
			}
        //step3
			OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
					(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);
        //step4
			this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, accessTokenAuthentication);
		} catch (OAuth2AuthenticationException ex) {
			SecurityContextHolder.clearContext();
			this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
		}
	}

step1.判断此次请求是否是 “令牌颁发” 请求,若是,则继续授权模式检验,否则跳过
step2.解析请求中的参数,构建成一个 Authentication(组装登陆认证对象)
step3.认证管理器对 Authentication 进行认证
step4.到这一步说明access_token生成好了, 将access_token和相关信息响应给请求方。

客户端认证 OAuth2ClientAuthenticationFilter 中也正是用的这种实现套路。将不同实现策略抽象为 AuthenticationConverterAuthenticationProvider 接口。每种策略实际上就是一个 AuthenticationConverter 实现类 加上一个 AuthenticationProvider实现类。

组装认证对象

Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request);

AuthenticationConverter 会根据请求中的参数和授权类型组装成对应的授权认证对象。
image

授权认证调用

OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
					(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);

image
OAuth2ResourceOwnerBaseAuthenticationProvider
image

认证逻辑

	Authentication usernamePasswordAuthentication = authenticationManager
					.authenticate(usernamePasswordAuthenticationToken);

根据认证方法传入的参数判定进入到AbstractUserDetailsAuthenticationProvider

@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));
		String username = determineUsername(authentication);
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
				this.logger.debug("Failed to find user '" + username + "'");
				if (!this.hideUserNotFoundExceptions) {
					throw ex;
				}
				throw new BadCredentialsException(this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
			Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
		}
		try {
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException ex) {
			if (!cacheWasUsed) {
				throw ex;
			}
			// There was a problem, so try again after checking
			// we're using latest data (i.e. not from the cache)
			cacheWasUsed = false;
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		this.postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (this.forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

查询用户信息
image

retrieveUser方法

protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException;

PigDaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider并重写
retrieveUser方法。返回值为UserDetails。
image

用户密码校验
image
PigDaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider并重写
additionalAuthenticationChecks方法。

@Override
	@SuppressWarnings("deprecation")
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {

		// app 模式不用校验密码
		String grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE);
		if (StrUtil.equals(SecurityConstants.APP, grantType)) {
			return;
		}

		if (authentication.getCredentials() == null) {
			this.logger.debug("Failed to authenticate since no credentials provided");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
		String presentedPassword = authentication.getCredentials().toString();
		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			this.logger.debug("Failed to authenticate since password does not match stored value");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

构建token

认证成功后返回OAuth2ResourceOwnerBaseAuthenticationProvider接着看
image
CustomeOAuth2AccessTokenGenerator实现OAuth2TokenGenerator接口.

Token 存储持久化

image

image

RedisOAuth2AuthorizationService实现OAuth2AuthorizationService
save方法

@Override
	public void save(OAuth2Authorization authorization) {
		Assert.notNull(authorization, "authorization cannot be null");

		if (isState(authorization)) {
			String token = authorization.getAttribute("state");
			redisTemplate.setValueSerializer(RedisSerializer.java());
			redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT,
					TimeUnit.MINUTES);
		}

		if (isCode(authorization)) {
			OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
					.getToken(OAuth2AuthorizationCode.class);
			OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
			long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),
					authorizationCodeToken.getExpiresAt());
			redisTemplate.setValueSerializer(RedisSerializer.java());
			redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()),
					authorization, between, TimeUnit.MINUTES);
		}

		if (isRefreshToken(authorization)) {
			OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
			long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
			redisTemplate.setValueSerializer(RedisSerializer.java());
			redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()),
					authorization, between, TimeUnit.SECONDS);
		}

		if (isAccessToken(authorization)) {
			OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
			long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
			redisTemplate.setValueSerializer(RedisSerializer.java());
			redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()),
					authorization, between, TimeUnit.SECONDS);
		}
	}

onAuthenticationSuccess

image
PigAuthenticationSuccessEventHandler实现AuthenticationSuccessHandler

@SneakyThrows
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) {
		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
		Map<String, Object> map = accessTokenAuthentication.getAdditionalParameters();
		if (MapUtil.isNotEmpty(map)) {
			// 发送异步日志事件
			PigUser userInfo = (PigUser) map.get(SecurityConstants.DETAILS_USER);
			log.info("用户:{} 登录成功", userInfo.getName());
			SecurityContextHolder.getContext().setAuthentication(accessTokenAuthentication);
			SysLog logVo = SysLogUtils.getSysLog();
			logVo.setTitle("登录成功");
			String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME);
			if (StrUtil.isNotBlank(startTimeStr)) {
				Long startTime = Long.parseLong(startTimeStr);
				Long endTime = System.currentTimeMillis();
				logVo.setTime(endTime - startTime);
			}
			logVo.setCreateBy(userInfo.getName());
			logVo.setUpdateBy(userInfo.getName());
			SpringContextHolder.publishEvent(new SysLogEvent(logVo));
		}

		// 输出token
		sendAccessTokenResponse(request, response, authentication);
	}

输出token

private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException {

		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;

		OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
		OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
		Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();

		OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
				.tokenType(accessToken.getTokenType()).scopes(accessToken.getScopes());
		if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
			builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
		}
		if (refreshToken != null) {
			builder.refreshToken(refreshToken.getTokenValue());
		}
		if (!CollectionUtils.isEmpty(additionalParameters)) {
			builder.additionalParameters(additionalParameters);
		}
		OAuth2AccessTokenResponse accessTokenResponse = builder.build();
		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);

		// 无状态 注意删除 context 上下文的信息
		SecurityContextHolder.clearContext();
		this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
	}

博客园
腾讯云
掘金
简书

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

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

相关文章

常见的并发线程面试题

常见的并发面试题 一.进程与线程的区别? 进程是操作系统进行资源分配的最小单元&#xff0c;线程是操作系统进行运算调度的最小单元。进程中包含了线程&#xff0c;线程属于进程。进程的内存和资源是该进程下的线程所共享的。 二.创建线程的方式以及区别? 继承Thread类&am…

vue源码中的nextTick是怎样实现的

一、Vue.nextTick 内部逻辑 在执行 initGlobalAPI(Vue) 初始化 Vue 全局 API 中&#xff0c;这么定义 Vue.nextTick。 function initGlobalAPI(Vue) {//...Vue.nextTick nextTick; }可以看出是直接把 nextTick 函数赋值给 Vue.nextTick&#xff0c;就可以了&#xff0c;非常…

leetcode-每日一题-二进制表示中质数个计算置位(简单,popcount算法)

从这道题了解到了一个时间复杂度为o(1)的一个计算一个数转换为二进制时1存在的个数问题&#xff0c;很巧妙运用了二分来求解&#xff0c;代码如下 unsigned popcount (unsigned u) {u (u & 0x55555555) ((u >> 1) & 0x55555555);u (u & 0x33333333) ((u…

API接口使用方法(封装好的电商平台)

为了进行此平台API的调用&#xff0c;首先我们需要做下面几件事情。 1、 获取一个KEY。 点击获取 2、 参考API文档里的接入方式和示例。 3、查看测试工具是否有需要的接口&#xff0c;响应实例的返回字段是否符合参数要求。 4、利用平台的文档中心和API测试工具&#xff0c…

自定义网页中被选中文本的样式 CSS selection

文章目录两张图自定义被选中文本的样式::selection 选择器的可填属性::selection 选择器支持的CSS属性 完整版 demo浏览器兼容官网文档两张图 对于选中的文本(准确地说应该是被选中的DOM元素)&#xff0c;浏览器的默认样式&#xff1a;淡青色的背景色。 2. 自定义样式&#xf…

[说明] Doris使用培训

参考文献 apache doris在蜀海供应链数仓建设中的实践 应用实践 | 数仓体系效率全面提升&#xff01;同程数科基于 Apache Doris 的数据仓库建设 一、doris的背景介绍 doris的使用场景 实时/离线一体的数仓 借用一句话&#xff1a; Uniq 模型拳打KUDU、HUDI&#xff0c;Agg…

[LeetCode 1775]通过最少操作数使数组的和相等

题目描述 题目链接&#xff1a;[LeetCode 1775]通过最少操作数使数组的和相等 给你两个长度可能不等的整数数组 nums1 和 nums2 。两个数组中的所有值都在 1 到 6 之间&#xff08;包含 1 和 6&#xff09;。 每次操作中&#xff0c;你可以选择 任意 数组中的任意一个整数&a…

openEuler kubesphere kubekey 安装 ceph csi 及使用实例

按照官方文档,很轻松就可以进行安装, 安装过程中遇到一些疑问, 在安装后也得到清晰的理解 1. /root/ceph-csi-rbd.yaml 这里面的clusterId 按理说应该是ceph的clusterId, 不过官方文档并没有首重强调修改,暂时保持默认 下面6789端口就是我们在物理机上安装的ceph mon的主机i…

PCB设计指南:安规、布局布线、EMC、热设计、工艺

文章目录Part 1 安规距离要求部分一、爬电距离和电气间隙距离要求&#xff1a;Part 2 抗干扰、EMC部分一、长线路抗干扰二、小信号走线尽量远离大电流走线&#xff0c;忌平行&#xff0c;D>2.0mm。三、小信号线处理&#xff1a;电路板布线尽量集中&#xff0c;减少布板面积提…

广州蓝景分享—实用的CSS技巧,助你成为更好的开发者

Hello~~各位小伙伴&#xff0c;相信在前端开发项目中&#xff0c;CSS实现如修改输入占位符样式&#xff0c;多行文本溢出&#xff0c;隐藏滚动条&#xff0c;修改光标颜色&#xff0c;水平和垂直居中等等&#xff0c;这些都是我们非常熟悉的开发场景&#xff01;前端开发者几乎…

HashMap部分源码解析

作者&#xff1a;~小明学编程 文章专栏&#xff1a;Java数据结构 格言&#xff1a;目之所及皆为回忆&#xff0c;心之所想皆为过往 目录 前言 常量字段 构造方法 put方法 确定初始容量 为何我们的数组的大小要是2的n次幂 hash为何要异或其高位 扩容机制 前言 我们在前…

极客时间Kafka - 04 Kafka生产者和消费者拦截器

文章目录1. 什么是拦截器&#xff1f;2. Kafka 拦截器3. 典型使用场景4. 案例分享1. 什么是拦截器&#xff1f; 如果你用过 Spring Interceptor 或是 Apache Flume&#xff0c;那么应该不会对拦截器这个概念感到陌生&#xff0c;其基本思想就是允许应用程序在不修改逻辑的情况…

无人机边缘计算中的计算卸载——Stackelberg博弈方法论文复现附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

不会还有人不会热修复吧?

Class流派原理 基本原理:加载类的时候是找element&#xff0c;每个element对于一个dex。我要把我修复的那个类单独放到dex插入dexlist前面&#xff0c;在你做类加载从前往后找优先从你的dex加载加载的就是你修复后的class.这就是 实现代码 通过context拿到pathClassLoader&am…

Qt跨平台截图工具

Qt跨平台截图工具 文章目录Qt跨平台截图工具1、概述2、实现效果3、软件构成4、关键代码5、源代码更多精彩内容&#x1f449;个人内容分类汇总 &#x1f448;&#x1f449;Qt自定义模块、工具&#x1f448; 1、概述 Qt版本&#xff1a;V5.12.5兼容系统&#xff1a; Windows&…

2022,记录与华为的这场会议

一、数据治理团体标准发布会 11月26日&#xff0c;中国计算机用户协会信息科技审计分会联合华为与擎创科技共同举办了“金融行业运维数据治理团体标准应用研讨暨2022年度调研报告线上发布会”。来自国家开发银行、中国建设银行、中国邮政储蓄银行、招商银行、兴业银行、中信银行…

【LeetCode_字符串_逻辑分析】9. 回文数

目录考察点第一次&#xff1a;2022年12月7日10:16:33解题思路代码展示题目描述给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左…

340页11万字智慧政务大数据资源平台大数据底座数据治理建设方案

目 录 第一章 项目概况 1.1 项目名称 1.2 项目单位 1.3 项目建设依据 1.4 项目建设内容和目标 1.4.1 建设内容 1.4.2 建设目标 1.5 项目投资估算及建设周期 1.5.1 项目投资估算 1.5.2 服务周期 第二章 现状 2.1 项目单位概况 2.1.1 单位职责、内设及下属机构、人员…

【配准图像】

MU-Net: A MULTISCALE UNSUPERVISED NETWORK FOR REMOTE SENSING IMAGE REGISTRATION &#xff08;MU-Net&#xff1a;一种多尺度无监督遥感图像配准网络&#xff09; 多传感器或多模态图像对的配准是许多遥感应用的基础性任务。为了实现高精度、低成本的遥感图像配准&#x…

彻底搞懂JS原型与原型链

说到JavaScript的原型和原型链&#xff0c;相关文章已有不少&#xff0c;但是大都晦涩难懂。本文将换一个角度出发&#xff0c;先理解原型和原型链是什么&#xff0c;有什么作用&#xff0c;再去分析那些令人头疼的关系。 一、引用类型皆为对象 原型和原型链都是来源于对象而…