我怎样才能读懂所有使用Keycoak和Spring的用户?

0mkxixxg  于 2022-10-23  发布在  Spring
关注(0)|答案(3)|浏览(142)

我正在使用keycloak 3.4spring boot开发一个Web应用程序。我正在使用Active Directory作为用户联盟来检索所有用户信息。
但要在我的网络应用程序中使用这些信息,我想我必须将它们保存在“本地网络应用程序”数据库中。
那么,用户登录后,我如何将他们保存在数据库中呢?
我在考虑一个场景,比如:“我有一个引用用户B的对象A,所以我必须在它们之间建立一个关系。所以我添加了一个外键。”
在这种情况下,我需要在我的数据库上有用户。没有吗?

编辑

为了避免保存数据库上的所有用户,我尝试使用管理员API,因此我在控制器中添加了以下代码。
我还创建了另一个名为Test的客户端来获取所有用户,这样我就可以使用client-idclient-secret.,或者有什么方法可以使用JWT来使用管理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();

错误是:

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

我哪里做错了?

编辑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);

但授权总是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();
     }

}

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

编辑5.
这是我的密钥罩配置:

我使用VIEW用户权限登录的用户:

编辑6

这是启用日志记录后的日志表单密钥罩:

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

mwg9r5ms1#

为了访问整个用户列表,您必须验证登录的用户是否至少包含来自realm-management客户端的view-users角色,请参见我在一段时间之前编写的this answer。一旦用户拥有该角色,她检索的JWT将创建该角色。
从您的评论中我可以推断出,您似乎对Authorization标题缺乏一些依据。一旦用户登录,她就会从KEYCLOAK获得签名的JWT,这样领域中的每个客户端都可以信任它,而不需要询问KEYCLOAK。这个JWT包含访问令牌,每个用户请求的Authorization报头后面都需要该令牌,并以Bearer关键字为前缀(请参阅https://auth0.com/blog/cookies-vs-tokens-definitive-guide/中的基于令牌的身份验证)。
因此,当用户向您的应用程序发出请求以查看用户列表时,其包含view-users角色的访问令牌已经进入请求头。无需手动解析它,自己创建另一个请求来访问Keyloak用户终结点并附加它(就像您似乎正在使用KeycloakBuilder所做的那样),Keyloak 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);
    }

    ...
}

注意,模板的作用域是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());
    }

}

您将需要实现您自己的User类,该类与密钥罩服务器返回的JSON响应相匹配。
请注意,当用户不允许访问该列表时,会从Keyloak服务器返回403响应代码。您甚至可以当着自己的面否认它,使用一些注解:@PreAuthorize("hasRole('VIEW_USERS')")
最后但并非最不重要的一点是,我认为@dchrzascik的答案很有针对性。总而言之,我想说的是,实际上还有另一种方法可以避免每次都从密钥斗篷服务器检索整个用户列表,或者将用户存储在应用程序数据库中:您可以实际缓存它们,因此如果您从应用程序进行用户管理,则可以更新缓存。

编辑

我已经实现了一个样例项目来展示如何获取上传到Github的完整用户列表。它是为机密客户端配置的(当使用公共客户端时,应该从应用程序中删除该秘密)。

另请参阅:

odopli94

odopli942#

我建议仔细检查一下你是否真的需要拥有自己的用户存储。您应该只依赖Keyloak的用户联盟,以避免复制数据,从而避免随之而来的问题。其中,Keyloak负责管理用户,你应该让它来做自己的工作。
由于您使用的是OIDC,因此您可以从中受益于两件事:
1.在您以JWT形式获得的身份令牌中,您有一个“SUB”字段。此字段唯一标识用户。从OpenID Connect spec
必填。主题识别符。最终用户在颁发者中的本地唯一且从未重新分配的标识符,它将由客户端使用,例如24400320或AItOawmwtWwcT0k51BayewNvucJUqsvl6qs7A4。长度不得超过255个ASCII字符。子值是区分大小写的字符串。
在KEYCLOAK中,“SUB”只是一个UUID。您可以使用该字段将您的“对象A”与“用户B”相关联。在您的数据库中,这将只是一个常规列,而不是外键。
在Java中,您可以使用安全上下文访问该JWT数据。您还可以查看keycloak's authz-springboot quickstart,其中显示了如何访问KeycloakSecurityContext-从那里您可以获得一个具有getSubject方法的IDToken。
1.Keyshaak提供有用户资源的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;
}

在这种情况下,我们使用HttpServletRequest及其包含的令牌。我们可以通过使用来自Spring Security的org.springframework.security.core.Authentication或直接获取授权头部来获取相同的数据。问题是,KeyloakBuilder需要一个字符串作为“授权”,而不是AccessToken--这就是出现这种错误的原因。
请记住,为了使此功能正常工作,创建请求的用户必须具有来自“领域管理”客户端的“view-user”角色。您可以在该用户或他所属的某个组的“角色Map”选项卡中将该角色分配给他。
此外,您必须经过适当的身份验证才能从安全上下文中受益,否则您将获得空。示例性的Spring安全密钥罩配置类是:

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

slhcrj9b3#

Keyloak不具备批量加载用户的功能。
如果security is not the utmost criteria用于您的应用程序。我想你已经有数据库了。这取决于您要查找的用户信息的类型,但最简单的方法不需要编写另一个模块(上面的答案)或修改密钥遮盖代码就是directly query it from the database
普普通通SELECT * FROM user_entity
只需确保不通过键盘遮盖就不会修改任何内容,无论何时发生架构更改,都要更新查询

相关问题