拜托可不可以先点个赞证明你看过
每篇文章都是我学习的过程,可能有些思路看起来乱乱的,但这都是我珍贵的思绪留下的痕迹,随手帮我点个赞,加个关注吧 如果有用的话。要不然就找不到我啦。或者有什么需要讨论的,给我评论留言,让我觉得不孤独。谢谢大家。
上篇关于授权请求的文章我们分析了 用户在没有登录的情况下,访问 /oauth2/authorize端点,会引导用户去登录:
因为
/oauth/authorize
的目的是"让某个用户同意授权"------如果连用户是谁都不知道(即未登录),就无法确定"谁在授权",也就无法完成授权流程。
换句话说:
🔐 授权(Authorization)的前提是先知道"主体是谁"------也就是用户必须先认证(Authentication)。
OAuth2AuthorizationCodeRequestAuthenticationProvider
OAuth2AuthorizationCodeRequestAuthenticationProvider是专门用来处理授权请求的认证提供者
java
@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
ini
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService
.findById(registeredClient.getId(), principal.getName());
if (currentAuthorizationConsent != null) {
authenticationContextBuilder.authorizationConsent(currentAuthorizationConsent);
}
1 OAuth2AuthorizationConsentService
是什么?
- OAuth2AuthorizationConsentService是一个服务,负责管理用户的"授权同意"记录。
- 它的职责是:持久化地保存"用户是否同意过某个客户端的某些权限" 。
它的接口定义规范如下:对OAuth2AuthorizationConsent的增删改查功能
java
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);
}
java
.findById(registeredClient.getId(), principal.getName())
所以它查的是:
"用户 A 是否曾经同意过客户端 B 的授权请求?"
2. OAuth2AuthorizationConsent
是什么?
这是一个对象,表示 用户对某个客户端的授权同意记录。
arduino
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
中
接下来是这段代码:判断是否需要弹出授权确认页。
kotlin
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);
}
authorizationConsentRequired方法是干嘛的啊?
scss
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 授权流程中:
🔍 用户是否需要看到"授权确认页面"(Consent Page)------也就是那个经典的 "Do you allow XXX to access your data?" 的弹窗。
作用:判断当前授权请求是否需要用户手动确认授权(即是否要显示"同意授权"页面)。
- 返回
true
→ 需要用户确认(显示 Consent 页面) - 返回
false
→ 不需要用户确认(自动放行,直接生成code
)
✅ 条件 1:客户端是否配置为"不需要授权确认"
检查客户端是否在注册时明确设置为 "不需要用户确认" 。
✅ 条件 2:是否只请求了 openid
scope(纯 OIDC 登录)
判断:
- 请求的 scopes 中是否包含
openid
- 并且 只有
openid
这一个 scope
如果是,则不需要用户确认。
📌 为什么?
因为 openid
scope 的含义是:
"使用 OAuth2 做用户登录(身份认证),不访问任何用户数据。"
这本质上是 OpenID Connect(OIDC)的"登录"行为,而不是"授权访问资源"。
✅ 条件 3:用户是否已经同意过这些权限(Consent 已存在)
🔍 做了什么?
判断:
- 用户是否曾经同意过这个客户端的授权
- 并且 历史同意的权限范围(scopes)包含了本次请求的所有 scopes
如果是,则不需要再次确认。
在需要弹出授权确认页的情况下
kotlin
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
,而是 授权服务器内部生成的"同意页状态" 。 -
目的:
- 🔐 防止 CSRF 攻击 :当用户提交"同意"表单时,必须携带这个
state
,服务器会验证它是否匹配。
- 🔐 防止 CSRF 攻击 :当用户提交"同意"表单时,必须携带这个
🔹 2. 构建 OAuth2Authorization
对象
-
这是一个 待确认的授权上下文对象,但它还不是最终的授权记录。
-
它包含了:
- 客户端信息(
registeredClient
) - 当前用户(
principal
) - 原始授权请求参数(
authorizationRequest
) - 刚生成的
state
- 客户端信息(
-
注意:此时
authorization
的状态是"未完成",还没有authorization_code
。
🔹 3. 保存授权上下文
🔹 4. 获取用户之前已经授权过的 scope(如果有),用于在同意页上显示"已授权权限"。
🔹 5. 返回 OAuth2AuthorizationConsentAuthenticationToken
-
这个
AuthenticationToken
是一个信号,告诉 Spring Security:"现在需要跳转到'授权确认页',让用户决定是否同意!"
而且返回的是一个经过认证的OAuth2AuthorizationCodeRequestAuthenticationToken。
less
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方法中
kotlin
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
进行跳转
ini
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://localhost:9000/oauth2/authorize
请求参数:client_id=oidc-client&state=WJwb_Ex16hovx1oLhwyu2RoUukAF0drYtTUwluq1vdw%3D&scope=profile
总结:
本篇又花时间讨论了: 如果在用户登录的情况下,访问/oauth2/authorize
请求授权的时候,如果需要授权确认页,就弹出确认页。点击Post提交的的时候才会换取code
下一篇文章就会讨论post 提交授权请求的流程,记得关注啊。另外本篇文章的源码中,有很多很好的设计模式,比如builder设计模式,我希望后期我能有时间整理下spring框架中,使用的优雅的设计模式,如果感兴趣的话,可以关注哈,谢谢大家。