Teo*_*Teo 8 spring authorization spring-security spring-boot keycloak
我正在使用keycloak 3.4并spring boot开发一个网络应用程序.我正在使用Active Directory作为用户Federeation来检索所有用户信息.
但是要在我的网络应用程序中使用这些信息,我想我必须将它们保存在"local-webapp"数据库中.
因此,在用户登录后,如何将其保存在我的数据库中?
我正在考虑这样的场景:"我有一个对象A,它引用了用户B,因此我必须在它们之间建立关系.所以我添加了一个外键."
在这种情况下,我需要让我的数据库上的用户.没有?
编辑
为了避免保存我的数据库上的所有用户,我正在尝试使用管理员API,因此我在控制器中添加了以下代码.
我还创建了另一个调用Test所有用户的客户端,这样我可以使用client-id和client-secret.或者有没有办法JWT使用admin api?
客户端:
Keycloak keycloak2 = KeycloakBuilder.builder()
.serverUrl("http://localhost:8080/auth/admin/realms/MYREALM/users")
.realm("MYREALMM")
.username("u.user")
.password("password")
.clientId("Test")
.clientSecret("cade3034-6ee1-4b18-8627-2df9a315cf3d")
.resteasyClient(new ResteasyClientBuilder().connectionPoolSize(20).build())
.build();
RealmRepresentation realm2 = keycloak2.realm("MYREALMM").toRepresentation();
Run Code Online (Sandbox Code Playgroud)
错误是:
2018-02-05 12:33:06.638 ERROR 16975 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.Error: Unresolved compilation problem:
The method realm(String) is undefined for the type AccessTokenResponse
] with root cause
java.lang.Error: Unresolved compilation problem:
The method realm(String) is undefined for the type AccessTokenResponse
Run Code Online (Sandbox Code Playgroud)
我哪里做错了?
编辑2
我也试过这个:
@Autowired
private HttpServletRequest request;
public ResponseEntity listUsers() {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) request.getUserPrincipal();
KeycloakPrincipal principal=(KeycloakPrincipal)token.getPrincipal();
KeycloakSecurityContext session = principal.getKeycloakSecurityContext();
Keycloak keycloak = KeycloakBuilder.builder()
.serverUrl("http://localhost:8080/auth")
.realm("MYREALMM")
.authorization(session.getToken().getAuthorization().toString())
.resteasyClient(new ResteasyClientBuilder().connectionPoolSize(20).build())
.build();
RealmResource r = keycloak.realm("MYREALMM");
List<org.keycloak.representations.idm.UserRepresentation> list = keycloak.realm("MYREALMM").users().list();
return ResponseEntity.ok(list);
Run Code Online (Sandbox Code Playgroud)
但授权总是如此null.为什么?
编辑3 以下您可以找到我的spring安全配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.httpBasic().disable();
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.permitAll()
.logoutSuccessUrl("/")
.invalidateHttpSession(true);
}
@Autowired
public KeycloakClientRequestFactory keycloakClientRequestFactory;
@Bean
public KeycloakRestTemplate keycloakRestTemplate() {
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
simpleAuthorityMapper.setPrefix("ROLE_");
simpleAuthorityMapper.setConvertToUpperCase(true);
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(simpleAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**", "/webjars/**");
}
@Bean
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public AccessToken accessToken() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return ((KeycloakSecurityContext) ((KeycloakAuthenticationToken) request.getUserPrincipal()).getCredentials()).getToken();
}
}
Run Code Online (Sandbox Code Playgroud)
编辑4
这些是里面的属性 applicatoin.properties
#######################################
# KEYCLOAK #
#######################################
keycloak.auth-server-url=http://localhost:8181/auth
keycloak.realm=My Realm
keycloak.ssl-required=external
keycloak.resource=AuthServer
keycloak.credentials.jwt.client-key-password=keystorePwd
keycloak.credentials.jwt.client-keystore-file=keystore.jks
keycloak.credentials.jwt.client-keystore-password=keystorePwd
keycloak.credentials.jwt.alias=AuthServer
keycloak.credentials.jwt.token-expiration=10
keycloak.credentials.jwt.client-keystore-type=JKS
keycloak.use-resource-role-mappings=true
keycloak.confidential-port=0
keycloak.principal-attribute=preferred_username
Run Code Online (Sandbox Code Playgroud)
编辑5.
编辑6
这是启用日志记录后的日志形式keycloak:
2018-02-12 08:31:00.274 3DEBUG 5802 --- [nio-8080-exec-1] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8080/utente/prova4
2018-02-12 08:31:00.274 3DEBUG 5802 --- [nio-8080-exec-1] .k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /utente/prova4
2018-02-12 08:31:00.274 3DEBUG 5802 --- [nio-8080-exec-1] o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:8080/utente/prova4
2018-02-12 08:31:00.274 3DEBUG 5802 --- [nio-8080-exec-1] o.k.a.AuthenticatedActionsHandler : Policy enforcement is disabled.
2018-02-12 08:31:00.275 3DEBUG 5802 --- [nio-8080-exec-1] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8080/utente/prova4
2018-02-12 08:31:00.275 3DEBUG 5802 --- [nio-8080-exec-1] o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:8080/utente/prova4
2018-02-12 08:31:00.275 3DEBUG 5802 --- [nio-8080-exec-1] o.k.a.AuthenticatedActionsHandler : Policy enforcement is disabled.
2018-02-12 08:31:00.276 3DEBUG 5802 --- [nio-8080-exec-1] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8080/utente/prova4
2018-02-12 08:31:00.276 3DEBUG 5802 --- [nio-8080-exec-1] o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:8080/utente/prova4
2018-02-12 08:31:00.276 3DEBUG 5802 --- [nio-8080-exec-1] o.k.a.AuthenticatedActionsHandler : Policy enforcement is disabled.
2018-02-12 08:31:10.580 3DEBUG 5802 --- [nio-8080-exec-1] o.k.a.s.client.KeycloakRestTemplate : Created GET request for "http://localhost:8181/auth/admin/realms/My%20Realm%20name/users"
2018-02-12 08:31:10.580 3DEBUG 5802 --- [nio-8080-exec-1] o.k.a.s.client.KeycloakRestTemplate : Setting request Accept header to [application/json, application/*+json]
2018-02-12 08:31:10.592 3DEBUG 5802 --- [nio-8080-exec-1] o.k.a.s.client.KeycloakRestTemplate : GET request for "http://localhost:8181/auth/admin/realms/My%20Realm%20name/users" resulted in 401 (Unauthorized); invoking error handler
2018-02-12 08:31:10.595 ERROR 5802 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 401 Unauthorized] with root cause
org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:85) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:707) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
Run Code Online (Sandbox Code Playgroud)
Xtr*_*ica 11
为了访问整个用户列表,您必须验证记录的用户是否至少包含客户端的view-users角色realm-management,请参阅我之前写的这个答案.一旦用户拥有这个角色,她检索的JWT就会将其汇总.
正如我可以从你的评论中推断出来的那样,你似乎缺乏关于Authorization标题的一些基础.一旦用户登录,她就会从keycloak获得签名的JWT,因此该领域的每个客户都可以信任它,而无需询问Keycloak.此JWT包含访问令牌,后者在Authorization每个用户请求的标头中都需要,前缀为Bearer关键字(请参阅https://auth0.com/blog/cookies-vs-tokens-definitive-中的基于令牌的身份验证 - 指南/).
因此,当用户向您的应用发出请求以查看用户列表时,包含该view-users角色的访问令牌已经进入请求标头.而不是必须手动解析它,自己创建另一个请求来访问Keycloak用户端点并附加它(就像你似乎正在做的那样KeycloakBuilder),Keycloak Spring Security适配器已经提供了一个KeycloakRestTemplate类,它能够向另一个执行请求当前用户的服务:
SecurityConfig.java
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
...
@Autowired
public KeycloakClientRequestFactory keycloakClientRequestFactory;
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate keycloakRestTemplate() {
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
...
}
Run Code Online (Sandbox Code Playgroud)
请注意模板的范围PROTOTYPE,因此Spring将为每个请求使用不同的实例.
然后,自动装配此模板并使用它来发出请求:
@Service
public class UserRetrievalService{
@Autowired
private KeycloakRestTemplate keycloakRestTemplate;
public List<User> getUsers() {
ResponseEntity<User[]> response = keycloakRestTemplate.getForEntity(keycloakUserListEndpoint, User[].class);
return Arrays.asList(response.getBody());
}
}
Run Code Online (Sandbox Code Playgroud)
您需要实现自己的User类,该类与keycloak服务器返回的JSON响应相匹配.
请注意,当不允许用户访问列表时,将从Keycloak服务器返回403响应代码.你甚至可以在自己之前否认它,使用一些注释,如:@PreAuthorize("hasRole('VIEW_USERS')").
最后但同样重要的是,我认为@ dchrzascik的答案很明确.总而言之,我想说实际上还有另一种方法可以避免每次从keycloak服务器检索整个用户列表或者将用户存储在应用程序数据库中:您实际上可以缓存它们,以便您可以更新缓存从您的应用程序进行用户管理.
编辑
我已经实现了一个示例项目来展示如何获取整个用户列表,上传到Github.它是为机密客户端配置的(当使用公共客户端时,应从application.properties中删除该机密).
也可以看看:
我建议仔细检查您是否真的需要拥有自己的用户存储。您应该仅在 Keycloak 的用户联盟上进行中继,以避免重复数据,从而避免随之而来的问题。其中,Keycloak 负责管理用户,您应该让它完成它的工作。
由于您使用的是 OIDC,因此您可以从以下两方面受益:
在您以 JWT 形式获得的身份令牌中,您有一个“子”字段。该字段唯一标识用户。从OpenID Connect 规范:
必需的。主题标识符。最终用户在发行者中本地唯一且从未重新分配的标识符,旨在由客户端使用,例如,24400320 或 AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4。它的长度不得超过 255 个 ASCII 字符。sub 值是区分大小写的字符串。
在 keycloak 中,“sub”只是一个 UUID。您可以使用此字段将您的“对象 A”与“用户 B”相关联。在您的数据库中,这只是一个常规列,而不是外键。
在 Java 中,您可以使用安全上下文访问此 JWT 数据。您还可以查看keycloak 的 authz-springboot 快速入门,其中显示了如何访问KeycloakSecurityContext - 从那里您可以获得一个具有 getSubject 方法的 IDToken。
Keycloak 提供具有用户资源的Admin REST API 。这是 OIDC 支持的 API,因此您必须经过正确的身份验证。使用该 API,您可以对用户执行操作 - 包括列出他们。您可以直接使用该 API,也可以使用 Java SDK:keycloak admin client。
在这种情况下,您应该使用从请求中的用户那里获得的 JWT。使用 JWT,您可以确定发出请求的人可以列出该领域中的所有用户。例如,请考虑以下代码:
@GetMapping("/users")
public List<UserRepresentation> check(HttpServletRequest request){
KeycloakSecurityContext context = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
Keycloak keycloak = KeycloakBuilder.builder()
.serverUrl("http://localhost:8080/auth")
.realm("example")
.authorization(context.getTokenString())
.resteasyClient(new ResteasyClientBuilder().connectionPoolSize(20).build())
.build();
List<UserRepresentation> list = keycloak.realm("example").users().list();
return list;
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我们使用 HttpServletRequest 和它包含的令牌。我们可以通过使用org.springframework.security.core.Authenticationfrom spring security 或直接获取 Authorization 标头来获取相同的数据。问题是 KeycloakBuilder 需要一个字符串作为“授权”,而不是 AccessToken - 这就是您出现该错误的原因。
请记住,为了使其工作,创建请求的用户必须具有来自“领域管理”客户端的“查看用户”角色。您可以在该用户或他所属的某个组的“角色映射”选项卡中为他分配该角色。
此外,您必须经过适当的身份验证才能从安全上下文中受益,否则您将获得空值。示例性的 spring security keycloak 配置类是:
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/api/users/*")
.hasRole("admin")
.anyRequest()
.permitAll();
}
}
Run Code Online (Sandbox Code Playgroud)| 归档时间: |
|
| 查看次数: |
8976 次 |
| 最近记录: |