6

再会,

我已经设置了一个实现 SSO 和 API 网关模式的工作示例(类似于此处描述的内容https://spring.io/guides/tutorials/spring-security-and-angular-js/#_the_api_gateway_pattern_angular_js_and_spring_security_part_iv)。

该系统由独立的服务器组件组成:AUTH-SERVER、API-GATEWAY、SERVICE-DISCOVERY、RESOURCE/UI SERVER。

在 API-GATEWAY(使用 Spring Boot @EnableZuulProxy @EnableOAuth2Sso 实现)我配置了多个 OAuth 提供程序,包括我自己的使用 JWT 的 OAuth 服务器:

security:
  oauth2:
    client:
      accessTokenUri: http://localhost:9999/uaa/oauth/token
      userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
      clientId: acme
      clientSecret: acmesecret
      redirectUri: http://localhost:9000/login
  resource:
    jwt:
      key-value: |
      -----BEGIN PUBLIC KEY-----
      ...public-key...
      -----END PUBLIC KEY-----

facebook:
  client:
    clientId: 233668646673605
    clientSecret: 33b17e044ee6a4fa383f46ec6e28ea1d
    accessTokenUri: https://graph.facebook.com/oauth/access_token
    userAuthorizationUri: https://www.facebook.com/dialog/oauth
    tokenName: oauth_token
    authenticationScheme: query
    clientAuthenticationScheme: form
    redirectUri: http://localhost:8080
  resource:
    userInfoUri: https://graph.facebook.com/me

github:
  client:
    clientId: bd1c0a783ccdd1c9b9e4
    clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
    accessTokenUri: https://github.com/login/oauth/access_token
    userAuthorizationUri: https://github.com/login/oauth/authorize
    clientAuthenticationScheme: form
  resource:
    userInfoUri: https://api.github.com/user

google:
  client:
    clientId: 1091750269931-152sv64o8a0vd5hg8v2lp92qd2d4i00r.apps.googleusercontent.com
    clientSecret: n4I4MRNLKMdv603SU95Ic9lJ
    accessTokenUri: https://www.googleapis.com/oauth2/v3/token
    userAuthorizationUri: https://accounts.google.com/o/oauth2/auth
    authenticationScheme: query
    redirectUri: http://localhost:9000/login/google
    scope:
      - email
      - profile
  resource:
    userInfoUri: https://www.googleapis.com/oauth2/v2/userinfo

Java配置:

package com.devdream.cloud.apigateway;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateCustomizer;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.CompositeFilter;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;

@SpringBootApplication
@EnableZuulProxy
@EnableOAuth2Sso
public class APIGatewayApplication extends WebSecurityConfigurerAdapter {

    public static void main(String[] args) {
        SpringApplication.run(APIGatewayApplication.class, args);
    }

    @Autowired
    OAuth2ClientContext oauth2ClientContext;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http//
        .logout()
                //
                .and()
                //
                .antMatcher("/**")
                //
                .authorizeRequests()
                //
                .antMatchers("/index.html", "/home.html", "/login", "/stomp/**")
                .permitAll()
                //
                .anyRequest()
                .authenticated()
                //
                .and()
                //
                .csrf()
                //
                .csrfTokenRepository(csrfTokenRepository()).and()
                .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class).headers()
                .frameOptions().sameOrigin()//
                .and()//
                .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
    }

    private Filter csrfHeaderFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request,
                    HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
                CsrfToken csrf = (CsrfToken) request
                        .getAttribute(CsrfToken.class.getName());
                if (csrf != null) {
                    Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
                    String token = csrf.getToken();
                    if (cookie == null || token != null
                            && !token.equals(cookie.getValue())) {
                        cookie = new Cookie("XSRF-TOKEN", token);
                        cookie.setPath("/");
                        response.addCookie(cookie);
                    }
                }
                filterChain.doFilter(request, response);
            }
        };
    }

    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        repository.setParameterName("X-XSRF-TOKEN");
        return repository;

    }

    private Filter ssoFilter() {
        CompositeFilter filter = new CompositeFilter();
        List<Filter> filters = new ArrayList<>();
        filters.add(ssoFilter(facebook(), "/login/facebook"));
        filters.add(ssoFilter(github(), "/login/github"));
        filters.add(ssoFilter(google(), "/login/google"));
        filter.setFilters(filters);
        return filter;
    }

    private Filter ssoFilter(ClientResources client, String path) {
        OAuth2ClientAuthenticationProcessingFilter oAuth2Filter = new OAuth2ClientAuthenticationProcessingFilter(
                path);
        OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(
                client.getClient(), oauth2ClientContext);
        oAuth2Filter.setRestTemplate(oAuth2RestTemplate);
        oAuth2Filter.setTokenServices(new UserInfoTokenServices(client
                .getResource().getUserInfoUri(), client.getClient()
                .getClientId()));
        return oAuth2Filter;
    }

    @Bean
    @ConfigurationProperties("github")
    ClientResources github() {
        return new ClientResources();
    }

    @Bean
    @ConfigurationProperties("facebook")
    ClientResources facebook() {
        return new ClientResources();
    }

    @Bean
    @ConfigurationProperties("google")
    ClientResources google() {
        return new ClientResources();
    }

    @Bean
    public FilterRegistrationBean oauth2ClientFilterRegistration(
            OAuth2ClientContextFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(filter);
        registration.setOrder(-100);
        return registration;
    }
}

当未经身份验证的请求发送到网关时,请求会按预期重定向到 AUTH-SERVER,这里我提供使用我的 AUTH-SERVER 登录的选项以及上面配置的社交选项,方法是提供一个链接,该链接基本上需要用户返回要被上面配置的相关 OAuth 过滤器路径拦截的 API-GATEWAY。我的 AUTH-SERVER 发出 JWT 令牌按预期工作,为我的资源数据和 ui 提供服务,但是当我成功使用 Google 进行身份验证时,我从资源/ui 服务器收到以下响应:

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<oauth>
<error_description>Cannot convert access token to JSON</error_description>
<error>invalid_token</error>
</oauth>

然后我意识到这可能是由于资源服务器的 OAuth 配置?

security:
  oauth2:
    client:
      client-id: acme
      client-secret: acmesecret
    resource:
      jwt:
        key-value: |
          -----BEGIN PUBLIC KEY-----
          ...public-key...
          -----END PUBLIC KEY-----

资源服务器如何知道解码外部 OAuth 提供者发送的令牌?资源服务器可以配置多个OAuth2客户端吗?我在这里的想法有缺陷吗?

在调试对资源服务器的请求后,我在 org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter 类中发现了 Google 发送的令牌值:

ya29..xwLBw5mz3XoTo-xuaSGbwhuE3_wqtAwL8tP7sGe5wMRvChk6pxeH8CpPnPg83OlbnA

令牌中似乎没有有效负载?

我还看到正在使用的验证器正在使用上面配置的 jwt 键值。

我将如何配置多个资源服务器 oauth 资源以及资源服务器如何知道要使用哪一个?

4

0 回答 0