Spring Authorization Server(一)
版本要求:
- Spring Authorization Server 版本:1.1.2
- JDK 版本:17
- Spring Boot 版本:3.1.4
认证/授权服务器搭建 Spring Authorization Server重要组件:
- SecurityFilterChain -> authorizationServerSecurityFilterChain Spring Security的过滤器链,用于协议端点的。
- SecurityFilterChain -> defaultSecurityFilterChain Spring Security的过滤器链,用于Spring Security的身份认证
- UserDetailsService 主要进行用户身份验证
- RegisteredClientRepository 主要用于管理客户端
- JWKSource 用于签名访问令牌
- KeyPair 启动时生成的带有密钥的KeyPair实例,用于创建上面的JWKSource
- JwtDecoder JwtDecoder的一个实例,用于解码已签名的访问令牌
- AuthorizationServerSettings 用于配置Spring Authorization Server的AuthorizationServerSettings实例。
添加pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
完整pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<relativePath/>
</parent>
<groupId>com.natalia</groupId>
<artifactId>natalia-authorization</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>auth-server</name>
<description>natalia-authorization</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
yml配置文件
server:
port: 9000
logging:
level:
org.springframework.security: trace
SpringAuthorizationServer相关配置
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.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.MediaTypeRequestMatcher;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
@Configuration
@EnableWebSecurity
public class AuthorizationServerConfig {
/**
* Spring Authorization Server 相关配置
* 主要配置OAuth 2.1和OpenID Connect 1.0
*
* @param http
* @return
* @throws Exception
*/
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
//开启OpenID Connect 1.0(其中oidc为OpenID Connect的缩写)
.oidc(Customizer.withDefaults());
http
//将需要认证的请求,重定向到login进行登录认证。
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
)
// 使用jwt处理接收到的access token
.oauth2ResourceServer((resourceServer) -> resourceServer
.jwt(Customizer.withDefaults()));
return http.build();
}
/**
* Spring Security 过滤链配置(此处是纯Spring Security相关配置)
*/
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
//设置所有请求都需要认证,未认证的请求都被重定向到login页面进行登录
.anyRequest().authenticated()
)
// 由Spring Security过滤链中UsernamePasswordAuthenticationFilter过滤器拦截处理“login”页面提交的登录信息。
.formLogin(Customizer.withDefaults());
return http.build();
}
/**
* Spring Security的配置
* 设置用户信息,校验用户名、密码
* 正常的流程是自定义一个service类,实现UserDetailsService接口,去查询DB 查询用户信息,封装为一个UserDetails对象返回
* 这里就直接写一个user存入内存中进行测试
*
* @return
*/
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails userDetails = User.builder()
.username("admin")
.password(passwordEncoder().encode("123456"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
/**
* 注册客户端信息
* 查询认证服务器信息
* http://127.0.0.1:9000/.well-known/openid-configuration
* <p>
* 获取授权码
* http://localhost:9000/oauth2/authorize?response_type=code&client_id=oidc-client&scope=profileopenid&redirect_uri=http://www.baidu.com
*/
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("oidc-client")
//{noop}开头,表示“secret”以明文存储
.clientSecret(passwordEncoder().encode("secret"))
// 就使用默认的认证方式
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
// 配置授权码模式,刷新令牌,客户端模式
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://spring-oauth-client:9001/login/oauth2/code/messaging-client-oidc")
//我们暂时还没有客户端服务,以免重定向跳转错误导致接收不到授权码
.redirectUri("http://www.baidu.com")
.postLogoutRedirectUri("http://127.0.0.1:8080/")
//设置客户端权限范围
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
//客户端设置用户需要确认授权
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
//配置基于内存的客户端信息
return new InMemoryRegisteredClientRepository(oidcClient);
}
/**
* 配置 JWK,为JWT(id_token)提供加密密钥,用于加密/解密或签名/验签
* JWK详细见:https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key-41
*/
@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);
}
/**
* 生成RSA密钥对,给上面jwkSource()方法的提供密钥对
*/
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) {
//配置jwt解析器
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
//什么都不配置,则使用默认地址
return AuthorizationServerSettings.builder().build();
}
@Bean
public PasswordEncoder passwordEncoder() {
// 使用 BCrypt 加密
return new BCryptPasswordEncoder();
}
}
启动服务查看认证服务信息 http://127.0.0.1:9000/.well-known/openid-configuration