Cloud Foundry UAA快速指南

2025/04/16

1. 概述

Cloud Foundry用户帐户和身份验证(CF UAA)是一种身份管理和授权服务;更准确地说,它是一个OAuth 2.0提供程序,允许对客户端应用程序进行身份验证和颁发令牌。

在本教程中,我们将介绍设置CF UAA服务器的基础知识。然后,我们将了解如何使用它来保护资源服务器应用程序。

但在此之前,让我们先明确一下UAA在OAuth 2.0授权框架中的作用。

2. Cloud Foundry UAA和OAuth 2.0

让我们首先了解UAA与OAuth 2.0规范的关系。

OAuth 2.0规范定义了四个可以相互连接的参与者:资源所有者、资源服务器、客户端和授权服务器。

作为OAuth 2.0提供商,UAA扮演授权服务器的角色。这意味着它的主要目标是为客户端应用程序颁发访问令牌,并为资源服务器验证这些令牌

为了允许这些参与者进行交互,我们首先需要设置一个UAA服务器,然后再实现两个应用程序:一个作为客户端,另一个作为资源服务器。

我们将在客户端使用authorization_code授权流程,我们将在资源服务器中使用Bearer token授权。为了更安全、更高效的握手,我们将使用签名的JWT作为访问令牌

3. 设置UAA服务器

首先,我们将安装UAA并填充一些演示数据

安装完成后,我们将注册一个名为webappclient的客户端应用程序。然后,我们将创建一个名为appuser的用户,该用户具有两个角色:resource.read和resource.write。

3.1 安装

UAA是一个Java Web应用程序,可以在任何兼容的Servlet容器中运行,在本教程中,我们将使用Tomcat

让我们下载UAA war并将其存入我们的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

不过,在启动它之前,我们需要配置它的数据源和JWS密钥对。

3.2 所需配置

默认情况下,UAA从其类路径上的uaa.yml读取配置。但是,由于我们刚刚下载了war文件,因此最好告诉UAA文件系统上的自定义位置。

我们可以通过设置UAA_CONFIG_PATH属性来实现这一点:

export UAA_CONFIG_PATH=~/.uaa

或者,我们可以设置CLOUD_FOUNDRY_CONFIG_PATH。或者,我们可以使用UAA_CONFIG_URL指定远程位置。

然后,我们可以将UAA所需的配置复制到我们的配置路径中:

wget -qO- https://raw.githubusercontent.com/cloudfoundry/uaa/4.27.0/uaa/src/main/resources/required_configuration.yml \
  > $UAA_CONFIG_PATH/uaa.yml

请注意,我们删除最后三行,因为我们马上要替换它们。

3.3 配置数据源

因此,让我们配置数据源,UAA将在其中存储有关客户端的信息

出于本教程的目的,我们将使用HSQLDB:

export SPRING_PROFILES="default,hsqldb"

当然,由于这是一个Spring Boot应用程序,我们也可以在uaa.yml中将其指定为spring.profiles属性。

3.4 配置JWS密钥对

由于我们使用JWT,UAA需要有一个私钥来签署UAA颁发的每个JWT

OpenSSL使这变得简单:

openssl genrsa -out signingkey.pem 2048
openssl rsa -in signingkey.pem -pubout -out verificationkey.pem

授权服务器将使用私钥对JWT进行签名,我们的客户端和资源服务器将使用公钥验证该签名。

我们将它们导出到JWT_TOKEN_SIGNING_KEY和JWT_TOKEN_VERIFICATION_KEY:

export JWT_TOKEN_SIGNING_KEY=$(cat signingkey.pem)
export JWT_TOKEN_VERIFICATION_KEY=$(cat verificationkey.pem)

同样,我们可以通过jwt.token.signing-key和jwt.token.verification-key属性在uaa.yml中指定这些。

3.5 启动UAA

最后,我们启动UAA:

$CATALINA_HOME/bin/catalina.sh run

此时,我们应该有一个可用的UAA服务器,网址为http://localhost:8080/uaa

如果我们访问http://localhost:8080/uaa/info,我们将看到一些基本的启动信息

3.6 安装UAA命令行客户端

CF UAA命令行客户端是管理UAA的主要工具,但要使用它,我们需要先安装Ruby

sudo apt install rubygems
gem install cf-uaac

然后,我们可以配置uaac以指向我们正在运行的UAA实例:

uaac target http://localhost:8080/uaa

请注意,如果我们不想使用命令行客户端,也可以使用UAA的HTTP客户端。

3.7 使用UAAC填充客户端和用户

现在我们已经安装了uaac,让我们用一些演示数据填充UAA。至少,我们需要:一个客户端、一个用户以及resource.read和resource.write组

因此,要进行任何管理,我们都需要自行进行身份验证。我们将选择UAA附带的默认管理员,该管理员具有创建其他客户端、用户和组的权限

uaac token client get admin -s adminsecret

(当然,在发货前我们肯定需要通过oauth-clients.xml文件更改这个帐户。)

