繁体   English   中英

OAuth2客户端凭据通过Spring Boot Keycloak集成进行流动

[英]OAuth2 client credentials flow via Spring Boot Keycloak integration

我的应用程序包括:

  • 后端/资源服务器
  • UI Webapp
  • 钥匙斗篷

UI正在使用具有授权码授予流程的keycloak客户端通过RESTful API与后端服务器进行通信。 一切正常。

现在,我需要使用系统/服务帐户(通常具有比用户更多的权限)访问后端资源的其他可能性。 您将如何实施此要求? 我认为客户端凭据流在这里很有用。

是否可以将OAuth2客户端凭据流与用于keyboot客户端的Spring Boot一起使用? 我发现了一些示例,这些示例使用Spring Security OAuth2客户端功能来实现客户端凭证流,但是这感觉很奇怪,因为我已经将keycloak客户端用于OAuth了。

编辑:解决方案

感谢您的回答,这对我很有帮助。 现在,在我的UI Web应用程序中,我可以通过使用经过身份验证的用户OAuth2令牌或使用我的UI服务帐户的客户端凭据流中的令牌与后端进行通信。 每种方法都有自己的RestTemplate ,首先是通过整合keycloak完成,第二个是Spring Security中的OAuth2作为解释做

是的,您可以使用OAuth 2.0客户端凭据流和服务帐户。

Keycloak建议了三种保护SpringBoot REST服务的方法:

  1. 带有Keycloak Spring Boot适配器
  2. 带有钥匙罩Spring Security适配器
  3. 与OAuth2 / OpenID Connect

下面以OAuth2 / OIDC方式的示例对此进行了很好的解释:

如果您遵循此示例,请记住:

注意将客户端配置为:

  • 访问类型:机密
  • 授权:已启用
  • 服务帐户(OAuth客户端凭据流程):已启用

注意将目标服务配置为:

  • 访问类型:仅承载

因此,呼叫者应该是confidential ,目标服务应该是bearer-only

创建您的用户,角色,映射器...,并将角色分配给您的用户。

检查您的spring项目中是否具有此依赖项:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security.oauth.boot</groupId>
  <artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>

配置要在REST客户端(application.properties)中使用的身份验证,例如:

security.oauth2.client.client-id=employee-service
security.oauth2.client.client-secret=68977d81-c59b-49aa-aada-58da9a43a850
security.oauth2.client.user-authorization-uri=${rest.security.issuer-uri}/protocol/openid-connect/auth
security.oauth2.client.access-token-uri=${rest.security.issuer-uri}/protocol/openid-connect/token
security.oauth2.client.scope=openid
security.oauth2.client.grant-type=client_credentials

像Arun的示例一样,实现JwtAccessTokenCustomizerSecurityConfigurer (ResourceServerConfigurerAdapter)。

最后实现您的服务控制器:

@RestController
@RequestMapping("/api/v1/employees")
public class EmployeeRestController {

  @GetMapping(path = "/username")
  @PreAuthorize("hasAnyAuthority('ROLE_USER')")
  public ResponseEntity<String> getAuthorizedUserName() {
    return ResponseEntity.ok(SecurityContextUtils.getUserName());
  }

  @GetMapping(path = "/roles")
  @PreAuthorize("hasAnyAuthority('ROLE_USER')")
  public ResponseEntity<Set<String>> getAuthorizedUserRoles() {
    return ResponseEntity.ok(SecurityContextUtils.getUserRoles());
  }
}

有关完整的教程,请阅读引用的Arun教程。

希望能帮助到你。

在@ dmitri-algazin之后,您可以实现工作流,基本上有两个选择:

  1. 如果您想涵盖Keycloak之外的其他IdM(可以通过某种方式解决单一职责原则) ,则可以使用RestTemplate 在下面可以找到变量:
    //Constants
    @Value("${keycloak.url}")
    private String keycloakUrl;

    @Value("${keycloak.realm}")
    private String keycloakRealm;

    @Value("${keycloak.client_id}")
    private String keycloakClientId;

    RestTemplate restTemplate = new RestTemplate();
    private static final String BEARER = "BEARER ";

