【问题标题】:How to retrieve scopes from OAuth token within Spring boot SSO + zuul如何在 Spring Boot SSO + zuul 中从 OAuth 令牌中检索范围
【发布时间】:2016-11-16 22:25:05
【问题描述】:

我正在尝试使用 Spring boot SSO + Zuul 制作一个简单的 API 网关。我需要将 OAuth 范围转换为标头,其他一些后端服务将进一步使用这些标头来根据标头执行 RBAC。

我正在使用这个 CustomOAuth2TokenRelayFilter,它基本上会在发送到后端之前设置标题。我的问题是如何从当前令牌中获取范围。 OAuth2AuthenticationDetails 类确实提供了令牌值,但它不提供范围。

我不确定如何获得其中的作用域。

以下是自定义 Zuul 过滤器,主要取自 https://github.com/spring-cloud/spring-cloud-security/blob/master/spring-cloud-security/src/main/java/org/springframework/cloud/security/oauth2/proxy/OAuth2TokenRelayFilter.java

    import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;

@Component
    public class CustomOAuth2TokenRelayFilter extends ZuulFilter {

        private static Logger LOGGER = LoggerFactory.getLogger(CustomOAuth2TokenRelayFilter.class);

        private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
        private static final String TOKEN_TYPE = "TOKEN_TYPE";

        private OAuth2RestOperations restTemplate;


        public void setRestTemplate(OAuth2RestOperations restTemplate) {
            this.restTemplate = restTemplate;
        }


        @Override
        public int filterOrder() {
            return 1;
        }

        @Override
        public String filterType() {
            return "pre";
        }

        @Override
        public boolean shouldFilter() {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();

            if (auth instanceof OAuth2Authentication) {
                Object details = auth.getDetails();
                if (details instanceof OAuth2AuthenticationDetails) {
                    OAuth2AuthenticationDetails oauth = (OAuth2AuthenticationDetails) details;
                    RequestContext ctx = RequestContext.getCurrentContext();

                    LOGGER.debug ("role " + auth.getAuthorities());

                    LOGGER.debug("scope", ctx.get("scope")); // How do I obtain the scope ??


                    ctx.set(ACCESS_TOKEN, oauth.getTokenValue());
                    ctx.set(TOKEN_TYPE, oauth.getTokenType()==null ? "Bearer" : oauth.getTokenType());
                    return true;
                }
            }
            return false;
        }

        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            ctx.addZuulRequestHeader("x-pp-user", ctx.get(TOKEN_TYPE) + " " + getAccessToken(ctx));
            return null;
        }

        private String getAccessToken(RequestContext ctx) {
            String value = (String) ctx.get(ACCESS_TOKEN);
            if (restTemplate != null) {
                // In case it needs to be refreshed
                OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder
                        .getContext().getAuthentication();
                if (restTemplate.getResource().getClientId()
                        .equals(auth.getOAuth2Request().getClientId())) {
                    try {
                        value = restTemplate.getAccessToken().getValue();
                    }
                    catch (Exception e) {
                        // Quite possibly a UserRedirectRequiredException, but the caller
                        // probably doesn't know how to handle it, otherwise they wouldn't be
                        // using this filter, so we rethrow as an authentication exception
                        throw new BadCredentialsException("Cannot obtain valid access token");
                    }
                }
            }
            return value;
        }

    }

