1. Overview
1.概述
Cloud Foundry User Account and Authentication (CF UAA) is an identity management and authorization service. More precisely, it’s an OAuth 2.0 provider allowing authentication and issuing tokens to Client applications.
Cloud Foundry User Account and Authentication (CF UAA)是一个身份管理和授权服务。更确切地说,它是一个OAuth 2.0提供商,允许认证并向客户端应用程序发放令牌。
In this tutorial, we’re going to cover the basics of setting up a CF UAA server. We’ll then look at how to use it for protecting Resource Server applications.
在本教程中,我们将介绍设置CF UAA服务器的基本知识。然后,我们将看看如何使用它来保护资源服务器应用程序。
But before, let’s clarify the role of UAA in the OAuth 2.0 authorization framework.
但是在这之前,让我们先澄清一下UAA在OAuth 2.0授权框架中的作用。
2. Cloud Foundry UAA and OAuth 2.0
2.Cloud Foundry UAA和OAuth 2.0
Let’s start by understanding how UAA relates to the OAuth 2.0 specification.
让我们首先了解UAA与OAuth 2.0规范的关系。
The OAuth 2.0 specification defines four participants that can connect to each other: a Resource Owner, a Resource Server, a Client, and an Authorization Server.
OAuth 2.0规范定义了可以相互连接的四个参与者:资源所有者、资源服务器、客户端和授权服务器。
As an OAuth 2.0 provider, UAA plays the role of the authorization server. This means its primary goal is issuing access tokens for client applications and validating these tokens for resource servers.
作为一个OAuth 2.0提供商,UAA扮演着授权服务器的角色。这意味着其主要目标是为客户端应用程序发布访问令牌,并为资源服务器s验证这些令牌。
To allow the interaction of these participants, we need to first to set up a UAA server and then implement two more applications: one as a client and the other as a resource server.
为了让这些参与者进行互动,我们首先需要建立一个UAA服务器,然后再实现两个应用程序:一个作为客户端,另一个作为资源服务器。
We’ll use the authorization_code grant flow with the client. And we’ll use Bearer token authorization with the resource server. For a more secure and efficient handshake, we’ll use signed JWTs as our access tokens.
我们将在客户端使用authorization_code grant>流程。而我们将与资源服务器一起使用Bearer token授权。为了实现更安全、更高效的握手,我们将使用经过签名的JWT作为我们的访问令牌。
3. Setting Up a UAA Server
3.设置UAA服务器
First, we’ll install UAA and populate it with some demo data.
首先,我们将安装UAA,并用一些演示数据来填充它。
Once installed, we’ll register a client application named webappclient. Then, we’ll create a user named appuser with two roles, resource.read and resource.write.
一旦安装完毕,我们将注册一个名为webappclient.的客户端应用程序。然后,我们将创建一个名为appuser的用户,他有两个角色:resource.read 和resource.write。
3.1. Installation
3.1.安装
UAA is a Java web application that can be run in any compliant servlet container. In this tutorial, we’ll use Tomcat.
UAA是一个Java网络应用程序,可以在任何兼容的Servlet容器中运行。在本教程中,我们将使用Tomcat>。
Let’s go ahead and download the UAA war and deposit it into our Tomcat deployment:
让我们继续下载UAA战争并将其存入我们的Tomcat部署中。
wget -O $CATALINA_HOME/webapps/uaa.war \
https://search.maven.org/remotecontent?filepath=org/cloudfoundry/identity/cloudfoundry-identity-uaa/4.27.0/cloudfoundry-identity-uaa-4.27.0.war
Before we start it up, though, we’ll need to configure its data source and JWS key pair.
不过,在我们启动它之前,我们需要配置它的数据源和JWS密钥对。
3.2. Required Configuration
3.2.所需配置
By default, UAA reads configuration from uaa.yml on its classpath. But, since we’ve just downloaded the war file, it’ll be better for us to tell UAA a custom location on our file system.
默认情况下,UAA从其classpath上的uaa.yml读取配置。但是,由于我们刚刚下载了war文件,我们最好告诉UAA在我们的文件系统上的一个自定义位置。
We can do this by setting the UAA_CONFIG_PATH property:
我们可以通过设置UAA_CONFIG_PATH属性来做到这一点:。
export UAA_CONFIG_PATH=~/.uaa
Alternatively, we can set CLOUD_FOUNDRY_CONFIG_PATH. Or, we can specify a remote location with UAA_CONFIG_URL.
或者,我们可以设置CLOUD_FOUNDRY_CONFIG_PATH。或者,我们可以用UAA_CONFIG_URL.指定一个远程位置。
Then, we can copy UAA’s required configuration into our config path:
然后,我们可以将UAA的必要配置复制到我们的配置路径中。
wget -qO- https://raw.githubusercontent.com/cloudfoundry/uaa/4.27.0/uaa/src/main/resources/required_configuration.yml \
> $UAA_CONFIG_PATH/uaa.yml
Note that we are deleting the last three lines because we are going to replace them in a moment.
请注意,我们要删除最后三行,因为我们马上要替换它们。
3.3. Configuring the Data Source
3.3.配置数据源
So, let’s configure the data source, where UAA is going to store information about clients.
因此,让我们来配置数据源,UAA将在那里存储客户的信息。
For the purpose of this tutorial, we’re going to use HSQLDB:
为了本教程的目的,我们将使用HSQLDB。
export SPRING_PROFILES="default,hsqldb"
Of course, since this is a Spring Boot application, we could also specify this in uaa.yml as the spring.profiles property.
当然,由于这是一个Spring Boot应用程序,我们也可以在uaa.yml中指定为spring.profile属性。
3.4. Configuring the JWS Key Pair
3.4.配置JWS密钥对
Since we are using JWT, UAA needs to have a private key to sign each JWT that UAA issues.
由于我们使用的是JWT,UAA需要有一个私钥来签署UAA发出的每个JWT。。
OpenSSL makes this simple:
OpenSSL让这一切变得简单。
openssl genrsa -out signingkey.pem 2048
openssl rsa -in signingkey.pem -pubout -out verificationkey.pem
The authorization server will sign the JWT with the private key, and our client and resource server will verify that signature with the public key.
授权服务器将用私钥签署JWT,而我们的客户端和资源服务器将用公钥验证该签名。
We’ll export them to JWT_TOKEN_SIGNING_KEY and JWT_TOKEN_VERIFICATION_KEY:
我们将把它们导出到JWT_TOKEN_SIGNING_KEY和JWT_TOKEN_VERIFICATION_KEY。
export JWT_TOKEN_SIGNING_KEY=$(cat signingkey.pem)
export JWT_TOKEN_VERIFICATION_KEY=$(cat verificationkey.pem)
Again, we could specify these in uaa.yml via the jwt.token.signing-key and jwt.token.verification-key properties.
同样,我们可以通过jwt.token.signing-key和jwt.token.verify-key属性在uaa.yml中指定这些。
3.5. Starting up UAA
3.5.启动UAA
Finally, let’s start things up:
最后,让我们开始行动吧。
$CATALINA_HOME/bin/catalina.sh run
At this point, we should have a working UAA server available at http://localhost:8080/uaa.
在这一点上,我们应该有一个可用的UAA服务器,地址是http://localhost:8080/uaa。
If we go to http://localhost:8080/uaa/info, then we’ll see some basic startup info
如果我们进入http://localhost:8080/uaa/info,那么我们将看到一些基本的启动信息
3.6. Installing the UAA Command-Line Client
3.6.安装UAA命令行客户端
The CF UAA Command-Line Client is the main tool for administering UAA, but to use it, we need to install Ruby first:
CF UAA命令行客户端是管理UAA的主要工具,但要使用它,我们需要先安装Ruby。
sudo apt install rubygems
gem install cf-uaac
Then, we can configure uaac to point to our running instance of UAA:
然后,我们可以配置uaac以指向我们正在运行的UAA实例。
uaac target http://localhost:8080/uaa
Note that if we don’t want to use command-line client, we can, of course, use UAA’s HTTP client.
注意,如果我们不想使用命令行客户端,当然也可以使用UAA的HTTP客户端。
3.7. Populating Clients and Users Using UAAC
3.7.使用UAAC填充客户和用户
Now that we have uaac installed, let’s populate UAA with some demo data. At a minimum, we’ll need: A client, a user, and resource.read and resource.write groups.
现在我们已经安装了uaac,让我们用一些演示数据来填充UAA。至少,我们将需要。一个客户端,一个用户,以及resource.read和resource.write群组。
So, to do any administration, we’ll need to authentication ourselves. We’ll pick the default admin that ships with UAA, which has permissions to create other clients, users, and groups:
因此,要进行任何管理,我们需要认证自己。我们将选择UAA附带的默认管理员,它有权限创建其他客户、用户和组:。
uaac token client get admin -s adminsecret
(Of course, we definitely need to change this account – via the oauth-clients.xml file – before shipping!)
(当然,我们肯定需要改变这个账户–通过oauth-clients.xml文件–才能发货!)
Basically, we can read this command as: “Give me a token, using client credentials with the client_id of admin and a secret of adminsecret“.
基本上,我们可以把这个命令理解为。”给我一个token,使用clientcredentials,其client_id为admin,secret为adminsecret” 。
If all goes well, we’ll see a success message:
如果一切顺利,我们会看到一个成功的消息。
Successfully fetched token via client credentials grant.
The token is stored in uaac‘s state.
该令牌被存储在uaac的状态中。
Now, operating as admin, we can register a client named webappclient with client add:
现在,作为admin操作,我们可以用client add:注册一个名为webappclient的客户端。
uaac client add webappclient -s webappclientsecret \
--name WebAppClient \
--scope resource.read,resource.write,openid,profile,email,address,phone \
--authorized_grant_types authorization_code,refresh_token,client_credentials,password \
--authorities uaa.resource \
--redirect_uri http://localhost:8081/login/oauth2/code/uaa
And also, we can register a user named appuser with user add:
还有,我们可以通过用户添加:注册一个名为appuser的用户。
uaac user add appuser -p appusersecret --emails appuser@acme.com
Next, we’ll add two groups – resource.read and resource.write – using with group add:
接下来,我们将添加两个组–resource.read和resource.write–使用with组添加:
uaac group add resource.read
uaac group add resource.write
And finally, we’ll assign these groups to appuser with member add:
最后,我们将把这些组分配给appuser,用成员添加:。
uaac member add resource.read appuser
uaac member add resource.write appuser
Phew! So, what we’ve done so far is:
吁!因此,到目前为止,我们所做的是
- Installed and configured UAA
- Installed uaac
- Added a demo client, users, and groups
So, let’s keep in mind these pieces of information and jump to the next step.
因此,让我们牢记这些信息并跳到下一步。
4. OAuth 2.0 Client
4.OAuth 2.0客户端
In this section, we’ll use Spring Boot to create an OAuth 2.0 Client application.
在本节中,我们将使用Spring Boot来创建一个OAuth 2.0客户端应用程序。
4.1. Application Setup
4.1.应用程序设置
Let’s start by accessing Spring Initializr and generating a Spring Boot web application. We choose only the Web and OAuth2 Client components:
让我们首先访问Spring Initializr并生成一个Spring Boot Web应用程序。我们只选择Web和OAuth2 Client组件。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
In this example, we’ve used version 2.1.3 of Spring Boot.
在这个例子中,我们使用了Spring Boot的版本2.1.3。
Next, we need to register our client, webappclient.
接下来,我们需要注册我们的客户端,webappclient。
Quite simply, we’ll need to give the app the client-id, client-secret, and UAA’s issuer-uri. We’ll also specify the OAuth 2.0 scopes that this client wants the user to grant to it:
很简单,我们需要给应用程序提供client-id, client-secret, 和UAA的issuer-uri。我们还将指定该客户端希望用户授予它的OAuth 2.0作用域。
#registration
spring.security.oauth2.client.registration.uaa.client-id=webappclient
spring.security.oauth2.client.registration.uaa.client-secret=webappclientsecret
spring.security.oauth2.client.registration.uaa.scope=resource.read,resource.write,openid,profile
#provider
spring.security.oauth2.client.provider.uaa.issuer-uri=http://localhost:8080/uaa/oauth/token
For more information about these properties, we can have a look at the Java docs for the registration and provider beans.
关于这些属性的更多信息,我们可以看看注册和供应商Bean的Java文档。
And since we’re already using port 8080 for UAA, let’s have this run on 8081:
既然我们已经为UAA使用了8080端口,让我们在8081上运行这个端口。
server.port=8081
4.2. Login
4.2 登录
Now if we access the /login path, we should have a list of all registered clients. In our case, we have only one registered client:
现在如果我们访问/login路径,我们应该有一个所有注册客户的列表。在我们的例子中,我们只有一个注册客户。
Clicking on the link will redirect us to the UAA login page:
Here, let’s login with appuser/appusersecret.
这里,让我们用appuser/appusersecret登录。
Submitting the form should redirect us to an approval form where the user can authorize or deny access to our client:
提交表格应将我们重定向到一个批准表格,用户可以授权或拒绝对我们的客户进行访问:。
The user can then grant which privileges she wants. For our purposes, we’ll select everything except resource:write.
然后用户可以授予她想要的权限。就我们的目的而言,我们将选择所有资源:写除外。。
Whatever the user checks will be the scopes in the resulting access token.
无论用户检查什么,都将是所产生的访问令牌中的作用域。
To prove this, we can copy the token shown at the index path, http://localhost:8081, and decode it using the JWT debugger. We should see the scopes we checked on the approval page:
为了证明这一点,我们可以复制在索引路径显示的标记,http://localhost:8081,并使用JWT调试器对其进行解码。我们应该看到我们在批准页面上检查的范围:。
{
"jti": "f228d8d7486942089ff7b892c796d3ac",
"sub": "0e6101d8-d14b-49c5-8c33-fc12d8d1cc7d",
"scope": [
"resource.read",
"openid",
"profile"
],
"client_id": "webappclient"
// more claims
}
Once our client application receives this token, it can authenticate the user and they’ll have access to the app.
一旦我们的客户端应用程序收到这个令牌,它就可以验证用户,他们就可以访问该应用程序。
Now, an app that doesn’t show any data isn’t very useful, so our next step will be to stand up a resource server – which has the user’s data – and connect the client to it.
现在,一个不显示任何数据的应用程序不是很有用,所以我们的下一步将是建立一个资源服务器–它有用户的数据–并将客户端连接到它。
The completed resource server will have two protected APIs: one that requires the resource.read scope and another that requires resource.write.
完成的资源服务器将有两个受保护的API:一个需要resource.read范围,另一个需要resource.write。
What we’ll see is that the client, using the scopes we granted, will be able to call the read API but not write.
我们将看到的是,客户端使用我们授予的作用域,将能够调用读API,但不能调用写。。
5. Resource Server
5.资源服务器
The resource server hosts the user’s protected resources.
资源服务器承载着用户的受保护资源。。
It authenticates clients via the Authorization header and in consultation with an authorization server – in our case, that’s UAA.
它通过Authorization头并与授权服务器协商,对客户进行认证–在我们的案例中,就是UAA。
5.1. Application Set Up
5.1.应用程序的设置
To create our resource server, we’ll use Spring Initializr again to generate a Spring Boot web application. This time, we’ll choose the Web and OAuth2 Resource Server components:
为了创建我们的资源服务器,我们将再次使用Spring Initializr来生成一个Spring Boot Web应用程序。这一次,我们将选择Web和OAuth2资源服务器组件。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
As with the Client application, we’re using the version 2.1.3 of Spring Boot.
与客户端应用程序一样,我们使用的是Spring Boot的2.1.3版本。
The next step is to indicate the location of the running CF UAA in the application.properties file:
下一步是在application.properties文件中指明运行中的CF UAA的位置:。
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/uaa/oauth/token
Of course, let’s pick a new port here, too. 8082 will work fine:
当然,我们在这里也选一个新的端口吧。8082就可以了。
server.port=8082
And that’s it! We should have a working resource server and by default, all requests will require a valid access token in the Authorization header.
就这样了!我们应该有一个工作的资源服务器,默认情况下,所有请求都需要在Authorization 头中有一个有效的访问令牌。
5.2. Protecting Resource Server APIs
5.2.保护资源服务器的API
Next, let’s add some endpoints worth protecting, though.
接下来,让我们添加一些值得保护的端点,不过。
We’ll add a RestController with two endpoints, one authorized for users having the resource.read scope and the other for users having the resource.write scope:
我们将添加一个RestController,有两个端点,一个授权给有resource.read范围的用户,另一个授权给有resource.write范围的用户:。
@GetMapping("/read")
public String read(Principal principal) {
return "Hello write: " + principal.getName();
}
@GetMapping("/write")
public String write(Principal principal) {
return "Hello write: " + principal.getName();
}
Next, we’ll override the default Spring Boot configuration to protect the two resources:
接下来,我们将覆盖默认的Spring Boot配置以保护这两种资源。
@EnableWebSecurity
public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/read/**").hasAuthority("SCOPE_resource.read")
.antMatchers("/write/**").hasAuthority("SCOPE_resource.write")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
Note that the scopes supplied in the access token are prefixed with SCOPE_ when they are translated to a Spring Security GrantedAuthority.
注意访问令牌中提供的作用域在翻译成Spring Security GrantedAuthority时,会以SCOPE_为前缀。
5.3. Requesting a Protected Resource From a Client
5.3.从客户处请求受保护的资源
From the Client application, we’ll call the two protected resources using RestTemplate. Before making the request, we retrieve the access token from the context and add it to the Authorization header:
在客户端应用程序中,我们将使用RestTemplate调用这两个受保护的资源。在发出请求之前,我们从上下文中检索访问令牌,并将其添加到授权头:。
private String callResourceServer(OAuth2AuthenticationToken authenticationToken, String url) {
OAuth2AuthorizedClient oAuth2AuthorizedClient = this.authorizedClientService.
loadAuthorizedClient(authenticationToken.getAuthorizedClientRegistrationId(),
authenticationToken.getName());
OAuth2AccessToken oAuth2AccessToken = oAuth2AuthorizedClient.getAccessToken();
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + oAuth2AccessToken.getTokenValue());
// call resource endpoint
return response;
}
Note, though, that we can remove this boilerplate if we use WebClient instead of RestTemplate.
不过请注意,如果我们使用WebClient而不是RestTemplate,我们可以删除这个模板。
Then, we’ll add two calls to the resource server endpoints:
然后,我们将添加两个对资源服务器端点的调用。
@GetMapping("/read")
public String read(OAuth2AuthenticationToken authenticationToken) {
String url = remoteResourceServer + "/read";
return callResourceServer(authenticationToken, url);
}
@GetMapping("/write")
public String write(OAuth2AuthenticationToken authenticationToken) {
String url = remoteResourceServer + "/write";
return callResourceServer(authenticationToken, url);
}
As expected, the call of the /read API will succeed, but not the /write one. The HTTP status 403 tells us that the user is not authorized.
正如预期的那样,对/读 API的调用将成功,但对/写的调用却不成功。 HTTP状态403告诉我们,用户没有被授权。
6. Conclusion
6.结论
In this article, we started with a brief overview of the OAuth 2.0 as it’s the base foundation for UAA, an OAuth 2.0 Authorization Server. Then, we configured it for issuing access tokens for a client and securing a resource server application.
在这篇文章中,我们首先简要介绍了OAuth 2.0,因为它是UAA的基础,一个OAuth 2.0授权服务器。然后,我们对它进行了配置,以便为客户端发放访问令牌,并确保资源服务器应用程序的安全。
The full source code for the examples is available over on Github.
例子的完整源代码可在Github上获得。