首先,您需要生成访问令牌:

    @Override
    public AccessTokenResponse login(KeycloakUser user) throws NotAuthorizedException {
        try {
            String uri = keycloakUrl + "/realms/" + keycloakRealm + 
                    "/protocol/openid-connect/token";
            String data = "grant_type=password&username="+
                    user.getUsername()+"&password="+user.getPassword()+"&client_id="+
                    keycloakClientId;

            HttpHeaders headers = new HttpHeaders();
            headers.set("Content-Type", "application/x-www-form-urlencoded");

            HttpEntity<String> entity = new HttpEntity<String>(data, headers);
            ResponseEntity<AccessTokenResponse> response = restTemplate.exchange(uri, 
                    HttpMethod.POST, entity, AccessTokenResponse.class);            

            if (response.getStatusCode().value() != HttpStatus.SC_OK) {
                log.error("Unauthorised access to protected resource", response.getStatusCode().value());
                throw new NotAuthorizedException("Unauthorised access to protected resource");
            }
            return response.getBody();
        } catch (Exception ex) {
            log.error("Unauthorised access to protected resource", ex);
            throw new NotAuthorizedException("Unauthorised access to protected resource");
        } 
    }

然后,使用令牌可以从用户检索信息:

    @Override
    public String user(String authToken) throws NotAuthorizedException {

        if (! authToken.toUpperCase().startsWith(BEARER)) {
            throw new NotAuthorizedException("Invalid OAuth Header. Missing Bearer prefix");
        }

        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", authToken);

        HttpEntity<String> entity = new HttpEntity<>(headers);

        ResponseEntity<AccessToken> response = restTemplate.exchange(
                keycloakUrl + "/realms/" + keycloakRealm + "/protocol/openid-connect/userinfo", 
                HttpMethod.POST, 
                entity, 
                AccessToken.class);

        if (response.getStatusCode().value() != HttpStatus.SC_OK) {
            log.error("OAuth2 Authentication failure. "
                    + "Invalid OAuth Token supplied in Authorization Header on Request. Code {}", response.getStatusCode().value());
            throw new NotAuthorizedException("OAuth2 Authentication failure. "
                    + "Invalid OAuth Token supplied in Authorization Header on Request.");
        }

        log.debug("User info: {}", response.getBody().getPreferredUsername());
        return response.getBody().getPreferredUsername();
    }

您可以将该URL替换为@ dimitri-algazin提供的URL,以检索所有用户信息。

  1. 可以使用Keycloak依赖项:
        <!-- keycloak -->
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-admin-client</artifactId>
            <version>3.4.3.Final</version>
        </dependency>

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-client</artifactId>
            <version>3.1.4.Final</version>
        </dependency>

并使用这些类来生成令牌:

            Keycloak keycloak = KeycloakBuilder
                    .builder()
                    .serverUrl(keycloakUrl)
                    .realm(keycloakRealm)
                    .username(user.getUsername())
                    .password(user.getPassword())
                    .clientId(keycloakClientId)
                    .resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build())
                    .build();

            return keycloak.tokenManager().getAccessToken();

例子摘自这里 我们还将映像上传到Docker Hub,以促进与Keycloak的交互。 因此,我们从选项2)开始。 目前,我们正在涵盖其他IdM,我们选择了选项1),以避免包括额外的依赖项。 结论:

如果您坚持使用Keycloak,我会选择选项2 ,因为类包含Keycloak工具的额外功能。 我会选择选项1进行进一步介绍,并使用其他OAuth 2.0工具。

我们有类似的要求,请通过用户uuid获取用户电子邮件。

创建服务用户,确保该用户具有角色“领域管理”->“视图用户”(也可以是查询用户)

过程很简单:使用服务用户登录密钥库(保留属性文件中编码的密码和/或用户名),使用授权标头中的accessToken来请求密钥库到

GET http:// {yourdomainadress} / auth / admin / realms / {yourrealmname} / users / {userId}

一种使用REST API登录到keycloak的方法:

POST http:// {yourdomainadress} / auth / realms / {yourrealmname} / protocol / openid-connect / token

标头:

内容类型:application / x-www-form-urlencoded

正文x-www-form-urlencoded:

client_id:您的客户

用户名:您正在使用的用户

密码:用户密码

grant_type:密码

client_secret:11112222-3333-4444-5555-666666666666(如果客户端“访问类型” =“机密”,则需要客户端密码)

简短地说 :请确保您的服务用户被分配了正确的角色来执行操作,登录,查询keycloak(检查文档以获取正确的查询URL和参数,这总是很困难)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM