前文我们已经简单的介绍了如何搭建授权服务器,下面将继续介绍如何自定义OAuth2授权同意页面。
如果你已经无法容忍Spring Authorization Server 默认丑陋的授权同意页面,那么你可以继续阅读本文,逐步创建一个令自己满意的授权同意页面。
OAuth2授权服务器实现从创建一个授权服务器开始。
maven依赖配置org.springframework.boot spring-boot-starter-security 2.6.7 org.springframework.security spring-security-oauth2-authorization-server 0.3.1 org.springframework.boot spring-boot-starter-web 2.6.7 org.springframework.boot spring-boot-starter-thymeleaf 2.6.7
首先我们为授权服务器配置端口8080:
server: port: 8080
之后我们创建一个AuthorizationServerConfig配置类,在此类中我们将创建OAuth2授权服务器所需特定的Bean。首先指定我们授权同意页面/oauth2/consent uri替换原有默认实现。
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>();
//定义授权同意页面
authorizationServerConfigurer.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI));
RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
http.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.apply(authorizationServerConfigurer);
return http.exceptionHandling(exceptions -> exceptions.
authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))).build();
}
//...
}
接下来我们使用RegisteredClient构建器类型创建一个OAuth2客户端,并将它存储在缓存中。
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("relive-client")
.clientSecret("{noop}relive-client")
.clientName("ReLive27")
.clientAuthenticationMethods(s -> {
s.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
s.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
})
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.redirectUri("http://127.0.0.1:8070/login/oauth2/code/messaging-client-authorization-code")
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true)
.requireProofKey(false)
.build())
.tokenSettings(TokenSettings.builder()
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
.accessTokenTimeToLive(Duration.ofSeconds(30 * 60))
.refreshTokenTimeToLive(Duration.ofSeconds(60 * 60))
.reuseRefreshTokens(true)
.build())
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
其余配置将不再赘述,可以参考之前将JWT与Spring Security OAuth2结合使用文章。
接下来将创建一个授权页面控制器,并将所需参数传递给Model:
@Controller
@RequiredArgsConstructor
public class AuthorizationConsentController {
private final RegisteredClientRepository registeredClientRepository;
@GetMapping(value = "/oauth2/consent")
public String consent(Principal principal, Model model,
@RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
@RequestParam(OAuth2ParameterNames.SCOPE) String scope,
@RequestParam(OAuth2ParameterNames.STATE) String state) {
Set scopesToApprove = new LinkedHashSet<>();
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
Set scopes = registeredClient.getScopes();
for (String requestedScope : StringUtils.delimitedListToStringArray(scope, " ")) {
if (scopes.contains(requestedScope)) {
scopesToApprove.add(requestedScope);
}
}
model.addAttribute("clientId", clientId);
model.addAttribute("clientName", registeredClient.getClientName());
model.addAttribute("state", state);
model.addAttribute("scopes", withDescription(scopesToApprove));
model.addAttribute("principalName", principal.getName());
model.addAttribute("redirectUri", registeredClient.getRedirectUris().iterator().next());
return "consent";
}
private static Set withDescription(Set scopes) {
Set scopeWithDescriptions = new LinkedHashSet<>();
for (String scope : scopes) {
scopeWithDescriptions.add(new ScopeWithDescription(scope));
}
return scopeWithDescriptions;
}
public static class ScopeWithDescription {
private static final String DEFAULT_DESCRIPTION = "我们无法提供有关此权限的信息";
private static final Map scopeDescriptions = new HashMap<>();
static {
scopeDescriptions.put(
"profile",
"验证您的身份"
);
scopeDescriptions.put(
"message.read",
"了解您可以访问哪些权限"
);
scopeDescriptions.put(
"message.write",
"代表您行事"
);
}
public final String scope;
public final String description;
ScopeWithDescription(String scope) {
this.scope = scope;
this.description = scopeDescriptions.getOrDefault(scope, DEFAULT_DESCRIPTION);
}
}
}
之后让我们定义html页面,这里使用thymeleaf模版引擎:
Custom consent page - Consent required
希望获得以下许可:
访问授权页面
启动服务后,我们将发起一个授权请求,http://localhost:8080/oauth2/authorize?response_type=code&client_id=relive-client&scope=message.write%20message.read%20profile&state=some-state&redirect_uri=http://127.0.0.1:8070/login/oauth2/code/messaging-client-authorization-code,在认证成功后,我们可以看到以下我们定义的授权同意页面:
与往常一样,本文中使用的源代码可在 GitHub 上获得。



