如何为ADFS配置Spring Boot安全性OAuth2? [英] How to configure spring boot security OAuth2 for ADFS?

查看:1520
本文介绍了如何为ADFS配置Spring Boot安全性OAuth2?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有人使用ADFS成功配置Spring Boot OAuth2作为身份提供商?我成功地在Facebook上学习了本教程, https://spring.io/guides/tutorials / spring-boot-oauth2 / ,但ADFS似乎没有userInfoUri。我认为ADFS在令牌本身(JWT格式?)中返回声明数据,但不确定如何使用Spring。以下是我目前在我的属性文件中所拥有的内容:

Has anyone successfully configured Spring Boot OAuth2 with ADFS as the identity provider? I followed this tutorial successfully for Facebook, https://spring.io/guides/tutorials/spring-boot-oauth2/, but ADFS doesn't appear to have a userInfoUri. I think ADFS returns the claims data in the token itself (JWT format?), but not sure how to make that work with Spring. Here is what I have so far in my properties file:

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: [not sure what to put here?]


推荐答案

tldr; ADFS将用户信息嵌入到oauth令牌中。您需要创建并覆盖org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices对象以提取此信息并将其添加到Principal对象

tldr; ADFS embeds user information in the oauth token. You need to create and override the org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices object to extract this information and add it to the Principal object

要开始使用,请先关注Spring OAuth2教程 https:/ /spring.io/guides/tutorials/spring-boot-oauth2/ 。使用这些应用程序属性(填写您自己的域名):

To get started, first follow the Spring OAuth2 tutorial: https://spring.io/guides/tutorials/spring-boot-oauth2/. Use these application properties (fill in your own domain):

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

注意:我们会忽略userInfoUri中的任何内容,但是Spring OAuth2似乎需要一些东西。

Note: We will be ignoring whatever is in the userInfoUri, but Spring OAuth2 seems to require something be there.

创建一个新类,AdfsUserInfoTokenServices,你可以复制和调整下面(你会想要清理一些)。这是Spring类的副本;如果你愿意的话,你可以扩展它,但我做了足够的改变,似乎并没有给我带来太大的好处:

Create a new class, AdfsUserInfoTokenServices, which you can copy and tweak below (you will want to clean it up some). This is a copy of the Spring class; You could probably extend it if you want, but I made enough changes where that didn't seem like it gained me much:

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<String, Object> 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<String, Object> map) {
    Object principal = getPrincipal(map);
    List<GrantedAuthority> 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<String, Object> 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<String, Object> 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<Map<String, Object>>(){});
    }
    catch (Exception ex) {
        this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "
                + ex.getMessage());
        return Collections.<String, Object>singletonMap("error",
                "Could not fetch user details");
    }
}
}

getMap方法是解析令牌值并提取和解码JWT格式的用户信息(这里可以改进错误检查,这是草稿,但给出了要点)。有关ADFS如何在令牌中嵌入数据的信息,请参阅此链接的底部: https://blogs.technet.microsoft.com/askpfeplat/2014/11/02/adfs-deep-dive-comparing-ws-fed- saml-and-oauth /

The getMap method is where the token value is parsed and the JWT formatted user info is extracted and decoded (error checking can be improved here, this is a rough draft, but gives you the gist). See toward the bottom of this link for information on how ADFS embeds data in the token: https://blogs.technet.microsoft.com/askpfeplat/2014/11/02/adfs-deep-dive-comparing-ws-fed-saml-and-oauth/

将此添加到您的配置中:

@Autowired
private ResourceServerProperties sso;

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

现在按照这些说明的第一部分设置ADFS客户端和依赖方信任 https://vcsjones.com/2015/05/04/authenticating-asp-net-5-to-ad-fs-oauth/

您需要将依赖方信任的ID添加到属性文件userAuthorizationUri作为参数'resource'的值。

You need to add the id of your relying party trust to the properties file userAuthorizationUri as the value of the parameter 'resource'.

声明规则:

如果您不想创建自己的PrincipalExtractor或AuthoritiesExtractor(请参阅AdfsUserInfoTokenServices代码),请设置您用于用户名的任何属性(例如SAM-Account-Name)以便它具有和传出声明类型'用户名'。在为组创建声明规则时,请确保声明类型是权限(ADFS只是让我输入,该名称没有现有的声明类型)。否则,您可以编写提取器来使用ADFS声明类型。

If you don't want to have to create your own PrincipalExtractor or AuthoritiesExtractor (see the AdfsUserInfoTokenServices code), set whatever attribute you are using for the username (e.g. SAM-Account-Name) so that it has and Outgoing Claim Type 'username'. When creating claim rules for groups, make sure the Claim type is "authorities" (ADFS just let me type that in, there isn't an existing claim type by that name). Otherwise, you can write extractors to work with the ADFS claim types.

完成所有操作后,您应该有一个工作示例。这里有很多细节,但是一旦你把它弄下来,它就不会太糟糕(比让SAML与ADFS一起工作更容易)。关键是要了解ADFS在OAuth2令牌中嵌入数据的方式,并了解如何使用UserInfoTokenServices对象。希望这有助于其他人。

Once that is all done, you should have a working example. There are a lot of details here, but once you get it down, it's not too bad (easier than getting SAML to work with ADFS). The key is understanding the way ADFS embeds data in the OAuth2 token and understanding how to use the UserInfoTokenServices object. Hope this helps someone else.

这篇关于如何为ADFS配置Spring Boot安全性OAuth2?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