Skip to content

Commit 3de109a

Browse files
committed
feat(auth-server): 实现 OAuth2 登录功能
- 重构 SecurityConfig,添加 OAuth2 登录相关配置 - 新增 FederatedIdentityAuthenticationSuccessHandler 处理登录成功事件 - 添加授权同意页面和错误页面的控制器 - 新增登录和首页的 Thymeleaf 模板 - 更新 Maven依赖,添加 OAuth2 客户端、Thymeleaf等
1 parent e744665 commit 3de109a

File tree

45 files changed

+830
-572
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+830
-572
lines changed

my-samples/auth-server-05-accessTokenResponseHandler/pom.xml

Lines changed: 0 additions & 45 deletions
This file was deleted.

my-samples/auth-server-05-accessTokenResponseHandler/src/main/resources/application.yml

Lines changed: 0 additions & 6 deletions
This file was deleted.

my-samples/auth-server-06-OAuth2TokenCustomizer/pom.xml renamed to my-samples/auth-server-05-oauth2-login/pom.xml

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<relativePath/> <!-- lookup parent from repository -->
1010
</parent>
1111
<groupId>com.chensoul</groupId>
12-
<artifactId>auth-server-06-OAuth2TokenCustomizer</artifactId>
12+
<artifactId>auth-server-05-oauth2-login</artifactId>
1313
<version>0.0.1-SNAPSHOT</version>
1414

1515
<dependencies>
@@ -19,14 +19,37 @@
1919
</dependency>
2020
<dependency>
2121
<groupId>org.springframework.boot</groupId>
22-
<artifactId>spring-boot-starter-data-redis</artifactId>
22+
<artifactId>spring-boot-starter-oauth2-client</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.springframework.boot</groupId>
26+
<artifactId>spring-boot-starter-thymeleaf</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.thymeleaf.extras</groupId>
30+
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
2331
</dependency>
2432

2533
<dependency>
26-
<groupId>org.projectlombok</groupId>
27-
<artifactId>lombok</artifactId>
28-
<optional>true</optional>
34+
<groupId>org.webjars</groupId>
35+
<artifactId>webjars-locator-core</artifactId>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.webjars</groupId>
39+
<artifactId>bootstrap</artifactId>
40+
<version>5.3.5</version>
41+
</dependency>
42+
<dependency>
43+
<groupId>org.webjars</groupId>
44+
<artifactId>popper.js</artifactId>
45+
<version>2.11.7</version>
2946
</dependency>
47+
<dependency>
48+
<groupId>org.webjars</groupId>
49+
<artifactId>jquery</artifactId>
50+
<version>3.7.1</version>
51+
</dependency>
52+
3053
<dependency>
3154
<groupId>org.springframework.boot</groupId>
3255
<artifactId>spring-boot-starter-test</artifactId>
Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,41 @@
11
package com.chensoul.config;
22

3+
import com.chensoul.support.AccessTokenResponseHandler;
4+
import com.chensoul.support.FederatedIdentityAuthenticationSuccessHandler;
35
import org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerAutoConfiguration;
46
import org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerJwtAutoConfiguration;
57
import org.springframework.context.annotation.Bean;
68
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.core.Ordered;
710
import org.springframework.core.annotation.Order;
811
import org.springframework.http.MediaType;
912
import org.springframework.security.config.Customizer;
1013
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
11-
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
1214
import org.springframework.security.core.authority.AuthorityUtils;
15+
import org.springframework.security.core.session.SessionRegistry;
16+
import org.springframework.security.core.session.SessionRegistryImpl;
17+
import org.springframework.security.core.userdetails.User;
18+
import org.springframework.security.core.userdetails.UserDetails;
19+
import org.springframework.security.core.userdetails.UserDetailsService;
1320
import org.springframework.security.oauth2.core.AuthorizationGrantType;
1421
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
1522
import org.springframework.security.oauth2.core.oidc.OidcScopes;
16-
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
23+
import org.springframework.security.oauth2.server.authorization.*;
1724
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
1825
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
1926
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
2027
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
28+
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
2129
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
2230
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
2331
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
2432
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
2533
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
34+
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
2635
import org.springframework.security.web.SecurityFilterChain;
36+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
2737
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
38+
import org.springframework.security.web.session.HttpSessionEventPublisher;
2839
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
2940

