来自多个身份验证服务器的OAuth2 / OIDC客户端授权的额外身份验证步骤

时间:2019-09-07 12:12:11

标签: java spring spring-security spring-security-oauth2

我目前已设置Spring Cloud Gateway反向代理,其目的是:

a)处理多个OAuth / OIDC提供程序的身份验证,包括获取令牌

b)在本地从提供程序中查找详细信息,确保已授权OAuth用户x Oauth提供程序组合

c)如果获得授权,请查找“授予/权限”,然后将请求转发给SCG,并在JWT中包含授权主体的详细信息。

d)如果未获得授权,则显示一个页面,其中显示OAuth2 Auth中的相关详细信息,并说明未授权。

我已经完成了大多数步骤,但是在将步骤c)合并到Spring Security Webflux中时遇到了困难

我要做的是获取从Authentication交换获得的OAuth2AuthenticationToken,在步骤中执行查找,然后返回 根据结果​​定制Prinicipal。

然后通过代码将其用于触发SCG行为或显示页面。

spring-boot.version> 2.1.6。发布 spring-cloud.version>格林尼治SR2

我的问题是我不知道这样做的最佳方法。

  1. 在OAuth2客户端中使用一些挂钩执行额外的身份验证步骤。在这种情况下,我可能需要返回OAuth2Principal

  2. 在身份验证之后,将额外的安全筛选器添加到链中。 这将用我自己的原则代替OAuth2Principal。我不确定身份验证后替换Principal是否合法,可能会删除身份验证状态

  3. 编写一个自定义的AuthN Provider,该代理将代理到OAuth客户端,并且一旦竞争就运行了它自己的逻辑,然后发出信号对其进行身份验证。这似乎是一种复杂的方法,而且我不确定要为此使用什么类。

我已经阅读了Spring Security文档,并了解了Spring Security的一般体系结构,但是无法找到解决此问题的最佳方法。

这是我的春季安全过滤器逻辑

@EnableWebFluxSecurity
public class SecurityConfig {
    @Bean
    @Profile("oauth")
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        return addAuthZ(http)
            .oauth2Login()
            .and().build();
    }

    private ServerHttpSecurity addAuthZ(ServerHttpSecurity http) {
        return http.authorizeExchange()
                .anyExchange().authenticated().and();
    }
}

这是配置,我正在使用示例OAuth2提供程序Google和Facebook,以及使用CAS提供的自定义OAuth2提供程序

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: SET_ME
            client-secret: SET_ME
          facebook:
            client-id: SET_ME
            client-secret: SET_ME
          sgd-authn:
            provider: sgd-authn
            client-id: SET_ME
            client-secret: SET_ME
            scope: openid
            client-authentication-method: secret
            authorization-grant-type: authorization_code
            #redirect-uri: "{baseUrl}/oauth2/
            redirect-uri-template: "{baseUrl}/{action}/oauth2/code/{registrationId}"
        provider:
          #  These are needed for talking to CAS OIDC
          sgd-authn:
            authorization-uri: ${cas.url}/oidc/authorize
            token-uri: ${cas.url}/oidc/accessToken
            jwk-set-uri: ${cas.url}/oidc/jwks
            user-info-uri: ${cas.url}/oidc/profile
            user-name-attribute: sub

1 个答案:

答案 0 :(得分:0)

好吧,我设法通过选项2使工作正常,在身份验证之后添加了过滤器。

  • 编写一个过滤器,该过滤器将采用身份验证(OAuth),通过某种逻辑运行该过滤器,并返回一个新的身份验证对象,该对象是特定超类的实例

  • 如果我们无法查看OAuth2详细信息,请安排此方法返回isAuthenticated = false。

  • 为NotAuthenticated编写定制处理程序

使用以下方式注册过滤器:

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(
            ServerHttpSecurity http,
            WebFilter proxyAuthFilter
    ) {
        return http.addFilterAt(proxyAuthFilter, SecurityWebFiltersOrder.AUTHENTICATION); // Do configuration ...
    }

过滤器是这样的:

public class ProxyAuthFilter implements WebFilter {
    static private Logger LOGGER = LoggerFactory.getLogger(ProxyAuthFilter.class);

    private final AuthZClientReactive authZClient;

    public ProxyAuthFilter(AuthZClientReactive authZClient) {
        this.authZClient = authZClient;
    }



    /**
     * Process the Web request and (optionally) delegate to the next
     * {@code WebFilter} through the given {@link WebFilterChain}.
     *
     * @param exchange the current server exchange
     * @param chain    provides a way to delegate to the next filter
     * @return {@code Mono<Void>} to indicate when request processing is complete
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return ReactiveSecurityContextHolder.getContext()
                .flatMap(this::transform)
                .then(chain.filter(exchange));
    }

    private Mono<Authentication> transform(SecurityContext securityContext) {
        Authentication authentication = securityContext.getAuthentication();
        if (authentication.isAuthenticated()) {
            return authenticate(authentication)
                    .map(a -> {
                        securityContext.setAuthentication(a);
                        return a;
                    });
        } else {
            LOGGER.info("ProxyFilter - not authenticated {}", authentication);
            return Mono.just(authentication);
        }
    }

    //  Runs the chain, then returns a Mono with the exchange object which completes
    //  when the auth header is added to the request.
    private Mono<Authentication> authenticate(Authentication authentication) {
        LOGGER.info("ProxyFilter - Checking authorisation for {}", authentication);
        Mono<AuthTokenInfo.Builder> authInfo = null;
        //  Catch if this filter runs twice
        if (authentication instanceof ProxyAuthentication) {
            LOGGER.info("ProxyAuthentication already found");
            return Mono.just(authentication);
        } else if (authentication instanceof UsernamePasswordAuthenticationToken) {
            //  If lookup is successful, returns instance of  ProxyAuthentication
            return getUsernamePasswordAuth((UsernamePasswordAuthenticationToken) authentication);
        } else if (authentication instanceof OAuth2AuthenticationToken) {
            //  If lookup is successful, returns instance of  ProxyAuthentication
            return getOAuthAuth((OAuth2AuthenticationToken) authentication);
        } else {
            LOGGER.info("Unknown principal {}", authentication);
            //  Signals a failed authentication, can be picked up by error page
            //  to display bespoke information 
            return Mono.just(new ProxyAuthenticationNotAuthenticated(
                    authentication, ProxyAuthenticationNotAuthenticated.Reason.UnknownAuthenticationType)
            );
        }
    }