Saltar al contenido

Las credenciales del cliente OAuth2 fluyen a través de la integración de Spring Boot Keycloak

Solución:

Sí, puede usar el flujo de credenciales de cliente de OAuth 2.0 y las cuentas de servicio.

Keycloak sugiere 3 formas de asegurar los servicios REST de SpringBoot:

  1. con adaptador de bota de resorte Keycloak
  2. con adaptador de seguridad Spring Keycloak
  3. con OAuth2 / OpenID Connect

Aquí hay una buena explicación sobre esto con un ejemplo en la forma OAuth2 / OIDC:

  • Tutorial de Arun B Chandrasekaran
  • Ejemplo de código de Arun B Chandrasekaran

Si sigue este ejemplo, tenga en cuenta:

Tenga cuidado de configurar su cliente como:

  • Tipo de acceso: confidencial
  • Autorización: habilitada
  • Cuenta de servicio (flujo de credenciales de cliente OAuth): habilitado

Tenga cuidado de configurar su servicio de destino como:

  • Tipo de acceso: solo portador

Entonces, la persona que llama debe ser confidential y el servicio de destino debe ser bearer-only.

Crea tus usuarios, roles, mapeadores … y asigna roles a tus usuarios.

Compruebe que tiene estas dependencias en su proyecto de primavera:

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

Configure la autenticación que se utilizará en el cliente REST (application.properties), por ejemplo:

security.oauth2.client.client-id=employee-service
security.oauth2.client.client-secret=68977d81-c59b-49aa-aada-58da9a43a850
security.oauth2.client.user-authorization-uri=${rest.security.issuer-uri}/protocol/openid-connect/auth
security.oauth2.client.access-token-uri=${rest.security.issuer-uri}/protocol/openid-connect/token
security.oauth2.client.scope=openid
security.oauth2.client.grant-type=client_credentials

Implementa tu JwtAccessTokenCustomizer y SecurityConfigurer (ResourceServerConfigurerAdapter) como la muestra de Arun.

Y finalmente implemente su controlador de servicio:

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

Para obtener un tutorial completo, lea el tutorial de Arun mencionado.

Espero eso ayude.

Siguiendo a @ dmitri-algazin para implementar el flujo de trabajo, tiene básicamente dos opciones:

  1. Si desea cubrir otros IdM además de Keycloak que resuelve de alguna manera el principio de responsabilidad única, usaría RestTemplate. A continuación puede encontrar las variables:
    //Constants
    @Value("${keycloak.url}")
    private String keycloakUrl;

    @Value("${keycloak.realm}")
    private String keycloakRealm;

    @Value("${keycloak.client_id}")
    private String keycloakClientId;

    RestTemplate restTemplate = new RestTemplate();
    private static final String BEARER = "BEARER ";

Primero necesitas generar el token de acceso:

    @Override
    public AccessTokenResponse login(KeycloakUser user) throws NotAuthorizedException {
        try {
            String uri = keycloakUrl + "/realms/" + keycloakRealm + 
                    "/protocol/openid-connect/token";
            String data = "grant_type=password&username="+
                    user.getUsername()+"&password="+user.getPassword()+"&client_id="+
                    keycloakClientId;

            HttpHeaders headers = new HttpHeaders();
            headers.set("Content-Type", "application/x-www-form-urlencoded");

            HttpEntity<String> entity = new HttpEntity<String>(data, headers);
            ResponseEntity<AccessTokenResponse> response = restTemplate.exchange(uri, 
                    HttpMethod.POST, entity, AccessTokenResponse.class);            

            if (response.getStatusCode().value() != HttpStatus.SC_OK) {
                log.error("Unauthorised access to protected resource", response.getStatusCode().value());
                throw new NotAuthorizedException("Unauthorised access to protected resource");
            }
            return response.getBody();
        } catch (Exception ex) {
            log.error("Unauthorised access to protected resource", ex);
            throw new NotAuthorizedException("Unauthorised access to protected resource");
        } 
    }

Y luego, con el token, puede recuperar información de los usuarios:

    @Override
    public String user(String authToken) throws NotAuthorizedException {

        if (! authToken.toUpperCase().startsWith(BEARER)) {
            throw new NotAuthorizedException("Invalid OAuth Header. Missing Bearer prefix");
        }

        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", authToken);

        HttpEntity<String> entity = new HttpEntity<>(headers);

        ResponseEntity<AccessToken> response = restTemplate.exchange(
                keycloakUrl + "/realms/" + keycloakRealm + "/protocol/openid-connect/userinfo", 
                HttpMethod.POST, 
                entity, 
                AccessToken.class);

        if (response.getStatusCode().value() != HttpStatus.SC_OK) {
            log.error("OAuth2 Authentication failure. "
                    + "Invalid OAuth Token supplied in Authorization Header on Request. Code {}", response.getStatusCode().value());
            throw new NotAuthorizedException("OAuth2 Authentication failure. "
                    + "Invalid OAuth Token supplied in Authorization Header on Request.");
        }

        log.debug("User info: {}", response.getBody().getPreferredUsername());
        return response.getBody().getPreferredUsername();
    }

Puede sustituir esta URL por la proporcionada por @ dimitri-algazin para recuperar toda la información de los usuarios.

  1. Es posible utilizar las dependencias de Keycloak:
        <!-- keycloak -->
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-admin-client</artifactId>
            <version>3.4.3.Final</version>
        </dependency>

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-client</artifactId>
            <version>3.1.4.Final</version>
        </dependency>

Y usa las clases para generar el token:

            Keycloak keycloak = KeycloakBuilder
                    .builder()
                    .serverUrl(keycloakUrl)
                    .realm(keycloakRealm)
                    .username(user.getUsername())
                    .password(user.getPassword())
                    .clientId(keycloakClientId)
                    .resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build())
                    .build();

            return keycloak.tokenManager().getAccessToken();

Los ejemplos se extraen de aquí. También subimos la imagen a Docker Hub para facilitar la interacción con Keycloak. Por esta razón comenzamos con la opción 2). En este momento estamos en el proceso de cubrir otros IdM y optamos por la opción 1) para evitar incluir dependencias adicionales. Conclusión:

Yo iria por opcion 2 si se apega a Keycloak porque las clases incluyen funcionalidades adicionales para la herramienta Keycloak. Yo iria por Opción 1 para mayor cobertura y otras herramientas de OAuth 2.0.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *