通过 Spring Boot Keycloak 集成的 OAuth2 客户端凭据流

我的申请包括:

  • 后端/资源服务器

  • 网络应用程序用户界面

  • 钥匙斗篷

UI 使用具有授权代码授权流程的 keycloak 客户端通过 RESTful API 与后端服务器进行通信。这工作正常。

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

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

编辑:解决方案

感谢您的回答对我帮助很大。在我的 UI Web 应用程序中,我现在可以通过使用经过身份验证的用户 OAuth2 令牌或使用我的 UI 服务帐户的客户端凭据流中的令牌与后端进行通信。每种方式都有自己的方式RestTemplate,第一种是通过 keycloak 集成完成的,第二种是由 Spring Security OAuth2 完成的。


月关宝盒
浏览 180回答 3
3回答

湖上湖

是的,您可以使用 OAuth 2.0 客户端凭据流程和服务帐户。Keycloak 建议使用 3 种方法来保护 SpringBoot REST 服务的安全:带有 Keycloak Spring Boot 适配器带 keycloak Spring 安全适配器与 OAuth2 / OpenID 连接请注意将您的客户端配置为:访问类型:机密授权:已启用服务帐户(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-servicesecurity.oauth2.client.client-secret=68977d81-c59b-49aa-aada-58da9a43a850security.oauth2.client.user-authorization-uri=${rest.security.issuer-uri}/protocol/openid-connect/authsecurity.oauth2.client.access-token-uri=${rest.security.issuer-uri}/protocol/openid-connect/tokensecurity.oauth2.client.scope=openidsecurity.oauth2.client.grant-type=client_credentialsJwtAccessTokenCustomizer像 Arun 的示例一样实现您的SecurityConfigurer (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());  }}

眼眸繁星

按照@dmitri-algazin 实施工作流程,您基本上有两个选择:如果您想涵盖除 Keycloak 之外的其他 IdM,它以某种方式解决了单一责任原则,我会使用RestTemplate. 您可以在下面找到变量:&nbsp; &nbsp; //Constants&nbsp; &nbsp; @Value("${keycloak.url}")&nbsp; &nbsp; private String keycloakUrl;&nbsp; &nbsp; @Value("${keycloak.realm}")&nbsp; &nbsp; private String keycloakRealm;&nbsp; &nbsp; @Value("${keycloak.client_id}")&nbsp; &nbsp; private String keycloakClientId;&nbsp; &nbsp; RestTemplate restTemplate = new RestTemplate();&nbsp; &nbsp; private static final String BEARER = "BEARER ";首先,您需要生成访问令牌:&nbsp; &nbsp; @Override&nbsp; &nbsp; public AccessTokenResponse login(KeycloakUser user) throws NotAuthorizedException {&nbsp; &nbsp; &nbsp; &nbsp; try {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; String uri = keycloakUrl + "/realms/" + keycloakRealm +&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "/protocol/openid-connect/token";&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; String data = "grant_type=password&username="+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; user.getUsername()+"&password="+user.getPassword()+"&client_id="+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; keycloakClientId;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; HttpHeaders headers = new HttpHeaders();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; headers.set("Content-Type", "application/x-www-form-urlencoded");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; HttpEntity<String> entity = new HttpEntity<String>(data, headers);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ResponseEntity<AccessTokenResponse> response = restTemplate.exchange(uri,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; HttpMethod.POST, entity, AccessTokenResponse.class);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (response.getStatusCode().value() != HttpStatus.SC_OK) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.error("Unauthorised access to protected resource", response.getStatusCode().value());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new NotAuthorizedException("Unauthorised access to protected resource");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return response.getBody();&nbsp; &nbsp; &nbsp; &nbsp; } catch (Exception ex) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.error("Unauthorised access to protected resource", ex);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new NotAuthorizedException("Unauthorised access to protected resource");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;&nbsp; &nbsp; }然后使用令牌,您可以从用户那里检索信息:&nbsp; &nbsp; @Override&nbsp; &nbsp; public String user(String authToken) throws NotAuthorizedException {&nbsp; &nbsp; &nbsp; &nbsp; if (! authToken.toUpperCase().startsWith(BEARER)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new NotAuthorizedException("Invalid OAuth Header. Missing Bearer prefix");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; HttpHeaders headers = new HttpHeaders();&nbsp; &nbsp; &nbsp; &nbsp; headers.set("Authorization", authToken);&nbsp; &nbsp; &nbsp; &nbsp; HttpEntity<String> entity = new HttpEntity<>(headers);&nbsp; &nbsp; &nbsp; &nbsp; ResponseEntity<AccessToken> response = restTemplate.exchange(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; keycloakUrl + "/realms/" + keycloakRealm + "/protocol/openid-connect/userinfo",&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; HttpMethod.POST,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; entity,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AccessToken.class);&nbsp; &nbsp; &nbsp; &nbsp; if (response.getStatusCode().value() != HttpStatus.SC_OK) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.error("OAuth2 Authentication failure. "&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + "Invalid OAuth Token supplied in Authorization Header on Request. Code {}", response.getStatusCode().value());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new NotAuthorizedException("OAuth2 Authentication failure. "&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + "Invalid OAuth Token supplied in Authorization Header on Request.");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; log.debug("User info: {}", response.getBody().getPreferredUsername());&nbsp; &nbsp; &nbsp; &nbsp; return response.getBody().getPreferredUsername();&nbsp; &nbsp; }您可以将此 URL 替换为 @dimitri-algazin 提供的 URL,以检索所有用户信息。可以使用 Keycloak 依赖项:&nbsp; &nbsp; &nbsp; &nbsp; <!-- keycloak -->&nbsp; &nbsp; &nbsp; &nbsp; <dependency>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <groupId>org.keycloak</groupId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <artifactId>keycloak-admin-client</artifactId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <version>3.4.3.Final</version>&nbsp; &nbsp; &nbsp; &nbsp; </dependency>&nbsp; &nbsp; &nbsp; &nbsp; <dependency>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <groupId>org.jboss.resteasy</groupId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <artifactId>resteasy-client</artifactId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <version>3.1.4.Final</version>&nbsp; &nbsp; &nbsp; &nbsp; </dependency>并使用类来生成令牌:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Keycloak keycloak = KeycloakBuilder&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .builder()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .serverUrl(keycloakUrl)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .realm(keycloakRealm)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .username(user.getUsername())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .password(user.getPassword())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .clientId(keycloakClientId)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .build();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return keycloak.tokenManager().getAccessToken();示例摘自此处。我们还将镜像上传到 Docker Hub,以方便与 Keycloak 交互。因此我们从选项 2) 开始。目前,我们正在覆盖其他 IdM,我们选择了选项 1),以避免包含额外的依赖项。结论:如果您坚持使用 Keycloak,我会选择选项 2 ,因为类包含 Keycloak 工具的额外功能。我会选择选项 1以获得更多的覆盖范围和其他 OAuth 2.0 工具。

潇潇雨雨

我们有类似的需求,通过用户 uuid 获取用户电子邮件。创建服务用户,确保用户具有“领域管理”->“查看用户”角色(也可能是查询用户)过程很简单:使用服务用户登录 keycloak(将密码和/或用户名编码在属性文件中),使用授权标头中的 accessToken 向 keycloak 发出请求获取 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 和参数,总是具有挑战性)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java