1. Introduction
1.绪论
OAuth is an open standard that describes a process of authorization. It can be used to authorize user access to an API. For example, a REST API can restrict access to only registered users with a proper role.
OAuth是一个开放的标准,描述了一个授权的过程。它可用于授权用户对API的访问。例如,一个REST API可以限制只有具有适当角色的注册用户才能访问。
An OAuth authorization server is responsible for authenticating the users and issuing access tokens containing the user data and proper access policies.
OAuth授权服务器负责对用户进行认证,并发放包含用户数据和适当访问策略的访问令牌。
In this tutorial, we’ll implement a simple OAuth application using the Spring Security OAuth Authorization Server project.
在本教程中,我们将使用Spring Security OAuth授权服务器项目实现一个简单的OAuth应用程序。
In the process, we’ll create a client-server application that will fetch a list of Baeldung articles from a REST API. Both the client services and server services will require an OAuth authentication.
在这个过程中,我们将创建一个客户端-服务器应用程序,从REST API中获取Baeldung文章的列表。客户端服务和服务器服务都需要一个OAuth认证。
2. Authorization Server Implementation
2.授权服务器的实施
We’ll start by looking at the OAuth authorization server configuration. It’ll serve as an authentication source for both the article resource and client servers.
我们先来看看OAuth授权服务器的配置。它将作为文章资源和客户端服务器的认证源。
2.1. Dependencies
2.1. 依赖性
First, we’ll need to add a few dependencies to our pom.xml file:
首先,我们需要在我们的pom.xml文件中添加一些依赖项。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.2.0</version>
</dependency>
2.2. Configuration
2.2.配置
Now we’ll configure the port that our auth server will run on by setting the server.port property in the application.yml file:
现在我们将通过在application.yml文件中设置server.port属性来配置我们的认证服务器将运行的端口。
server:
port: 9000
Then we can move to the Spring beans configuration. First, we’ll need a @Configuration class where we’ll create a few OAuth-specific beans. The first one will be the repository of client services. In our example, we’ll have a single client, created using the RegisteredClient builder class:
然后我们可以转向Spring Bean的配置。首先,我们需要一个@Configuration类,在那里我们将创建一些OAuth特有的bean。第一个将是客户端服务的存储库。在我们的例子中,我们将有一个单一的客户端,使用RegisteredClient构建器类来创建。
@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("articles-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
}
The properties we’re configuring are:
我们要配置的属性是。
- Client ID – Spring will use it to identify which client is trying to access the resource
- Client secret code – a secret known to the client and server that provides trust between the two
- Authentication method – in our case, we’ll use basic authentication, which is just a username and password
- Authorization grant type – we want to allow the client to generate both an authorization code and a refresh token
- Redirect URI – the client will use it in a redirect-based flow
- Scope – this parameter defines authorizations that the client may have. In our case, we’ll have the required OidcScopes.OPENID and our custom one, articles. read
Next, we’ll configure a bean to apply the default OAuth security and generate a default form login page:
接下来,我们将配置一个Bean来应用默认的OAuth安全,并生成一个默认的表单登录页面。
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
Each authorization server needs its signing key for tokens to keep a proper boundary between security domains. Let’s generate a 2048-byte RSA key:
每个授权服务器都需要它的令牌签名密钥,以保持安全域之间的适当边界。让我们生成一个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();
}
Except for the signing key, each authorization server needs to have a unique issuer URL as well. We’ll set it up as a localhost alias for http://auth-server on port 9000 by creating the ProviderSettings bean:
除了签名密钥,每个授权服务器也需要有一个独特的发行者URL。我们将通过创建ProviderSettingsBean,把它设置为http://auth-server端口9000的localhost别名。
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("http://auth-server:9000")
.build();
}
In addition, we’ll add an “127.0.0.1 auth-server” entry in our /etc/hosts file. This allows us to run the client and the auth server on our local machine, and avoids problems with session cookie overwrites between the two.
此外,我们将在/etc/hosts文件中添加一个”127.0.0.1 auth-server“条目。这允许我们在本地机器上运行客户端和认证服务器,并避免了两者之间的会话cookie重写问题。
Then we’ll enable the Spring web security module with an @EnableWebSecurity annotated configuration class:
然后我们将用@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();
}
// ...
}
Here we’re calling authorizeRequests.anyRequest().authenticated() to require authentication for all requests. We’re also providing a form-based authentication by invoking the formLogin(defaults()) method.
这里我们调用authorizeRequests.anyRequest().authenticated()来要求对所有请求进行认证。我们还通过调用formLogin(defaults())方法来提供一个基于表单的认证。
Finally, we’ll define a set of example users that we’ll use for testing. For the sake of this example, we’ll create a repository with just a single admin user:
最后,我们将定义一组用于测试的示例用户。为了这个例子,我们将创建一个只有一个管理员用户的资源库。
@Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.build();
return new InMemoryUserDetailsManager(user);
}
3. Resource Server
3.资源服务器
Now we’ll create a resource server that will return a list of articles from a GET endpoint. The endpoints should allow only requests that are authenticated against our OAuth server.
现在我们将创建一个资源服务器,它将从GET端点返回文章列表。端点应该只允许对我们的OAuth服务器进行认证的请求。
3.1. Dependencies
3.1. 依赖性
First, we’ll include the required dependencies:
首先,我们将包括所需的依赖性。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
<version>2.5.4</version>
</dependency>
3.2. Configuration
3.2.配置
Before we start with the implementation code, we should configure some properties in the application.yml file. The first one is the server port:
在我们开始执行代码之前,我们应该在application.yml文件中配置一些属性。第一个是服务器端口。
server:
port: 8090
Next, it’s time for the security configuration. We need to set up the proper URL for our authentication server with the host and the port we’ve configured in the ProviderSettings bean earlier:
接下来,是安全配置的时候了。我们需要为我们的认证服务器设置适当的URL,包括我们之前在ProviderSettingsBean中配置的主机和端口。
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://auth-server:9000
Now we can set up our web security configuration. Again, we want to explicitly state that every request to article resources should be authorized and have the proper articles.read authority:
现在我们可以设置我们的网络安全配置了。同样,我们要明确指出,对文章资源的每一个请求都应该被授权,并有适当的articles.read权限。
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.mvcMatcher("/articles/**")
.authorizeRequests()
.mvcMatchers("/articles/**")
.access("hasAuthority('SCOPE_articles.read')")
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
As shown here, we’re also invoking the oauth2ResourceServer() method, which will configure the OAuth server connection based on the application.yml configuration.
如图所示,我们也在调用oauth2ResourceServer()方法,它将根据application.yml配置来配置OAuth服务器连接。
3.3. Articles Controller
3.3.文章控制器
Finally, we’ll create a REST controller that will return a list of articles under the GET /articles endpoint:
最后,我们将创建一个REST控制器,在GET /articles端点下返回一个文章列表。
@RestController
public class ArticlesController {
@GetMapping("/articles")
public String[] getArticles() {
return new String[] { "Article 1", "Article 2", "Article 3" };
}
}
4. API Client
4.API客户端
For the last part, we’ll create a REST API client that will fetch the list of articles from the resource server.
最后一部分,我们将创建一个REST API客户端,从资源服务器上获取文章的列表。
4.1. Dependencies
4.1. 依赖性
To start, we’ll include the necessary dependencies:
首先,我们将包括必要的依赖性。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
<version>1.0.9</version>
</dependency>
4.2. Configuration
4.2.配置
As we did earlier, we’ll define some configuration properties for authentication purposes:
正如我们先前所做的那样,我们将定义一些配置属性用于认证目的。
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: "http://127.0.0.1:8080/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: "http://127.0.0.1:8080/authorized"
scope: articles.read
client-name: articles-client-authorization-code
provider:
spring:
issuer-uri: http://auth-server:9000
Now we’ll create a WebClient instance to perform HTTP requests to our resource server. We’ll use the standard implementation with just one addition of the OAuth authorization filter:
现在我们将创建一个WebClient实例来执行对资源服务器的HTTP请求。我们将使用标准的实现,只增加一个OAuth授权过滤器。
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
The WebClient requires an OAuth2AuthorizedClientManager as a dependency. Let’s create a default implementation:
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;
}
Lastly, we’ll configure web security:
最后,我们将配置网络安全。
@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();
}
}
Here, as well as in other servers, we’ll need every request to be authenticated. Additionally, we need to configure the login page URL (defined in .yml config) and the OAuth client.
在这里,和其他服务器一样,我们需要每个请求都经过认证。此外,我们需要配置登录页面的URL(定义在.yml配置中)和OAuth客户端。
4.3. Articles Client Controller
4.3.文章 客户端控制器
Finally, we can create the data access controller. We’ll use the previously configured WebClient to send an HTTP request to our resource server:
最后,我们可以创建数据访问控制器。我们将使用之前配置的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://127.0.0.1:8090/articles")
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String[].class)
.block();
}
}
In the above example, we’re taking the OAuth authorization token from the request in a form of OAuth2AuthorizedClient class. It’s automatically bound by Spring using the @RegisterdOAuth2AuthorizedClient annotation with proper identification. In our case, it’s pulled from the article-client-authorizaiton-code that we configured previously in the .yml file.
在上面的例子中,我们以OAuth2AuthorizedClient类的形式从请求中获取OAuth授权令牌。它被Spring使用@RegisterdOAuth2AuthorizedClient注解和适当的标识自动绑定。在我们的例子中,它来自我们之前在.yml文件中配置的article-client-authorizaiton-code。
This authorization token is further passed to the HTTP request.
这个授权令牌会进一步传递给HTTP请求。
4.4. Accessing the Articles List
4.4.访问文章列表
Now when we go into the browser and try to access the http://127.0.0.1:8080/articles page, we’ll be automatically redirected to the OAuth server login page under http://auth-server:9000/login URL:
现在,当我们进入浏览器并试图访问http://127.0.0.1:8080/articles页面时,我们将被自动重定向到http://auth-server:9000/login URL下的OAuth服务器登录页面。
After providing the proper username and password, the authorization server will redirect us back to the requested URL, the list of articles.
在提供适当的用户名和密码后,授权服务器将把我们重新引导到所要求的URL,即文章列表。
Further requests to the articles endpoint won’t require logging in, as the access token will be stored in a cookie.
对文章端点的进一步请求将不需要登录,因为访问令牌将被存储在一个cookie中。
5. Conclusion
5.总结
In this article, we learned how to set up, configure, and use the Spring Security OAuth Authorization Server.
在这篇文章中,我们学习了如何设置、配置和使用Spring Security OAuth授权服务器。
As always, the full source code is available over on GitHub.
一如既往,完整的源代码可在GitHub上获得,。