【问题讨论】:

    标签: spring-boot oauth-2.0 roles scopes spring-cloud-netflix


    【解决方案1】:

    您可以将OAuth2ClientContext 注入过滤器,并使用oAuth2ClientContext.getAccessToken().getScope() 检索范围。

    OAuth2ClientContext 是一个会话范围的 bean,包含当前访问令牌和保留状态。

    因此,如果我们将其应用于您的示例,它将如下所示:

    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.oauth2.client.OAuth2ClientContext;
    import org.springframework.security.oauth2.client.OAuth2RestOperations;
    import org.springframework.security.oauth2.provider.OAuth2Authentication;
    import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CustomOAuth2TokenRelayFilter extends ZuulFilter {
    
        private static Logger LOGGER = LoggerFactory.getLogger(CustomOAuth2TokenRelayFilter.class);
    
        private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
        private static final String TOKEN_TYPE = "TOKEN_TYPE";
    
        private OAuth2RestOperations restTemplate;
    
        @Autowired
        private OAuth2ClientContext oAuth2ClientContext;
    
        public void setRestTemplate(OAuth2RestOperations restTemplate) {
            this.restTemplate = restTemplate;
        }
    
    
        @Override
        public int filterOrder() {
            return 1;
        }
    
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public boolean shouldFilter() {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    
            if (auth instanceof OAuth2Authentication) {
                Object details = auth.getDetails();
                if (details instanceof OAuth2AuthenticationDetails) {
                    OAuth2AuthenticationDetails oauth = (OAuth2AuthenticationDetails) details;
                    RequestContext ctx = RequestContext.getCurrentContext();
    
                    LOGGER.debug ("role " + auth.getAuthorities());
    
                    LOGGER.debug("scope" + oAuth2ClientContext.getAccessToken().getScope());
    
                    ctx.set(ACCESS_TOKEN, oauth.getTokenValue());
                    ctx.set(TOKEN_TYPE, oauth.getTokenType()==null ? "Bearer" : oauth.getTokenType());
                    return true;
                }
            }
            return false;
        }
    
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            ctx.addZuulRequestHeader("x-pp-user", ctx.get(TOKEN_TYPE) + " " + getAccessToken(ctx));
            return null;
        }
    
        private String getAccessToken(RequestContext ctx) {
            String value = (String) ctx.get(ACCESS_TOKEN);
            if (restTemplate != null) {
                // In case it needs to be refreshed
                OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder
                        .getContext().getAuthentication();
                if (restTemplate.getResource().getClientId()
                        .equals(auth.getOAuth2Request().getClientId())) {
                    try {
                        value = restTemplate.getAccessToken().getValue();
                    }
                    catch (Exception e) {
                        // Quite possibly a UserRedirectRequiredException, but the caller
                        // probably doesn't know how to handle it, otherwise they wouldn't be
                        // using this filter, so we rethrow as an authentication exception
                        throw new BadCredentialsException("Cannot obtain valid access token");
                    }
                }
            }
            return value;
        }
    
    }
    

    【讨论】:

    • 不幸的是,为我返回 null。尝试在控制器中自动装配。在我的应用中使用客户端凭据授权
    • 似乎很有魅力。但是有 3 个问题:在上面的示例中,您注入了 oAuth2ClientContext 实例,为什么还要通过 Authentication 实例来访问呢?您是否喜欢有关OAuth2ClientContext 的会话范围性质的文档?使用 Zuul 代理之类的请求范围可能更安全?
    • @sandkeks 你是怎么解决这个问题的?
    【解决方案2】:

    您可以使用 SecurityContextHolderOAuth2Authentication 从 OAuth2 令牌中检索范围

    private static Set<String> getOAuthTokenScopes() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        OAuth2Authentication oAuth2Authentication;
    
        if (authentication instanceof OAuth2Authentication) {
            oAuth2Authentication = (OAuth2Authentication) authentication;
        } else {
            throw new IllegalStateException("Authentication not supported!");
        }
    
        return oAuth2Authentication.getOAuth2Request().getScope();
    }
    

    【讨论】:

    • 这对我有用,不像oAuth2ClientContext getScopes() 方法返回null。我可以简单地将OAuth2Authentication authentication 添加到我的@Controller 方法签名中,这使它变得更加简单。
    猜你喜欢
    • 2020-02-17
    • 2015-09-08
    • 2020-08-15
    • 1970-01-01
    • 2019-11-12
    • 2016-05-28
    • 2020-11-02
    • 2020-01-28
    • 1970-01-01
    相关资源
    最近更新 更多