1.简介
OAuth是描述授权过程的开放标准。它可用于授权用户访问API。例如,REST API可以仅限制具有适当角色的注册用户的访问。
OAuth授权服务器负责对用户进行身份验证,并发布包含用户数据和适当访问策略的访问令牌。
在本教程中,我们将使用Spring Security OAuth Authorization Server实验模块实现一个简单的OAuth服务器。
在此过程中,我们将创建一个客户端服务器应用程序,该应用程序将从REST API中获取Tutorial文章列表。客户端服务和服务器服务都将需要OAuth身份验证。
2.授权服务器实施
让我们从查看OAuth授权服务器配置开始。它将用作文章资源和客户端服务器的身份验证源。
2.1依赖关系
首先,我们需要在pom.xml
文件中添加一些依赖关系:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security.experimental</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.1.0</version>
</dependency>
2.2。配置
application.yml
文件中server.port
属性来配置身份验证服务器将运行的端口:
server:
port: 9000
之后,我们可以转到Spring bean配置。首先,我们需要一个@Configuration
类并导入OAuthAuthorizationServerConfiguration
。在配置类内部,我们将创建一些特定于OAuth的bean。第一个将是客户端服务的存储库。在我们的示例中,我们将使用RegisteredClient
构建器类创建一个客户端:
@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("article-client")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://localhost:8080/login/oauth2/code/articles-client-oidc")
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
}
我们正在配置的属性是:
- 客户端ID – Spring将使用它来标识哪个客户端正在尝试访问资源
- 客户端密码-客户端和服务器已知的秘密,可在两者之间提供信任
- 身份验证方法–在我们的示例中,我们将使用基本身份验证,即用户名和密码
- 授权授予类型–我们希望允许客户端生成授权代码和刷新令牌
- 重定向URI –客户端将在基于重定向的流中使用它
- 范围–此参数定义客户端可能具有的授权。在我们的案例中,我们只有一篇–
articles.read
每个授权服务器都需要其令牌的签名密钥,以在安全域之间保持适当的边界。让我们生成一个2048字节的RSA密钥:
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
除了签名密钥,每个授权服务器还需要具有唯一的发行者URL。通过创建ProviderSettings
bean,我们将其设置为端口9000
http://127.0.0.1
@Bean
public ProviderSettings providerSettings() {
return new ProviderSettings().issuer("http://127.0.0.1:9000");
}
最后,我们将使用带有@EnableWebSecurity
注释的配置类来启用Spring Web安全模块:
@EnableWebSecurity
public class DefaultSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
// ...
}
在这里,我们正在调用authorizeRequests.anyRequest().authenticated()
以要求对所有请求进行身份验证,并且通过调用formLogin(defaults())
方法来提供基于表单的身份验证。
此外,我们将定义一组用于测试的示例用户。为了这个示例,让我们创建一个只有一个管理员用户的存储库:
@Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.build();
return new InMemoryUserDetailsManager(user);
}
3.资源服务器
现在,我们将创建一个资源服务器,该服务器将从GET端点返回文章列表。端点应仅允许根据我们的OAuth服务器进行身份验证的请求。
3.1依赖关系
首先,让我们包括所需的依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
<version>2.4.3</version>
</dependency>
3.2 配置
在开始执行代码之前,我们应该在application.yml
文件中配置一些属性。第一个是服务器端口:
server:
port: 8090
接下来,是时候进行安全配置了。 ProviderSettings
bean中配置的主机和端口为身份验证服务器设置正确的URL:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://127.0.0.1:9000
现在,我们可以设置我们的Web安全配置。再次,我们要明确地说,对文章资源的每个请求都应被授权并具有适当的article.read
权限:
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.mvcMatcher("/articles/**")
.authorizeRequests()
.mvcMatchers("/articles/**").access("hasAuthority('SCOPE_article.read')")
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
如此处所示,我们还调用了oauth2ResourceServer()
方法,该方法将基于application.yml
配置来配置OAuth服务器连接。
3.3 Articles Controller
最后,我们将创建一个REST控制器,该控制器将返回GET /articles
端点下的文章列表:
@RestController
public class ArticlesController {
@GetMapping("/articles")
public String[] getArticles() {
return new String[] { "Article 1", "Article 2", "Article 3" };
}
}
4. API客户端
对于最后一部分,我们将创建一个REST API客户端,该客户端将从资源服务器中获取文章列表。
4.1 依赖关系
首先,让我们包括所需的依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
<version>1.0.4</version>
</dependency>
4.2 配置
正如我们之前所做的,我们将定义一些配置属性以用于身份验证:
server:
port: 8080
spring:
security:
oauth2:
client:
registration:
articles-client-oidc:
provider: spring
client-id: articles-client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid
client-name: articles-client-oidc
articles-client-authorization-code:
provider: spring
client-id: articles-client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/authorized"
scope: articles.read
client-name: articles-client-authorization-code
provider:
spring:
issuer-uri: http://127.0.0.1:9000
现在,让我们创建一个WebClient
实例以执行对资源服务器的HTTP请求。我们将仅使用OAuth授权过滤器的一种附加的标准实现:
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
WebClient
需要OAuth2AuthorizedClientManager
作为依赖项。让我们创建一个默认实现:
@Bean
OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
最后,我们将配置网络安全性:
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.oauth2Login(oauth2Login ->
oauth2Login.loginPage("/oauth2/authorization/articles-client-oidc"))
.oauth2Client(withDefaults());
return http.build();
}
}
在这里,以及在其他服务器中,我们将需要对每个请求进行身份验证。另外,我们需要配置登录页面URL(在.yml
config中定义)和OAuth客户端。
4.3 Articles Client Controller
最后,我们可以创建数据访问控制器。我们将使用先前配置的WebClient
将HTTP请求发送到资源服务器:
@RestController
public class ArticlesController {
private WebClient webClient;
@GetMapping(value = "/articles")
public String[] getArticles(
@RegisteredOAuth2AuthorizedClient("articles-client-authorization-code") OAuth2AuthorizedClient authorizedClient
) {
return this.webClient
.get()
.uri("http://localhost:8090/articles")
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String[].class)
.block();
}
}
OAuth2AuthorizedClient
类的形式从请求中获取OAuth授权令牌。它由Spring使用@RegisterdOAuth2AuthorizedClient
批注(带有正确的标识)自动绑定。在我们的例子中,它是从我们先前在.yml文件中article-client-authorizaiton-code
.yml
该授权令牌进一步传递到HTTP请求。
4.4 访问文章列表
现在,当我们进入浏览器并尝试访问http://localhost:8080/articles
页面时,我们将自动重定向到http://127.0.0.1:900/login
URL下的OAuth服务器登录页面:
提供正确的用户名和密码后,授权服务器会将我们重定向到所请求的URL(文章列表)。
由于访问令牌将存储在cookie中,因此对文章端点的其他请求将不需要登录。
5.结论
在本文中,我们学习了如何设置,配置和使用Spring Security OAuth授权服务器。
0 评论