Saltar al contenido

¿Cómo configurar Spring Boot Security OAuth2 para ADFS?

Hola, hemos encontrado la solución a tu búsqueda, deslízate y la verás un poco más abajo.

Solución:

tldr; ADFS incrusta información de usuario en el token de oauth. Debe crear y anular el objeto org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices para extraer esta información y agregarla al objeto Principal

Para comenzar, primero siga el tutorial de Spring OAuth2: https://spring.io/guides/tutorials/spring-boot-oauth2/. Utilice estas propiedades de la aplicación (complete su propio dominio):

security:
  oauth2:
    client:
      clientId: [client id setup with ADFS]
      userAuthorizationUri: https://[adfs domain]/adfs/oauth2/authorize?resource=[MyRelyingPartyTrust]
      accessTokenUri: https://[adfs domain]/adfs/oauth2/token
      tokenName: code
      authenticationScheme: query
      clientAuthenticationScheme: form
      grant-type: authorization_code
    resource:
      userInfoUri: https://[adfs domain]/adfs/oauth2/token

Nota: Ignoraremos lo que esté en userInfoUri, pero Spring OAuth2 parece requerir que haya algo allí.

Crea una nueva clase, AdfsUserInfoTokenServices, que puede copiar y modificar a continuación (querrá limpiarlo un poco). Ésta es una copia de la clase Spring; Probablemente podría extenderlo si lo desea, pero hice suficientes cambios en los que no parecía que me ganara mucho:

package edu.bowdoin.oath2sample;

import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.FixedAuthoritiesExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.FixedPrincipalExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.util.Assert;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

public class AdfsUserInfoTokenServices implements ResourceServerTokenServices 

protected final Logger logger = LoggerFactory.getLogger(getClass());

private final String userInfoEndpointUrl;

private final String clientId;

private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;

private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();

private PrincipalExtractor principalExtractor = new FixedPrincipalExtractor();

public AdfsUserInfoTokenServices(String userInfoEndpointUrl, String clientId) 
    this.userInfoEndpointUrl = userInfoEndpointUrl;
    this.clientId = clientId;


public void setTokenType(String tokenType) 
    this.tokenType = tokenType;


public void setRestTemplate(OAuth2RestOperations restTemplate) 
    // not used


public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) 
    Assert.notNull(authoritiesExtractor, "AuthoritiesExtractor must not be null");
    this.authoritiesExtractor = authoritiesExtractor;


public void setPrincipalExtractor(PrincipalExtractor principalExtractor) 
    Assert.notNull(principalExtractor, "PrincipalExtractor must not be null");
    this.principalExtractor = principalExtractor;


@Override
public OAuth2Authentication loadAuthentication(String accessToken)
        throws AuthenticationException, InvalidTokenException 
    Map map = getMap(this.userInfoEndpointUrl, accessToken);
    if (map.containsKey("error")) 
        if (this.logger.isDebugEnabled()) 
            this.logger.debug("userinfo returned error: " + map.get("error"));
        
        throw new InvalidTokenException(accessToken);
    
    return extractAuthentication(map);


private OAuth2Authentication extractAuthentication(Map map) 
    Object principal = getPrincipal(map);
    List authorities = this.authoritiesExtractor
            .extractAuthorities(map);
    OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
            null, null, null, null);
    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
            principal, "N/A", authorities);
    token.setDetails(map);
    return new OAuth2Authentication(request, token);


/**
 * Return the principal that should be used for the token. The default implementation
 * delegates to the @link PrincipalExtractor.
 * @param map the source map
 * @return the principal or @literal "unknown"
 */
protected Object getPrincipal(Map map) 
    Object principal = this.principalExtractor.extractPrincipal(map);
    return (principal == null ? "unknown" : principal);


@Override
public OAuth2AccessToken readAccessToken(String accessToken) 
    throw new UnsupportedOperationException("Not supported: read access token");


