当前位置: 首页 > article >正文

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

文章目录

    • 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.kler.cn/a/286501.html

相关文章:

  • 1.Template Method 模式
  • 【Unity3D】实现2D角色/怪物死亡消散粒子效果
  • 实时数据处理与模型推理:利用 Spring AI 实现对数据的推理与分析
  • cursor软件的chat和composer分别是什么
  • 登录授权流程
  • 数字化转型-工具变量(2024.1更新)-社科数据
  • gNB UE发送Timing AdvanceCommand
  • 新手如何学单片机
  • 续:MySQL的gtid模式
  • Nginx: TCP建立连接的优化和启用Fast Open功能
  • unicode编码存在转义字符,导致乱码问题的解决方案
  • 在gitignore忽略目录及该目录下的子文件
  • Guava Cache实现原理及最佳实践
  • 全国大学生数据建模比赛——深度学习
  • 网络工程师学习笔记——局域网和城域网
  • Linux之ip命令详解
  • 财富知识的认知(一)
  • 将单元格中的单引号隐藏,但是并不删除,用于从txt中复制到excel中直接将数字内容改为文本显示,刷新内容
  • kali——nikto的使用
  • VUE之Router命令行警告:Named Route ‘Home‘ has a default child route. 解决办法
  • HDMI显示器驱动设计与验证
  • NVIDIA Ada Lovelace 架构
  • [论文阅读]JTORO in NOMA-based VEC:A game-theoretic DRL approach
  • Java面试宝典-java基础01
  • 【原型模式】
  • 无人机遥控器工作原理!!!