Bienvenido a nuestro sitio, en este lugar vas a encontrar la respuesta de lo que andabas buscando.
Solución:
Para acceder a la lista completa de usuarios, debe verificar que el usuario registrado contiene al menos el view-users
papel de la realm-management
cliente, vea esta respuesta que escribí hace algún tiempo. Una vez que el usuario tiene este rol, el JWT que recupera lo mantendrá.
Como puedo inferir de sus comentarios, parece que le faltan algunas bases sobre la Authorization
encabezamiento. Una vez que el usuario inicia sesión, obtiene el JWT firmado de keycloak, para que todos los clientes del reino puedan confiar en él, sin necesidad de preguntar a Keycloak. Este JWT contiene el token de acceso, que luego se requiere en el Authorization
encabezado para cada solicitud del usuario, con el prefijo Bearer
palabra clave (ver Autenticación basada en token en https://auth0.com/blog/cookies-vs-tokens-definitive-guide/).
Entonces, cuando el usuario realiza la solicitud a su aplicación para ver la lista de usuarios, su token de acceso que contiene el view-users
El rol ya entra en los encabezados de la solicitud. En lugar de tener que analizarlo manualmente, cree otra solicitud usted mismo para acceder al punto final del usuario de Keycloak y adjúntelo (como parece estar haciendo con KeycloakBuilder
), el adaptador Keycloak Spring Security ya proporciona un KeycloakRestTemplate
class, que puede realizar una solicitud a otro servicio para el usuario actual:
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);
...
Tenga en cuenta que el alcance de la plantilla es PROTOTYPE
, por lo que Spring usará una instancia diferente para cada una de las solicitudes que se realicen.
Luego, conecte automáticamente esta plantilla y utilícela para realizar solicitudes:
@Service
public class UserRetrievalService
@Autowired
private KeycloakRestTemplate keycloakRestTemplate;
public List getUsers()
ResponseEntity response = keycloakRestTemplate.getForEntity(keycloakUserListEndpoint, User[].class);
return Arrays.asList(response.getBody());
Deberá implementar su propio User
clase que coincide con la respuesta JSON devuelta por el servidor de capa de claves.
Tenga en cuenta que, cuando el usuario no tiene permiso para acceder a la lista, el servidor Keycloak devuelve un código de respuesta 403. Incluso podrías negarlo antes que tú mismo, usando algunas anotaciones como: @PreAuthorize("hasRole('VIEW_USERS')")
.
Por último, pero no menos importante, creo que la respuesta de @ dchrzascik está bien orientada. En resumen, diría que en realidad hay otra forma de evitar recuperar la lista completa de usuarios del servidor keycloak cada vez o que sus usuarios se almacenen en la base de datos de su aplicación: en realidad, podría almacenarlos en caché, para poder actualizar ese caché si hacer la gestión de usuarios desde su aplicación.
EDITAR
Implementé un proyecto de muestra para mostrar cómo obtener la lista completa de usuarios, cargada en Github. Está configurado para un cliente confidencial (cuando se usa un cliente público, el secreto debe eliminarse de la aplicación.propiedades).
Ver también:
- https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/java/spring-security-adapter.adoc
Sugiero que verifiques dos veces si realmente necesitas tener tu propia tienda de usuario. Debe confiar únicamente en la federación de usuarios de Keycloak para evitar la duplicación de datos y, por lo tanto, evitar los problemas que conlleva. Entre otros, Keycloak es responsable de administrar usuarios y debes dejar que haga su trabajo.
Dado que está utilizando OIDC, hay dos cosas de las que se beneficia:
-
En el token de identidad que obtiene en forma de JWT, tiene un campo “sub”. Este campo identifica de forma única a un usuario. De la especificación de OpenID Connect:
REQUERIDO. Identificador de sujeto. Un identificador local único y nunca reasignado dentro del Emisor para el Usuario final, que está destinado a ser consumido por el Cliente, por ejemplo, 24400320 o AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4. NO DEBE exceder los 255 caracteres ASCII de longitud. El subvalor distingue entre mayúsculas y minúsculas string.
En keycloak, “sub” es solo un UUID. Puede utilizar este campo para correlacionar su “objeto A” con el “usuario B”. En su base de datos, esto sería solo una columna regular, no una extranjera key.
En Java, puede acceder a estos datos JWT utilizando el contexto de seguridad. También puede echar un vistazo al inicio rápido authz-springboot de keycloak donde se muestra cómo puede acceder a KeycloakSecurityContext; desde allí puede obtener un IDToken que tiene un método getSubject.
-
Keycloak proporciona una API REST de administración que tiene un recurso de usuario. Esta es una API compatible con OIDC, por lo que debe estar debidamente autenticado. Con esa API, puede realizar operaciones en los usuarios, incluido enumerarlos. Puede consumir esa API directamente o mediante el uso de Java SDK: cliente de administración de keycloak.
En este escenario, debe usar el JWT que obtiene del usuario en la solicitud. Al usar JWT, está seguro de que alguien que realiza una solicitud puede enumerar todos los usuarios en ese ámbito. Por ejemplo, considere el siguiente código:
@GetMapping("/users") public List
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 list = keycloak.realm("example").users().list(); return list; En ese caso, estamos usando HttpServletRequest y el token que contiene. Podemos obtener los mismos datos mediante el uso de
org.springframework.security.core.Authentication
de Spring Security o directamente obteniendo un encabezado de Autorización. El caso es que KeycloakBuilder espera un string como una ‘autorización’, no un AccessToken: esta es la razón por la que tiene ese error.Tenga en cuenta que para que esto funcione, el usuario que está creando una solicitud debe tener un rol de ‘ver-usuarios’ del cliente de ‘administración de dominios’. Puede asignarle ese rol en la pestaña ‘Asignación de roles’ para ese usuario o algún grupo al que pertenece.
Además, debe estar debidamente autenticado para beneficiarse del contexto de seguridad; de lo contrario, obtendrá un null. La clase de configuración de keycloak de seguridad de primavera ejemplar es:
@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();
valoraciones y reseñas
Si estás de acuerdo, tienes la libertad de dejar una crónica acerca de qué le añadirías a esta crónica.