private Map getMap(String path, String accessToken) 
    if (this.logger.isDebugEnabled()) 
        this.logger.debug("Getting user info from: " + path);
    
    try 
        DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
                accessToken);
        token.setTokenType(this.tokenType);

        logger.debug("Token value: " + token.getValue());

        String jwtBase64 = token.getValue().split("\.")[1];

        logger.debug("Token: Encoded JWT: " + jwtBase64);
        logger.debug("Decode: " + Base64.getDecoder().decode(jwtBase64.getBytes()));

        String jwtJson = new String(Base64.getDecoder().decode(jwtBase64.getBytes()));

        ObjectMapper mapper = new ObjectMapper();

        return mapper.readValue(jwtJson, new TypeReference>());
    
    catch (Exception ex) 
        this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "
                + ex.getMessage());
        return Collections.singletonMap("error",
                "Could not fetch user details");
    


El método getMap es donde se analiza el valor del token y se extrae y decodifica la información del usuario con formato JWT (la verificación de errores se puede mejorar aquí, este es un borrador, pero le da la esencia). Consulte la parte inferior de este enlace para obtener información sobre cómo ADFS incrusta datos en el token: https://blogs.technet.microsoft.com/askpfeplat/2014/11/02/adfs-deep-dive-comparing-ws-fed- saml-y-oauth /

Agregue esto a su configuración:

@Autowired
private ResourceServerProperties sso;

@Bean
public ResourceServerTokenServices userInfoTokenServices() 
    return new AdfsUserInfoTokenServices(sso.getUserInfoUri(), sso.getClientId());

Ahora siga la primera parte de estas instrucciones para configurar un cliente ADFS y un fideicomiso de parte de confianza: https://vcsjones.com/2015/05/04/authenticating-asp-net-5-to-ad-fs-oauth/

Debe agregar el ID de su confianza de parte de confianza al archivo de propiedades userAuthorizationUri como el valor del parámetro ‘recurso’.

Reglas de reclamo:

Si no desea tener que crear su propio PrincipalExtractor o AuthoritiesExtractor (consulte el código AdfsUserInfoTokenServices), configure lo que sea attribute que está utilizando para el nombre de usuario (por ejemplo, SAM-Account-Name) para que tenga un tipo de reclamo saliente ‘nombre de usuario’. Al crear reglas de reclamo para grupos, asegúrese de que el tipo de reclamo sea “autoridades” (ADFS simplemente déjeme escribir eso, no hay un tipo de reclamo existente con ese nombre). De lo contrario, puede escribir extractores para que funcionen con los tipos de notificaciones de ADFS.

Una vez que esté todo hecho, debería tener un ejemplo de trabajo. Hay muchos detalles aquí, pero una vez que lo aprende, no está tan mal (más fácil que hacer que SAML funcione con ADFS). los key es comprender la forma en que ADFS incrusta datos en el token OAuth2 y comprender cómo usar el objeto UserInfoTokenServices. Espero que esto ayude a alguien más.

Además de la respuesta aceptada:

@Ashika quiere saber si puede usar esto con REST en lugar del inicio de sesión del formulario. Simplemente cambie de la anotación @ EnableOAuth2Sso a @EnableResourceServer.

Con la anotación @EnableResourceServer, mantiene la capacidad de usar SSO aunque no usó la anotación @ EnableOAuth2Sso. Estás funcionando como servidor de recursos.

https://docs.spring.io/spring-security-oauth2-boot/docs/current/reference/htmlsingle/#boot-features-security-oauth2-resource-server

Aunque esta pregunta es antigua, no hay otra referencia en la web sobre cómo integrar Spring OAuth2 con ADFS.

Por lo tanto, agregué un proyecto de muestra sobre cómo integrarse con Microsoft ADFS utilizando la configuración automática de arranque listo para usar para el cliente Oauth2:

https://github.com/selvinsource/spring-security/tree/oauth2login-adfs-sample/samples/boot/oauth2login#adfs-login

Te invitamos a añadir valor a nuestro contenido añadiendo tu experiencia en los informes.

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