SpringSecurity Oauth2 - 密码认证获取访问令牌源码分析

news2024/11/13 7:59:13

文章目录

    • 1. 授权服务器过滤器
      • 1. 常用的过滤器
      • 2. 工作原理
    • 2. 密码模式获取访问令牌
      • 1. 工作流程
      • 2. 用户凭证验证
        • 1. ResourceOwnerPasswordTokenGranter
        • 2. ProviderManager
        • 3. CustomAuthProvider
        • 4. 认证后的结果

1. 授权服务器过滤器

在Spring Security中,OAuth2授权服务器的过滤器是处理OAuth2授权流程的核心组件之一。它们在请求进入授权服务器时被应用,以确保请求的合法性,并执行授权和令牌处理。

1. 常用的过滤器

AuthorizationEndpointFilter:

  • 处理/oauth/authorize请求,它是OAuth2授权流程的核心过滤器。
  • 负责处理客户端请求授权码或访问令牌的过程。这个过滤器会检查请求的有效性、处理用户的认证信息、并将请求引导至授权页面(通常是一个登录页面或授权确认页面)。

TokenEndpointFilter:

  • 处理/oauth/token请求,这是获取访问令牌和刷新令牌的核心过滤器。
  • 负责处理各种授权方式的令牌颁发过程,包括授权码模式、密码模式、客户端凭据模式等。

ClientCredentialsTokenEndpointFilter:

  • 专门处理客户端凭据授权模式的令牌请求。
  • 负责验证客户端的身份并直接发放访问令牌,因为客户端凭据模式不涉及用户的授权确认。

CheckTokenEndpointFilter:

  • 处理令牌的检查请求,通常位于/oauth/check_token路径下。
  • 用于验证令牌的有效性,通常是资源服务器用来验证访问令牌是否有效的一个过滤器。

OAuth2LoginAuthenticationFilter:

  • 处理OAuth2登录流程,处理授权码登录或隐式授权流程中的登录请求。
  • 这个过滤器在接收到OAuth2的登录请求后,执行OAuth2的认证流程,并在认证成功后生成相应的OAuth2授权信息。

2. 工作原理

① 过滤器链:在Spring Security的配置中,这些过滤器通常被配置在一个过滤器链中,这样每个请求都会经过这些过滤器,并根据请求路径、请求参数和方法来决定该请求需要经过哪些过滤器的处理。

② 安全配置:Spring Security OAuth2的配置通过AuthorizationServerConfigurerAdapter类来进行。在这个类中,你可以配置上述的过滤器,并指定授权路径、令牌路径、客户端详细信息服务、以及各种授权方式的处理逻辑。

③ 令牌存储:这些过滤器通常会结合令牌存储(例如内存存储、数据库存储或JWT存储)一起使用,以管理访问令牌和刷新令牌的生成、存储、验证和撤销。

2. 密码模式获取访问令牌

1. 工作流程

在这里插入图片描述

① 验证客户端:首先,TokenEndpointFilter 会调用 ClientCredentialsTokenEndpointFilter 验证 client_idclient_secret,确保客户端的合法性。

② 用户凭证验证:如果客户端验证通过,ResourceOwnerPasswordTokenGranter 会验证 usernamepassword。如果用户凭证正确,则生成访问令牌。

③ 生成和返回令牌:如果所有验证通过,过滤器会生成访问令牌并返回给客户端。

这套流程确保了在密码模式下,只有正确的客户端和用户组合才能成功获取访问令牌。

2. 用户凭证验证

1. ResourceOwnerPasswordTokenGranter
public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {

	private static final String GRANT_TYPE = "password";

	private final AuthenticationManager authenticationManager;

	public ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager,
			AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
		this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
	}

	protected ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
			ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
		super(tokenServices, clientDetailsService, requestFactory, grantType);
		this.authenticationManager = authenticationManager;
	}

	@Override
	protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

		Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
		String username = parameters.get("username");
		String password = parameters.get("password");
		// Protect from downstream leaks of password
		parameters.remove("password");

		Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
		((AbstractAuthenticationToken) userAuth).setDetails(parameters);
		try {
			userAuth = authenticationManager.authenticate(userAuth);
		}
		catch (AccountStatusException ase) {
			//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
			throw new InvalidGrantException(ase.getMessage());
		}
		catch (BadCredentialsException e) {
			// If the username/password are wrong the spec says we should send 400/invalid grant
			throw new InvalidGrantException(e.getMessage());
		}
		if (userAuth == null || !userAuth.isAuthenticated()) {
			throw new InvalidGrantException("Could not authenticate user: " + username);
		}
		
		OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);		
		return new OAuth2Authentication(storedOAuth2Request, userAuth);
	}
}

在这里插入图片描述

