Table of contents
foreword
Spring Boot 3 has been released for a while, and there are not many materials about Spring Boot 3 on the Internet . In line with the enthusiasm for new technologies, I have learned and researched a lot of new functions and features of Spring Boot 3. Interested students can refer to the official spring data for a comprehensive Detailed introduction of new features/improvements
- Spring version upgrade to 6.x
- JDK version at least 17+
- …
There are many new features. This article mainly focuses on the integration of OAuth 2.0. If you quickly develop your own authentication and authorization services, OAuth clients, and resource services
Introduction to the development environment of this article
development dependencies | Version |
---|---|
Spring Boot | 3.0.2 |
Development environment port description
Create three new services, corresponding to authentication and authorization service, OAuth client and resource service
Serve | port |
---|---|
Authentication and Authorization Service | 8080 |
OAuth client service | 8081 |
resource service | 8082 |
Authentication and Authorization Service
pom.xml dependencies
Spring released the spring-security-oauth2-authorization-server project, the latest version is version 1.0, pom.xml depends on the following
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>${spring-security-oauth2-authorization-server.version}</version>
</dependency>
</dependencies>
Create a new Oauth2ServerAutoConfiguration class
package com.wen3.oauth.ss.authserver.authconfigure;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
@Configuration
public class Oauth2ServerAutoConfiguration {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(new AntPathRequestMatcher("/actuator/**"),
new AntPathRequestMatcher("/oauth2/**"),
new AntPathRequestMatcher("/**/*.json"),
new AntPathRequestMatcher("/**/*.html")).permitAll()
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("test")
.password("test")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("demo-client-id")
.clientSecret("{noop}demo-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
// .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build())
.redirectUri("http://127.0.0.1:8081/login/oauth2/code/client-id-1")
.redirectUri("http://127.0.0.1:8081/login/oauth2/code/client-id-2")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.scope("user_info")
.scope("pull_requests")
// 登录成功后对scope进行确认授权
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
}
main function
@SpringBootApplication
public class OauthServerApplication {
public static void main(String[] args) {
SpringApplication.run(OauthServerApplication.class, args);
}
}
yml configuration
server:
port: 8080
Third-party application OAuth client
pom.xml dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
</dependencies>
Create a new Oauth2ClientAutoConfiguration class
package com.wen3.oauth.ss.authclient.autoconfigure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class Oauth2ClientAutoConfiguration {
@Bean
public SecurityFilterChain authorizationClientSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.anyRequest().authenticated()
.and().logout()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and().oauth2Client()
.and().oauth2Login();
return http.build();
}
}
Create a new OauthClientDemoController class
package com.wen3.oauth.ss.authclient.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class OauthClientDemoController {
@RequestMapping(path = "/hello")
public String demo() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
log.info("authentication: {}", authentication);
return "hello";
}
}
main function
@SpringBootApplication
public class OauthServerApplication {
public static void main(String[] args) {
SpringApplication.run(OauthServerApplication.class, args);
}
}
yml configuration
server:
port: 8081
servlet:
session:
cookie:
# 需要更换存放sessionId的cookie名字,否则认证服务和客户端的sessionId会相互覆盖
name: JSESSIONID-2
spring:
security:
oauth2:
client:
registration:
client-id-1:
provider: demo-client-id
client-id: demo-client-id
client-secret: demo-client-secret
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'
# client-authentication-method: POST
scope: user_info, pull_requests
client-name: demo-client-id
client-id-2:
provider: demo-client-id2
client-id: demo-client-id
client-secret: demo-client-secret
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'
# client-authentication-method: POST
scope: user_info, pull_requests
client-name: demo-client-id2
provider:
demo-client-id:
authorization-uri: http://127.0.0.1:8080/oauth2/authorize
token-uri: http://127.0.0.1:8080/oauth2/token
user-info-uri: http://127.0.0.1:8082/user/info
user-name-attribute: name
jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks
demo-client-id2:
authorization-uri: http://127.0.0.1:8080/oauth2/authorize
token-uri: http://127.0.0.1:8080/oauth2/token
user-info-uri: http://127.0.0.1:8082/user/info
user-name-attribute: name
jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks
resource service
pom.xml dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
</dependencies>
Create a new ResourceServerAutoConfiguration class
package com.wen3.oauth.ss.resourceserver.autoconfigure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class ResourceServerAutoConfiguration {
@Bean
public SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().anyRequest().authenticated().and()
.oauth2ResourceServer().jwt();
return http.build();
}
}
Create a new UserController class
package com.wen3.oauth.ss.resourceserver.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class UserController {
@RequestMapping(path = "/user/info", method = {
RequestMethod.GET,RequestMethod.POST}, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Object> getUser(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> map = new HashMap<>();
map.put("name", "xxx");
return map;
}
}
main function
package com.wen3.oauth.ss.resourceserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ResourceServerApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceServerApplication.class, args);
}
}
yml configuration
server:
port: 8082
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks
demo
- Enter in the browser address bar
http://127.0.0.1:8081/hello
Because multiple clients are configured, the user will choose which client to use for OAuth login
- Select the first client-id-1
- Jump to the authentication and authorization service to log in
- Enter username test, password test
- Jump to the authorization page after successful login
- Check the scope to confirm the authorization
- redirect request
All of the above pages are Spring’s default, real business development will customize these pages
OAuth client openid demo
- Type in the browser
http://127.0.0.1:8080/.well-known/openid-configuration
{ "issuer":"http://127.0.0.1:8080","authorization_endpoint":"http://127.0.0.1:8080/oauth2/authorize","token_endpoint":"http://127.0.0.1:8080/oauth2/token","token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt"],"jwks_uri":"http://127.0.0.1:8080/oauth2/jwks","userinfo_endpoint":"http://127.0.0.1:8080/userinfo","response_types_supported":["code"],"grant_types_supported":["authorization_code","client_credentials","refresh_token"],"revocation_endpoint":"http://127.0.0.1:8080/oauth2/revoke","revocation_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt"],"introspection_endpoint":"http://127.0.0.1:8080/oauth2/introspect","introspection_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"scopes_supported":["openid"]}
- If it is configured , it will be called to get the configuration information
issuer-uri
at startup , and if it is configured, it will be replaced with${issuer-uri}/.well-known/openid-configuration
provider
issuer-uri
path
/.well-known/openid-configuration
/.well-known/openid-configuration
The address obtained from this interfaceuser-info-uri
ishttp://127.0.0.1:8080/userinfo
so the user information will be obtained from the authorization service- In order to allow
/userinfo
the interface of the authorization service to return normally, you need toregistration
add it to the scope during configurationopenid
, and at the same time, you need to add at least one ofprofile
,email
,address
,phone
to the scope. The modified yml configuration is as follows
server:
port: 8081
servlet:
session:
cookie:
# 需要更换存放sessionId的cookie名字,否则认证服务和客户端的sessionId会相互覆盖
name: JSESSIONID-2
spring:
security:
oauth2:
client:
registration:
client-id-1:
provider: demo-client-id
client-id: demo-client-id
client-secret: demo-client-secret
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'
# client-authentication-method: POST
scope: openid, profile, user_info, pull_requests
client-name: demo-client-id
client-id-2:
provider: demo-client-id2
client-id: demo-client-id
client-secret: demo-client-secret
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'
# client-authentication-method: POST
scope: openid, profile, user_info, pull_requests
client-name: demo-client-id2
provider:
demo-client-id:
issuer-uri: http://127.0.0.1:8080
demo-client-id2:
issuer-uri: http://127.0.0.1:8080
- Authorization page will change
Finish
After studying the relevant source code of SpringBoot3, I basically experienced all the functions. This article is mainly to demonstrate the latest version of SpringBoot integrated OAuth function. The principle behind it, if you have any questions, you can leave a message to communicate.