纳瓦尔艺术免安装绿色中文版
2.82G · 2025-10-24
上篇关于授权请求的文章我们分析了 用户在没有登录的情况下,访问 /oauth2/authorize端点,会引导用户去登录:
换句话说:
OAuth2AuthorizationCodeRequestAuthenticationProvider是专门用来处理授权请求的认证提供者
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = (OAuth2AuthorizationCodeRequestAuthenticationToken) authentication;
RegisteredClient registeredClient = this.registeredClientRepository
.findByClientId(authorizationCodeRequestAuthentication.getClientId());
if (registeredClient == null) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID,
authorizationCodeRequestAuthentication, null);
}
...
Authentication principal = (Authentication) authorizationCodeRequestAuthentication.getPrincipal();
if (!isPrincipalAuthenticated(principal)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate authorization code request since principal not authenticated");
}
// Return the authorization request as-is where isAuthenticated() is false
return authorizationCodeRequestAuthentication;
}
}
OAuth2AuthorizationCodeRequestAuthenticationProvider在对authorizationCodeRequestAuthentication授权请求进行认证的时候,会通过isPrincipalAuthenticated判断用户是否已经登录
通过调试发现未登录的时候是匿名token,认证之后就是usernamepasswordAuthenticationToken,并且是已经认证的。也就是说认证之后这步校验就可以通过了。
接下来:这里有个authorizationConsentService,通过这个service去查询OAuth2AuthorizationConsent
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService
.findById(registeredClient.getId(), principal.getName());
if (currentAuthorizationConsent != null) {
authenticationContextBuilder.authorizationConsent(currentAuthorizationConsent);
}
OAuth2AuthorizationConsentService是什么?它的接口定义规范如下:对OAuth2AuthorizationConsent的增删改查功能
public interface OAuth2AuthorizationConsentService {
/**
* Saves the {@link OAuth2AuthorizationConsent}.
* @param authorizationConsent the {@link OAuth2AuthorizationConsent}
*/
void save(OAuth2AuthorizationConsent authorizationConsent);
/**
* Removes the {@link OAuth2AuthorizationConsent}.
* @param authorizationConsent the {@link OAuth2AuthorizationConsent}
*/
void remove(OAuth2AuthorizationConsent authorizationConsent);
/**
* Returns the {@link OAuth2AuthorizationConsent} identified by the provided
* {@code registeredClientId} and {@code principalName}, or {@code null} if not found.
* @param registeredClientId the identifier for the {@link RegisteredClient}
* @param principalName the name of the {@link Principal}
* @return the {@link OAuth2AuthorizationConsent} if found, otherwise {@code null}
*/
@Nullable
OAuth2AuthorizationConsent findById(String registeredClientId, String principalName);
}
.findById(registeredClient.getId(), principal.getName())
所以它查的是:
OAuth2AuthorizationConsent 是什么?这是一个对象,表示 用户对某个客户端的授权同意记录。
public final class OAuth2AuthorizationConsent implements Serializable {
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private static final String AUTHORITIES_SCOPE_PREFIX = "SCOPE_";
private final String registeredClientId;
private final String principalName;
private final Set<GrantedAuthority> authorities;
}
| 字段 | 说明 |
|---|---|
registeredClientId |
哪个客户端 |
principalName |
哪个用户 |
authorities |
用户曾经同意过的权限列表(如 read, write, profile) |
综上所述就是查询用户曾经是否同意过对某个client的授权。将其设置进authenticationContextBuilder中
接下来是这段代码:判断是否需要弹出授权确认页。
if (this.authorizationConsentRequired.test(authenticationContextBuilder.build())) {
String state = DEFAULT_STATE_GENERATOR.generateKey();
OAuth2Authorization authorization = authorizationBuilder(registeredClient, principal, authorizationRequest)
.attribute(OAuth2ParameterNames.STATE, state)
.build();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated authorization consent state");
}
this.authorizationService.save(authorization);
Set<String> currentAuthorizedScopes = (currentAuthorizationConsent != null)
? currentAuthorizationConsent.getScopes() : null;
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization");
}
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(),
registeredClient.getClientId(), principal, state, currentAuthorizedScopes, null);
}
private static boolean isAuthorizationConsentRequired(
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
if (!authenticationContext.getRegisteredClient().getClientSettings().isRequireAuthorizationConsent()) {
return false;
}
// 'openid' scope does not require consent
if (authenticationContext.getAuthorizationRequest().getScopes().contains(OidcScopes.OPENID)
&& authenticationContext.getAuthorizationRequest().getScopes().size() == 1) {
return false;
}
if (authenticationContext.getAuthorizationConsent() != null && authenticationContext.getAuthorizationConsent()
.getScopes()
.containsAll(authenticationContext.getAuthorizationRequest().getScopes())) {
return false;
}
return true;
}
这是一个非常核心且关键的方法,它决定了在 OAuth2 授权流程中:
true → 需要用户确认(显示 Consent 页面)false → 不需要用户确认(自动放行,直接生成 code)检查客户端是否在注册时明确设置为 “不需要用户确认” 。
openid scope(纯 OIDC 登录)判断:
openid
openid 这一个 scope
如果是,则不需要用户确认。
? 为什么?
因为 openid scope 的含义是:
这本质上是 OpenID Connect(OIDC)的“登录”行为,而不是“授权访问资源”。
? 做了什么?
判断:
如果是,则不需要再次确认。
if (this.authorizationConsentRequired.test(authenticationContextBuilder.build())) {
String state = DEFAULT_STATE_GENERATOR.generateKey();
OAuth2Authorization authorization = authorizationBuilder(registeredClient, principal, authorizationRequest)
.attribute(OAuth2ParameterNames.STATE, state)
.build();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated authorization consent state");
}
this.authorizationService.save(authorization);
Set<String> currentAuthorizedScopes = (currentAuthorizationConsent != null)
? currentAuthorizationConsent.getScopes() : null;
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization");
}
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(),
registeredClient.getClientId(), principal, state, currentAuthorizedScopes, null);
}
如果要弹出确认页,框架做了如上操作
? 1. 生成 state
这个 state 不是 OAuth 2.0 中客户端传来的 state,而是 授权服务器内部生成的“同意页状态” 。
目的:
state,服务器会验证它是否匹配。? 2. 构建 OAuth2Authorization 对象
这是一个 待确认的授权上下文对象,但它还不是最终的授权记录。
它包含了:
registeredClient)principal)authorizationRequest)state
注意:此时 authorization 的状态是“未完成”,还没有 authorization_code。
? 3. 保存授权上下文
? 4. 获取用户之前已经授权过的 scope(如果有),用于在同意页上显示“已授权权限”。
? 5. 返回 OAuth2AuthorizationConsentAuthenticationToken
这个 AuthenticationToken 是一个信号,告诉 Spring Security:
而且返回的是一个经过认证的OAuth2AuthorizationCodeRequestAuthenticationToken。
public OAuth2AuthorizationCodeRequestAuthenticationToken(String authorizationUri, String clientId,
Authentication principal, OAuth2AuthorizationCode authorizationCode, @Nullable String redirectUri,
@Nullable String state, @Nullable Set<String> scopes) {
super(Collections.emptyList());
Assert.hasText(authorizationUri, "authorizationUri cannot be empty");
Assert.hasText(clientId, "clientId cannot be empty");
Assert.notNull(principal, "principal cannot be null");
Assert.notNull(authorizationCode, "authorizationCode cannot be null");
this.authorizationUri = authorizationUri;
this.clientId = clientId;
this.principal = principal;
this.authorizationCode = authorizationCode;
this.redirectUri = redirectUri;
this.state = state;
this.scopes = Collections.unmodifiableSet((scopes != null) ? new HashSet<>(scopes) : Collections.emptySet());
this.additionalParameters = Collections.emptyMap();
setAuthenticated(true);
}
上面的代码返回以后,代码又回到了OAuth2AuthorizationEndpointFilter的authentication方法中
Authentication authenticationResult = this.authenticationManager.authenticate(authentication);
// 代码回到这里
if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authorization consent is required");
}
sendAuthorizationConsent(request, response,
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication,
(OAuth2AuthorizationConsentAuthenticationToken) authenticationResult);
return;
}
判断如果返回的是OAuth2AuthorizationCodeRequestAuthenticationToken(这也是一个认证通过的令牌),就调用sendAuthorizationConsent进行跳转
private void sendAuthorizationConsent(HttpServletRequest request, HttpServletResponse response,
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication) throws IOException {
String clientId = authorizationConsentAuthentication.getClientId();
Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal();
Set<String> requestedScopes = authorizationCodeRequestAuthentication.getScopes();
Set<String> authorizedScopes = authorizationConsentAuthentication.getScopes();
String state = authorizationConsentAuthentication.getState();
if (hasConsentUri()) {
String redirectUri = UriComponentsBuilder.fromUriString(resolveConsentUri(request))
.queryParam(OAuth2ParameterNames.SCOPE, String.join(" ", requestedScopes))
.queryParam(OAuth2ParameterNames.CLIENT_ID, clientId)
.queryParam(OAuth2ParameterNames.STATE, state)
.toUriString();
this.redirectStrategy.sendRedirect(request, response, redirectUri);
}
else {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Displaying generated consent screen");
}
DefaultConsentPage.displayConsent(request, response, clientId, principal, requestedScopes, authorizedScopes,
state, Collections.emptyMap());
}
}
private boolean hasConsentUri() {
return StringUtils.hasText(this.consentPage);
}
但是我们没有配置consentPage属性,所以会生成一个默认的授权确认页面,
从默认页面我们可以看出 如果用户点击同意授权,那么就还会再oauth2/authorize的url提交表单post请求用来表示同意授权。
点击确认:提交post请求 http://lo**ca*lhost:9000/oauth2/authorize
请求参数:client_id=oidc-client&state=WJwb_Ex16hovx1oLhwyu2RoUukAF0drYtTUwluq1vdw%3D&scope=profile
本篇又花时间讨论了: 如果在用户登录的情况下,访问/oauth2/authorize请求授权的时候,如果需要授权确认页,就弹出确认页。点击Post提交的的时候才会换取code
下一篇文章就会讨论post 提交授权请求的流程,记得关注啊。另外本篇文章的源码中,有很多很好的设计模式,比如builder设计模式,我希望后期我能有时间整理下spring框架中,使用的优雅的设计模式,如果感兴趣的话,可以关注哈,谢谢大家。
2025-10-24
网易《逆水寒》手游与宇树科技达成合作,虚拟世界将成机器人技术试验田
2025-10-24
谷歌间接承认 Tensor G5 芯片存在 GPU 问题,将推送更新优化 Pixel 10 系列手机