2. ProviderManager
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
		InitializingBean {
            
	private static final Log logger = LogFactory.getLog(ProviderManager.class);
	private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
	private List<AuthenticationProvider> providers = Collections.emptyList();
	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private AuthenticationManager parent;
	private boolean eraseCredentialsAfterAuthentication = true;

	public ProviderManager(List<AuthenticationProvider> providers) {
		this(providers, null);
	}

	public ProviderManager(List<AuthenticationProvider> providers,
			AuthenticationManager parent) {
		Assert.notNull(providers, "providers list cannot be null");
		this.providers = providers;
		this.parent = parent;
		checkState();
	}

	public void afterPropertiesSet() {
		checkState();
	}

	private void checkState() {
		if (parent == null && providers.isEmpty()) {
			throw new IllegalArgumentException(
					"A parent AuthenticationManager or a list "
							+ "of AuthenticationProviders is required");
		}
	}

	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		boolean debug = logger.isDebugEnabled();

		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			} catch (AuthenticationException e) {
				lastException = e;
			}
		}

		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException e) {
				lastException = parentException = e;
			}
		}

		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}

			// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).

		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
	}

	@SuppressWarnings("deprecation")
	private void prepareException(AuthenticationException ex, Authentication auth) {
		eventPublisher.publishAuthenticationFailure(ex, auth);
	}

	/**
	 * Copies the authentication details from a source Authentication object to a
	 * destination one, provided the latter does not already have one set.
	 *
	 * @param source source authentication
	 * @param dest the destination authentication object
	 */
	private void copyDetails(Authentication source, Authentication dest) {
		if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
			AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;

			token.setDetails(source.getDetails());
		}
	}

	public List<AuthenticationProvider> getProviders() {
		return providers;
	}

	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	public void setAuthenticationEventPublisher(
			AuthenticationEventPublisher eventPublisher) {
		Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
		this.eventPublisher = eventPublisher;
	}

	public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
		this.eraseCredentialsAfterAuthentication = eraseSecretData;
	}

	public boolean isEraseCredentialsAfterAuthentication() {
		return eraseCredentialsAfterAuthentication;
	}

	private static final class NullEventPublisher implements AuthenticationEventPublisher {
		public void publishAuthenticationFailure(AuthenticationException exception,
				Authentication authentication) {
		}

		public void publishAuthenticationSuccess(Authentication authentication) {
		}
	}
}

在这里插入图片描述

3. CustomAuthProvider
@Component
public class CustomAuthProvider implements AuthenticationProvider {

    @Autowired
    private CustomUserDetailService userDetailService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();

