Spring Authorization Server
Spring Authorization Server是Spring团队新开源的一个项目,它在Spring Security的基础上,提供了OAuth2.1和OpenID1.0协议的支持,开发者可以基于此定制化自己的账号认证和授权系统。
1.快速入门
下面我将展示如何快速搭建一个基于内存的简单授权系统。
1.1 环境要求
JDK版本:17
SpringBoot版本:3.2.3
Spring Authorization Server版本:1.2.2
1.2 添加依赖
新建Spring Boot项目后,添加以下依赖:
<dependencies>
<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>
1.3 编写配置
添加配置类,配置最基础的组件:
/**
* @author hundanli
* @version 1.0.0
* @date 2024/3/8 22:17
*/
@Configuration
@EnableWebSecurity
@ConditionalOnProperty(name = "auth.quickstart", havingValue = "true", matchIfMissing = true)
public class QuickStartConfig {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
// Enable OpenID Connect 1.0
.oidc(Customizer.withDefaults());
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer((resourceServer) -> resourceServer
.jwt(Customizer.withDefaults()));
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.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("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("hello")
.clientSecret("{noop}123456")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/authorized")
.postLogoutRedirectUri("http://127.0.0.1:8080/")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
return new InMemoryRegisteredClientRepository(oidcClient);
}
@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();
}
}
上述配置类中的每个Bean的作用如下:
1.Spring Security过滤器链,用于协议端点。
2.用于认证的Spring Security过滤器链。
3.用于检索要认证的用户的UserDetailsService实例。
4.用于管理客户端的RegisteredClientRepository实例。
5.用于签署访问令牌的com.nimbusds.jose.jwk.source.JWKSource实例。
6.启动时生成的用于创建上述JWKSource的java.security.KeyPair实例。
7.用于解码签名访问令牌的JwtDecoder实例。
8.用于配置Spring Authorization Server的AuthorizationServerSettings实例。
1.4 配置文件
在application.properties文件中配置端口,并配置trace日志级别以便排查问题
server.port=8000
spring.application.name=auth-server
logging.level.org.springframework.security=trace
上述步骤完成后,即可启动AuthServer服务了。
你可以访问http://127.0.0.1:8000/login,并用user/password去尝试登录验证是否正常。
1.5 Client服务
在完成AuthServer后,我们还需要实现一个简单的OAuth Client服务,以便走一个完整的OAuth登录流程。
创建一个SpringBoot项目,然后实现/authorized接口,对应上述AuthServer服务中配置的redirect_uri。
1.首先需要一个DTO来分序列化AccessToken:
/**
* @author hundanli
* @version 1.0.0
* @date 2024/3/8 16:04
*/
@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class AccessTokenDTO {
private String accessToken;
private String scope;
private String tokenType;
private Integer expiresIn;
}
2.AuthorizedController类:
/**
* @author hundanli
* @version 1.0.0
* @date 2024/3/7 23:20
*/
@RestController
public class AuthorizedController {
WebClient webClient = WebClient.create("http://127.0.0.1:8000");
@Autowired
private ObjectMapper objectMapper/* = new ObjectMapper()*/;
@GetMapping("/authorized")
public Mono<String> authorized(@RequestParam("code") String code) {
String clientId = "hello";
String clientSecret = "123456";
String base64Credentials = Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes(StandardCharsets.UTF_8));
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("grant_type", "authorization_code");
formData.add("redirect_uri", "http://127.0.0.1:8080/authorized");
formData.add("code", code);
formData.add("client_id", clientId);
formData.add("client_secret", clientSecret);
return webClient.post()
.uri("/oauth2/token")
.header(HttpHeaders.AUTHORIZATION, "Basic " + base64Credentials)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(String.class)
.flatMap(json -> {
try {
// 解析出access_token并请求/userinfo接口获取信息
AccessTokenDTO accessTokenDTO = objectMapper.readValue(json, AccessTokenDTO.class);
String accessToken = accessTokenDTO.getAccessToken();
return webClient.get().uri("/userinfo")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
.retrieve()
.bodyToMono(String.class);
} catch (JsonProcessingException e) {
return Mono.just("error");
}
});
}
}
这里用到了webflux响应式编程,不熟悉的可以用常规的webmvc替代。然后启动这个OAuth Client服务。
1.6 测试OAuth认证
2.此时会跳转到:http://127.0.0.1:8000/login 页面,输入user/password进行登录,AuthServer会返回302响应和code
3.浏览器将会自动进行302请求:http://127.0.0.1:8080/authorized?code=xxx
4.然后OAuth Client将会获取这个code调用AuthServer的/oauth2/token接口获取access_token。
5.最后再使用access_token调用AuthServer的/userinfo接口获取用户信息。
至此顺利完成Spring Authorization Server的第一步,完结撒花。