web-dev-qa-db-ja.com

keycloakとspringを使用してすべてのユーザーを読み取るにはどうすればよいですか?

私はkeycloak 3.4spring bootを使用してWebアプリを開発しています。 Active Directoryをユーザーフェデレーションとして使用して、すべてのユーザー情報を取得しています。

しかし、これらの情報を自分のWebアプリ内で使用するには、「local-webapp」データベース内に保存する必要があると思います。

ユーザーがログインした後、どうすればデータベース内に保存できますか?

次のようなシナリオについて考えています。 。そこで、外部キーを追加します。」

その場合、自分のDBにユーザーが必要です。番号?

[〜#〜]編集[〜#〜]

DB上のすべてのユーザーの保存を回避するために、Administrator APIを使用しようとしているので、コントローラー内に次のコードを追加しました。

また、Testという別のクライアントを作成してすべてのユーザーを取得しました。このようにして、client-idおよびclient-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

私はどこで間違っていますか?

EDIT 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です。どうして?

EDIT 3以下は、私の春のセキュリティ構成を見つけることができます:

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

}

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

これは私のkeycocloak設定です: enter image description here

ユーザー表示権限でログインするために使用しているユーザー: enter image description here

EDIT 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]
9
Teo

ユーザーのリスト全体にアクセスするには、ログに記録されたユーザーに少なくともview-usersクライアントからのrealm-managementロールが含まれていることを確認する必要があります。参照 この回答 少し前に書きました。ユーザーがこの役割を持つと、彼女が取得するJWTはそれを共存させます。

私はあなたのコメントから推測できるように、Authorizationヘッダーに関する根拠が不足しているようです。ユーザーがログインすると、Keycloakから署名済みのJWTを取得します。これにより、レルム内のすべてのクライアントがKeycloakに問い合わせなくても信頼できるようになります。このJWTにはアクセストークンが含まれています。これは、後で各ユーザーのリクエストのAuthorizationヘッダーで必須になり、先頭にBearerキーワードが付けられます(トークンベースの認証を参照してください https:// auth0 .com/blog/cookies-vs-tokens-definitive-guide / )。

したがって、ユーザーがユーザーのリストを表示するためにアプリにリクエストを送信すると、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);
    }

    ...
}

テンプレートのスコープは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());
    }

}

キークロークサーバーから返されるJSON応答と一致する独自のUserクラスを実装する必要があります。

ユーザーがリストへのアクセスを許可されていない場合、403応答コードがKeycloakサーバーから返されることに注意してください。 @PreAuthorize("hasRole('VIEW_USERS')")のようないくつかの注釈を使用して、自分の前でそれを拒否することもできます。

最後に重要なことですが、@ dchrzascikの回答は適切に指摘されています。要約すると、毎回キークロークサーバーからユーザーリスト全体を取得することや、ユーザーをアプリデータベースに保存することを回避する別の方法が実際にあると思います。実際にそれらをキャッシュして、必要に応じてそのキャッシュを更新することができます。アプリからユーザー管理を行います。


[〜#〜]編集[〜#〜]

Github にアップロードされた、ユーザーの全リストを取得する方法を示すサンプルプロジェクトを実装しました。機密クライアント用に構成されています(パブリッククライアントを使用する場合、シークレットはapplication.propertiesから削除する必要があります)。

参照:

9
Xtreme Biker

独自のユーザーストアが本当に必要かどうかを再確認することをお勧めします。 Keycloakのユーザーフェデレーションのみを中継して、データの重複を回避し、それに伴う問題を回避する必要があります。特に、Keycloakはユーザーの管理を担当しているため、ユーザーに任せる必要があります。

OIDCを使用しているので、次の2つのメリットがあります。

  1. JWTの形式で取得するIDトークンには、「サブ」フィールドがあります。このフィールドは、ユーザーを一意に識別します。 OpenID Connect仕様 から:

    必須。サブジェクト識別子。クライアントによって消費されることを目的とした、エンドユーザーの発行者内でローカルに一意で再割り当てされることのない識別子。 255文字を超えることはできませんASCII文字の長さ。サブ値は大文字と小文字を区別する文字列です。

    キークロークでは、「sub」は単なるUUIDです。このフィールドを使用して、「オブジェクトA」を「ユーザーB」に関連付けることができます。 DBでは、これは外部キーではなく通常の列になります。

    Javaでは、 security context を使用してこのJWTデータにアクセスできます。 keycloakのauthz-springbootクイックスタート にもアクセスして、 KeycloakSecurityContext -そこから、getSubjectメソッドを持つIDTokenを取得できます。

  2. Keycloakは管理者を提供しますREST usersリソースを持つ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とそれに含まれるトークンを使用しています。スプリングセキュリティからorg.springframework.security.core.Authenticationを使用するか、Authorizationヘッダーを直接取得することで、同じデータを取得できます。重要なのは、KeycloakBuilderが文字列を「認証」として期待しているのであって、AccessTokenではないということです。これが、エラーが発生する理由です。

    これが機能するためには、リクエストを作成しているユーザーが「レルム管理」クライアントからの「view-users」ロールを持っている必要があることに注意してください。そのロールは、そのユーザーまたはユーザーが属するグループの[ロールマッピング]タブで割り当てることができます。

    さらに、セキュリティコンテキストを利用するには、適切に認証される必要があります。そうしないと、nullが取得されます。代表的なSpring Securityキークローク構成クラスは次のとおりです。

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