        UserDetails userDetails = userDetailService.loadUserByUsername(username);

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                = new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
        usernamePasswordAuthenticationToken.setDetails(authentication.getDetails());
        return usernamePasswordAuthenticationToken;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

在这里插入图片描述

4. 认证后的结果

在这里插入图片描述

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

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

相关文章

ComfyUI上手使用记录

文章目录 资料安装基础概念常用的工具和插件放大图像从裁剪到重绘SDXL工作流搭建Clip的多种不同的应用Lcm-Turbo极速出图集成节点 资料 AI绘画之ComfyUI Stable Diffusion WEUI中的SDV1.5与SDXL模型结构Config对比 stable-diffusion-webui中stability的sdv1.5和sdxl模型结构c…

SPI驱动学习三(spidev的使用)

目录 一、 spidev驱动程序分析1. 驱动框架2. 驱动程序分析 二、SPI应用程序分析1. 使用方法2. 代码分析2.1 显示设备属性2.2 读数据2.3 先写再读2.4 同时读写 3. SPI应用编程详解4. spidev的缺点 一、 spidev驱动程序分析 参考资料&#xff1a; * 内核驱动&#xff1a;drivers…

足球大小球预测及足球大数据之机器学习预测大小球

足球运动是当今世界上开展最广、影响最大、最具魅力、拥有球迷数最多的体育项目之一&#xff0c;尤其是欧洲足球&#xff0c;每年赛事除了五大联赛&#xff08;英超、西甲、德甲、法甲、意甲&#xff09;之外&#xff0c;还会有欧冠&#xff08;欧洲冠军联赛&#xff09;&#…

Docker容器详细介绍

1.docker简介 1.1什么是Docker Docker是管理容器的引擎&#xff0c;为应用打包、部署平台&#xff0c;而非单纯的虚拟化技术 它具有以下几个重要特点和优势&#xff1a; 1. 轻量级虚拟化 Docker 容器相较于传统的虚拟机更加轻量和高效&#xff0c;能够快速启动和停止&#…

day-46 旋转图像

思路 不能使用辅助数组&#xff0c;所以关键在于弄清楚旋转后坐标的变化规律。当矩阵的大小n为偶数时&#xff0c;以n/2行和n/2列的元素为起点&#xff0c;当矩阵的大小n为奇数时&#xff0c;以n/2行和&#xff08;n1&#xff09;/2列的元素为起点 解题过程 关键&#xff1a;旋…

【python计算机视觉编程——照相机模型与增强现实】

python计算机视觉编程——照相机模型与增强现实 4.照相机模型与增强现实4.1 真空照相机模型4.1.1 照相机矩阵4.1.2 三维点的投影4.1.3 照相机矩阵的分解4.1.4 计算照相机中心 4.2 照相机标定4.3 以平面和标记物进行姿态估计sift.pyhomography.py主函数homography.pycamera.py主…

二分查找 | 二分模板 | 二分题目解析

1.二分查找 二分查找的一个前提就是要保证数组是有序的&#xff08;不准确&#xff09;&#xff01;利用二段性&#xff01; 1.朴素二分模板 朴素二分法的查找中间的值和目标值比较&#xff08;不能找范围&#xff09; while(left < right) // 注意是要&#xff1a; < …

华为云征文|基于Flexus云服务器X实例的应用场景-私有化部署自己的笔记平台

&#x1f534;大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂 先看这里 写在前面效果图华为云Flexus X实例云服务器Blossom 私有化笔记平台简介准备工作创建yaml文件执行yaml文件使用blossom 写在前面 我发现了个事儿&#xff0c;好多技术…

百望云携手春秋航空 迈入航空出行数电票新时代

在数字经济的大潮中&#xff0c;每一个行业的转型与升级都显得尤为关键&#xff0c;而航空业作为连接世界的桥梁&#xff0c;其数字化转型的步伐更是备受瞩目。随着百望云与春秋航空携手迈入航空出行数电票新时代&#xff0c;我们不仅见证了传统纸质票据向数字化转型的必然趋势…

Elastic Stack--ELFK实例与Dashboard界面

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 学习B站博主教程笔记&#xff1a; 最新版适合自学的ElasticStack全套视频&#xff08;Elk零基础入门到精通教程&#xff09;Linux运维必备—Elastic…

逆向工程核心原理 Chapter22 | 恶意键盘记录器

教程这一章没给具体的实现&#xff0c;这里在Chapter21学习的基础上&#xff0c;试着实现一个键盘记录器。 键盘记录器实现 这里有个技术问题&#xff1a;记录下的敲击键&#xff08;在KeyHook.dll中捕获的&#xff09;&#xff08;可以用wParam&#xff09;怎么打印出来&…

二叉树和堆知识点

1 特殊二叉树 1. 满二叉树&#xff1a;一个二叉树&#xff0c;如果每一个层的结点数都达到最大值&#xff0c;则这个二叉树就是满二叉树。也就是 说&#xff0c;如果一个二叉树的层数为K&#xff0c;且结点总数是 &#xff0c;则它就是满二叉树。 2. 完全二叉树&#xff1a;完全…

前端打包部署,Nginx服务器启动

前端vue打包部署 前端vue打包部署&#xff0c;执行NPM脚本下的build vue-cli-service... 生成dist文件夹 Nginx服务器 将刚刚的静态资源部署到Nginx

小白学装修(准备阶段)

装修还是 实事求是 脚踏实地 多用心 多学习 视频&#xff1a; 你离摆脱装修小白身份&#xff0c;只差这一个视频&#xff01;_哔哩哔哩_bilibili 本篇文章所涉及到的文件&#xff08;记得给诡计从不拖更一件三联&#xff09; 给诡计投币换的装修预算表资源-CSDN文库 住户…

【Python报错已解决】“ValueError: If using all scalar values, you must pass an index“

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言&#xff1a;一、问题描述1.1 报错示例&#xff1a;以下是一个可能引发上述错误的代码示例。1.2 报错分析&#x…

Docker 镜像构建

1、Docker 镜像结构 Docker镜像的结构是分层的&#xff0c;这种结构是Docker镜像轻量化和高效性的关键。每个Docker镜像都由一系列的“镜像层”&#xff08;image layers&#xff09;组成&#xff0c;这些层通过UnionFS&#xff08;联合文件系统&#xff09;技术叠加在一起&am…

磐石云语音识别引擎

磐石云发布了V1.2.2版本语音识别引擎。 经过严格客观的测试识别效果和阿里云、讯飞、火山进行了对比几乎无差。&#xff08;欢迎对比测试&#xff09; 上图是CPU下的流式识别效果 RTF0.1~0.14,也就是一并发一个小时大约处理7~10小时&#xff0c;这取决于硬件的配置&#xff0…

基于SpringBoot的教务与课程管理系统

&#x1f4a5;&#x1f4a5;源码和论文下载&#x1f4a5;&#x1f4a5;&#xff1a;基于SpringBoot的教务与课程管理系统源码论文报告数据库.rar 1. 系统介绍 随着计算机科学技术的迅猛进步及高等教育体系改革的持续深化&#xff0c;传统的教育管理方式、工具及其操作效率已经难…

APP测试(十一)

APP测试要点提取与分析 一、功能测试 APP是什么项目&#xff1f;核心业务功能梳理清楚 — 流程图分析APP客户端的单个功能模块 — 细化分析 需要使用等价类&#xff0c;边界值&#xff0c;考虑正常和异常情况&#xff08;长度&#xff0c;数据类型&#xff0c;必填&#xff0…

JavaFX基本控件-Label

JavaFX基本控件-Label 常用属性textpaddingalignmenttextAlignmentwidthheighttooltipborderwrapTextellipsisStringunderline 实现方式Java实现fxml实现 常用属性 text 设置文本内容 label.setText("这是一个测试数据");padding 内边距 label.setPadding(new Inset…