良い一日、
SSO と API Gateway パターンを実装する実際の例をセットアップしました (ここで説明されているものと同様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 で実装) で、JWT を使用する独自の OAuth サーバーを含む複数の 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 を戻します。JWT トークンを発行する AUTH-SERVER は期待どおりに機能し、リソース データと 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 リソースを構成するにはどうすればよいですか? リソース サーバーはどのリソースを使用するかをどのように認識しますか?