Skip to content

Commit 6d9812d

Browse files
committed
feat(auth): 实现 OAuth2 登录功能
- 新增 SecurityConfig 配置类,设置 OAuth2 授权服务器和客户端配置 - 添加多个 OAuth2 客户端映射器,支持不同身份提供者 - 实现自定义授权请求解析器和认证成功处理器 - 新增实体类和配置类以支持 OAuth2 客户端和用户映射
1 parent 3de109a commit 6d9812d

File tree

67 files changed

+2224
-22
lines changed

Some content is hidden

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

67 files changed

+2224
-22
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
3+
http://localhost:8080/oauth2/authorization/c9494410-420f-11f0-951d-e335fb26d32c
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>3.5.0</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
<groupId>com.chensoul</groupId>
12+
<artifactId>auth-server-06-oauth2-login</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>org.springframework.boot</groupId>
18+
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
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>
31+
</dependency>
32+
33+
<dependency>
34+
<groupId>org.projectlombok</groupId>
35+
<artifactId>lombok</artifactId>
36+
<optional>true</optional>
37+
</dependency>
38+
39+
<dependency>
40+
<groupId>org.webjars</groupId>
41+
<artifactId>webjars-locator-core</artifactId>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.webjars</groupId>
45+
<artifactId>bootstrap</artifactId>
46+
<version>5.3.5</version>
47+
</dependency>
48+
<dependency>
49+
<groupId>org.webjars</groupId>
50+
<artifactId>popper.js</artifactId>
51+
<version>2.11.7</version>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.webjars</groupId>
55+
<artifactId>jquery</artifactId>
56+
<version>3.7.1</version>
57+
</dependency>
58+
59+
<dependency>
60+
<groupId>org.springframework.boot</groupId>
61+
<artifactId>spring-boot-starter-test</artifactId>
62+
<scope>test</scope>
63+
</dependency>
64+
<dependency>
65+
<groupId>org.springframework.security</groupId>
66+
<artifactId>spring-security-test</artifactId>
67+
<scope>test</scope>
68+
</dependency>
69+
</dependencies>
70+
71+
<build>
72+
<plugins>
73+
<plugin>
74+
<groupId>org.springframework.boot</groupId>
75+
<artifactId>spring-boot-maven-plugin</artifactId>
76+
</plugin>
77+
</plugins>
78+
</build>
79+
</project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.chensoul;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class AuthServerApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(AuthServerApplication.class, args);
11+
}
12+
13+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package com.chensoul.config;
2+
3+
import com.chensoul.support.AccessTokenResponseHandler;
4+
import com.chensoul.support.FederatedIdentityAuthenticationSuccessHandler;
5+
import org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerAutoConfiguration;
6+
import org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerJwtAutoConfiguration;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.core.Ordered;
10+
import org.springframework.core.annotation.Order;
11+
import org.springframework.http.MediaType;
12+
import org.springframework.security.config.Customizer;
13+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
14+
import org.springframework.security.core.session.SessionRegistry;
15+
import org.springframework.security.core.session.SessionRegistryImpl;
16+
import org.springframework.security.core.userdetails.User;
17+
import org.springframework.security.core.userdetails.UserDetails;
18+
import org.springframework.security.core.userdetails.UserDetailsService;
19+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
20+
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
21+
import org.springframework.security.oauth2.core.oidc.OidcScopes;
22+
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
23+
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
24+
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
25+
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
26+
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
27+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
28+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
29+
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
30+
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
31+
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
32+
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
33+
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
34+
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
35+
import org.springframework.security.web.SecurityFilterChain;
36+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
37+
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
38+
import org.springframework.security.web.session.HttpSessionEventPublisher;
39+
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
40+
41+
import java.util.UUID;
42+
43+
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer.authorizationServer;
44+
45+
/**
46+
* @see OAuth2AuthorizationServerAutoConfiguration
47+
* @see OAuth2AuthorizationServerJwtAutoConfiguration
48+
*/
49+
@Configuration
50+
public class SecurityConfig {
51+
private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";
52+
53+
@Bean
54+
@Order(Ordered.HIGHEST_PRECEDENCE)
55+
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
56+
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = authorizationServer();
57+
58+
http.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
59+
.with(authorizationServerConfigurer, (authorizationServer) ->
60+
authorizationServer
61+
.authorizationEndpoint(authorizationEndpoint ->
62+
authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
63+
.tokenEndpoint(token ->
64+
token.accessTokenResponseHandler(new AccessTokenResponseHandler()))
65+
.oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0
66+
)
67+
// Redirect to the login page when not authenticated from the
68+
// authorization endpoint
69+
.exceptionHandling((exceptions) -> exceptions
70+
.defaultAuthenticationEntryPointFor(
71+
new LoginUrlAuthenticationEntryPoint("/login"),
72+
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
73+
)
74+
);
75+
76+
return http.build();
77+
}
78+
79+
@Bean
80+
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
81+
http
82+
.authorizeHttpRequests(authorize ->
83+
authorize
84+
.requestMatchers("/webjars/**", "/assets/**", "/login", "/logged-out").permitAll()
85+
.anyRequest().authenticated()
86+
)
87+
.formLogin(formLogin ->
88+
formLogin
89+
.loginPage("/login")
90+
)
91+
.oauth2Login(oauth2Login ->
92+
oauth2Login
93+
.loginPage("/login")
94+
.successHandler(authenticationSuccessHandler())
95+
);
96+
97+
return http.build();
98+
}
99+
100+
private AuthenticationSuccessHandler authenticationSuccessHandler() {
101+
return new FederatedIdentityAuthenticationSuccessHandler();
102+
}
103+
104+
// @formatter:off
105+
@Bean
106+
public UserDetailsService users() {
107+
UserDetails user = User.withDefaultPasswordEncoder()
108+
.username("user")
109+
.password("password")
110+
.roles("USER")
111+
.build();
112+
return new InMemoryUserDetailsManager(user);
113+
}
114+
115+
@Bean
116+
public RegisteredClientRepository registeredClientRepository() {
117+
// @formatter:off
118+
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
119+
.clientId("oidc-client")
120+
.clientSecret("{noop}oidc-client")
121+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
122+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
123+
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
124+
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
125+
.redirectUri("https://oidcdebugger.com/debug")
126+
.redirectUri("https://oauthdebugger.com/debug")
127+
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/oidc-client")
128+
.postLogoutRedirectUri("http://127.0.0.1:8080/")
129+
.scope(OidcScopes.OPENID)
130+
.scope(OidcScopes.ADDRESS)
131+
.scope(OidcScopes.EMAIL)
132+
.scope(OidcScopes.PHONE)
133+
.scope(OidcScopes.PROFILE)
134+
.scope("read")
135+
.scope("write")
136+
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
137+
.build();
138+
139+
RegisteredClient credentialsClient = RegisteredClient.withId(UUID.randomUUID().toString())
140+
.clientId("credentials-client")
141+
.clientSecret("{noop}credentials-client")
142+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
143+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
144+
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
145+
.scope(OidcScopes.OPENID)
146+
.scope(OidcScopes.PROFILE)
147+
.scope("read")
148+
.scope("write")
149+
.scope("client.create")
150+
.scope("client.read")
151+
.build();
152+
153+
RegisteredClient pkceClient = RegisteredClient.withId(UUID.randomUUID().toString())
154+
.clientId("pkce-client")
155+
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
156+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
157+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
158+
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
159+
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
160+
.redirectUri("http://127.0.0.1:4200")
161+
.redirectUri("https://oidcdebugger.com/debug")
162+
.redirectUri("https://oauthdebugger.com/debug")
163+
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/pkce-client")
164+
.postLogoutRedirectUri("http://127.0.0.1:8080/")
165+
.scope(OidcScopes.OPENID)
166+
.scope(OidcScopes.PROFILE)
167+
.clientSettings(ClientSettings.builder()
168+
.requireAuthorizationConsent(true)
169+
.requireProofKey(true)
170+
.build()
171+
)
172+
.build();
173+
174+
RegisteredClient opaqueClient = RegisteredClient.withId(UUID.randomUUID().toString())
175+
.clientId("opaque-client")
176+
.clientSecret("{noop}opaque-client")
177+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
178+
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
179+
.scope(OidcScopes.OPENID)
180+
.scope(OidcScopes.PROFILE)
181+
.tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build()
182+
).build();
183+
184+
// @formatter:on
185+
return new InMemoryRegisteredClientRepository(oidcClient, credentialsClient, pkceClient, opaqueClient);
186+
}
187+
188+
@Bean
189+
public AuthorizationServerSettings authorizationServerSettings() {
190+
return AuthorizationServerSettings.builder().build();
191+
}
192+
193+
@Bean
194+
public OAuth2AuthorizationService authorizationService() {
195+
return new InMemoryOAuth2AuthorizationService();
196+
}
197+
198+
@Bean
199+
public OAuth2AuthorizationConsentService authorizationConsentService() {
200+
return new InMemoryOAuth2AuthorizationConsentService();
201+
}
202+
203+
204+
@Bean
205+
public SessionRegistry sessionRegistry() {
206+
return new SessionRegistryImpl();
207+
}
208+
209+
@Bean
210+
public HttpSessionEventPublisher httpSessionEventPublisher() {
211+
return new HttpSessionEventPublisher();
212+
}
213+
}

0 commit comments

Comments
 (0)