基本上,我们可以将此命令理解为:“给我一个token,使用client凭据,其中client_id为admin,密钥为adminsecret”。

如果一切顺利,我们将看到一条成功消息:

Successfully fetched token via client credentials grant.

该令牌存储在uaac的状态中。

现在,以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

另外,我们可以使用user add注册一个名为appuser的用户

uaac user add appuser -p appusersecret --emails appuser@acme.com

接下来,我们将使用group add添加两个组-resource.read和resource.write:

uaac group add resource.read
uaac group add resource.write

最后,我们通过member add将这些组分配给appuser:

uaac member add resource.read appuser
uaac member add resource.write appuser

到目前为止,我们所做的是:

  • 安装并配置UAA
  • 安装uaac
  • 添加示例客户端、用户和组
  • 4. OAuth 2.0客户端

在本节中,我们将使用Spring Boot创建一个OAuth 2.0客户端应用程序

4.1 应用程序设置

让我们首先访问Spring Initializr并生成一个Spring Boot Web应用程序,我们仅选择Web和OAuth2客户端组件:

<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>

在此示例中,我们使用了Spring Boot 3.1.5版本

接下来,我们需要注册我们的客户端webappclient

很简单,我们需要为应用程序提供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

有关这些属性的更多信息,我们可以查看注册提供程序Bean的Java文档。

由于我们已经将端口8080用于UAA,因此让我们在8081上运行它:

server.port=8081

4.2 登录

现在,如果我们访问/login路径,我们应该有一个所有已注册客户端的列表。在我们的例子中,我们只有一个已注册客户端:

点击链接将会重定向到UAA登录页面

在这里,让我们使用appuser/appusersecret登录。

提交表单后,我们将会重定向到批准表单,用户可以在其中授权或拒绝我们客户端的访问

然后,用户可以授予所需的权限。为了方便我们操作,我们将选择除resource:write之外的所有内容

用户检查的任何内容都将是结果访问令牌中的范围。

为了证明这一点,我们可以复制index路径http://localhost:8081中显示的令牌,并使用JWT调试器对其进行解码,我们应该在批准页面上看到我们检查的范围

{
    "jti": "f228d8d7486942089ff7b892c796d3ac",
    "sub": "0e6101d8-d14b-49c5-8c33-fc12d8d1cc7d",
    "scope": [
        "resource.read",
        "openid",
        "profile"
    ],
    "client_id": "webappclient"
    // more claims
}

一旦我们的客户端应用程序收到此令牌,它就可以对用户进行身份验证,并且用户将可以访问该应用程序。

现在,一个不显示任何数据的应用程序并不是很有用,所以我们的下一步将是建立一个资源服务器,它拥有用户的数据并将客户端连接到它。

完整的资源服务器将有两个受保护的API:一个需要resource.read范围,另一个需要resource.write。

我们将看到,客户端使用我们授予的范围将能够调用read API,但不能调用write

5. 资源服务器

资源服务器托管用户的受保护资源

它通过Authorization标头并与授权服务器(在我们的例子中是UAA)协商来验证客户端。

5.1 应用程序设置

为了创建资源服务器,我们将再次使用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>

与客户端应用程序一样,我们使用Spring Boot 3.1.5版本。

下一步是在application.properties文件中指示正在运行的CF UAA的位置

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/uaa/oauth/token

当然,我们在这里也选择一个新端口,8082就可以了:

server.port=8082

就这样,我们应该有一个可用的资源服务器,并且默认情况下,所有请求都需要在Authorization标头中提供有效的访问令牌。

5.2 保护资源服务器API

接下来,让我们添加一些值得保护的端点。

我们将添加一个具有两个端点的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();
}

接下来,我们将覆盖默认的Spring Boot配置来保护这两个资源

@EnableWebSecurity
public class CFUAAOAuth2ResourceServerSecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
                        .requestMatchers("/read/**")
                        .hasAuthority("SCOPE_resource.read")
                        .requestMatchers("/write/**")
                        .hasAuthority("SCOPE_resource.write")
                        .anyRequest()
                        .authenticated())
                .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
        return http.build();
    }
}

请注意,当访问令牌中提供的范围转换为Spring Security GrantedAuthority时,它们会以SCOPE_为前缀

5.3 从客户端请求受保护的资源

从客户端应用程序中,我们将使用RestTemplate调用两个受保护的资源,在发出请求之前,我们从上下文中检索访问令牌并将其添加到Authorization标头中:

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;
}

但请注意,如果我们使用WebClient而不是RestTemplate,我们可以删除这个样板。

然后,我们将向资源服务器端点添加两个调用:

@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);
}

正如预期的那样,/read API的调用将成功,但/write API的调用则不会成功,HTTP状态403告诉我们用户未获得授权。

6. 总结

在本文中,我们首先简要概述了OAuth 2.0,因为它是OAuth 2.0授权服务器UAA的基础。然后,我们对其进行了配置,以便为客户端颁发访问令牌并保护资源服务器应用程序。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章