3041
import java.util.Set;
@@ -36,17 +47,20 @@
3647
* @see OAuth2AuthorizationServerAutoConfiguration
3748
* @see OAuth2AuthorizationServerJwtAutoConfiguration
3849
*/
39-
@EnableWebSecurity(debug = true)
4050
@Configuration
4151
public class SecurityConfig {
52+
private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";
53+
4254
@Bean
43-
@Order(1)
55+
@Order(Ordered.HIGHEST_PRECEDENCE)
4456
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
4557
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = authorizationServer();
4658

4759
http.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
4860
.with(authorizationServerConfigurer, (authorizationServer) ->
4961
authorizationServer
62+
.authorizationEndpoint(authorizationEndpoint ->
63+
authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
5064
.tokenEndpoint(token ->
5165
token.accessTokenResponseHandler(new AccessTokenResponseHandler()))
5266
.oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0
@@ -58,14 +72,47 @@ SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) th
5872
new LoginUrlAuthenticationEntryPoint("/login"),
5973
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
6074
)
75+
);
76+
77+
return http.build();
78+
}
79+
80+
@Bean
81+
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
82+
http
83+
.authorizeHttpRequests(authorize ->
84+
authorize
85+
.requestMatchers("/webjars/**", "/assets/**", "/login", "/logged-out").permitAll()
86+
.anyRequest().authenticated()
87+
)
88+
.formLogin(formLogin ->
89+
formLogin
90+
.loginPage("/login")
6191
)
62-
// Accept access tokens for User Info and/or Client Registration
63-
.oauth2ResourceServer((resourceServer) -> resourceServer
64-
.jwt(Customizer.withDefaults()));
92+
.oauth2Login(oauth2Login ->
93+
oauth2Login
94+
.loginPage("/login")
95+
.successHandler(authenticationSuccessHandler())
96+
);
6597

6698
return http.build();
6799
}
68100

101+
private AuthenticationSuccessHandler authenticationSuccessHandler() {
102+
return new FederatedIdentityAuthenticationSuccessHandler();
103+
}
104+
105+
// @formatter:off
106+
@Bean
107+
public UserDetailsService users() {
108+
UserDetails user = User.withDefaultPasswordEncoder()
109+
.username("user")
110+
.password("password")
111+
.roles("USER")
112+
.build();
113+
return new InMemoryUserDetailsManager(user);
114+
}
115+
69116
@Bean
70117
public RegisteredClientRepository registeredClientRepository() {
71118
// @formatter:off
@@ -140,12 +187,28 @@ public RegisteredClientRepository registeredClientRepository() {
140187
}
141188

142189
@Bean
143-
public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() {
144-
return (context) -> {
145-
if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
146-
Set<String> authorities = AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities());
147-
context.getClaims().claim("authorities", authorities);
148-
}
149-
};
190+
public AuthorizationServerSettings authorizationServerSettings() {
191+
return AuthorizationServerSettings.builder().build();
192+
}
193+
194+
@Bean
195+
public OAuth2AuthorizationService authorizationService() {
196+
return new InMemoryOAuth2AuthorizationService();
197+
}
198+
199+
@Bean
200+
public OAuth2AuthorizationConsentService authorizationConsentService() {
201+
return new InMemoryOAuth2AuthorizationConsentService();
202+
}
203+
204+
205+
@Bean
206+
public SessionRegistry sessionRegistry() {
207+
return new SessionRegistryImpl();
208+
}
209+
210+
@Bean
211+
public HttpSessionEventPublisher httpSessionEventPublisher() {
212+
return new HttpSessionEventPublisher();
150213
}
151214
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
package com.chensoul.config;
2+
package com.chensoul.support;
33

44
import jakarta.servlet.ServletException;
55
import jakarta.servlet.http.HttpServletRequest;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2020-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.chensoul.support;
17+
18+
import jakarta.servlet.ServletException;
19+
import jakarta.servlet.http.HttpServletRequest;
20+
import jakarta.servlet.http.HttpServletResponse;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import org.springframework.security.core.Authentication;
24+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
25+
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
26+
import org.springframework.security.oauth2.core.user.OAuth2User;
27+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
28+
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
29+
30+
import java.io.IOException;
31+
import java.util.function.Consumer;
32+
33+
/**
34+
* An {@link AuthenticationSuccessHandler} for capturing the {@link OidcUser} or
35+
* {@link OAuth2User} for Federated Account Linking or JIT Account Provisioning.
36+
*
37+
* @author Steve Riesenberg
38+
* @since 1.1
39+
*/
40+
public final class FederatedIdentityAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
41+
private static final Logger log = LoggerFactory.getLogger(FederatedIdentityAuthenticationSuccessHandler.class);
42+
private final AuthenticationSuccessHandler delegate = new SavedRequestAwareAuthenticationSuccessHandler();
43+
44+
private Consumer<OAuth2User> oauth2UserHandler = (user) -> {
45+
log.info("OAuth2User: {}", user);
46+
};
47+
48+
private Consumer<OidcUser> oidcUserHandler = (user) -> this.oauth2UserHandler.accept(user);
49+
50+
@Override
51+
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
52+
if (authentication instanceof OAuth2AuthenticationToken) {
53+
if (authentication.getPrincipal() instanceof OidcUser oidcUser) {
54+
this.oidcUserHandler.accept(oidcUser);
55+
} else if (authentication.getPrincipal() instanceof OAuth2User oauth2User) {
56+
this.oauth2UserHandler.accept(oauth2User);
57+
}
58+
}
59+
60+
this.delegate.onAuthenticationSuccess(request, response, authentication);
61+
}
62+
63+
public void setOAuth2UserHandler(Consumer<OAuth2User> oauth2UserHandler) {
64+
this.oauth2UserHandler = oauth2UserHandler;
65+
}
66+
67+
public void setOidcUserHandler(Consumer<OidcUser> oidcUserHandler) {
68+
this.oidcUserHandler = oidcUserHandler;
69+
}
70+
71+
}

0 commit comments

Comments
 (0)