Spring SecurityとOpenID Connect

1概要

このクイックチュートリアルでは、Spring Security OAuth 2実装を使ったOpenID Connectの設定に焦点を当てます。

OpenID Connect は、OAuth 2.0プロトコルの上に構築された単純なID層です。

そして、より具体的には、https://developers.google.com/identity/protocols/OpenIDConnect[OpenID Connectの実装] https://developers.google.com/identity/protocols/を使用してユーザーを認証する方法を学びます。 OpenIDConnect[Google]

2 Mavenの設定

まず、Spring Bootアプリケーションに次の依存関係を追加する必要があります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

3 IDトークン

実装の詳細に入る前に、OpenIDがどのように機能するのか、そしてどのようにしてそれと対話するのかを簡単に見てみましょう。

OpenIDはOAuthの上に構築されているため、この時点では、もちろんOAuth2を既に理解していることが重要です。

まず、識別機能を使用するために、 openid という新しいOAuth2スコープを使用します。 ** これにより、アクセストークンに " id token __"というフィールドが追加されます。

id token__は、IDプロバイダー(この場合はGoogle)によって署名された、ユーザーに関するID情報を含むJWT(JSON Web Token)です。

最後に、 server(Authorization Code) implicit の両方のフローが id token__を取得するために最も一般的に使用される方法です。この例では、 server flow を使用します。

3 OAuth2クライアント設定

次に、OAuth 2クライアントを次のように設定しましょう。

@Configuration
@EnableOAuth2Client
public class GoogleOpenIdConnectConfig {
    @Value("${google.clientId}")
    private String clientId;

    @Value("${google.clientSecret}")
    private String clientSecret;

    @Value("${google.accessTokenUri}")
    private String accessTokenUri;

    @Value("${google.userAuthorizationUri}")
    private String userAuthorizationUri;

    @Value("${google.redirectUri}")
    private String redirectUri;

    @Bean
    public OAuth2ProtectedResourceDetails googleOpenId() {
        AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
        details.setClientId(clientId);
        details.setClientSecret(clientSecret);
        details.setAccessTokenUri(accessTokenUri);
        details.setUserAuthorizationUri(userAuthorizationUri);
        details.setScope(Arrays.asList("openid", "email"));
        details.setPreEstablishedRedirectUri(redirectUri);
        details.setUseCurrentUri(false);
        return details;
    }

    @Bean
    public OAuth2RestTemplate googleOpenIdTemplate(OAuth2ClientContext clientContext) {
        return new OAuth2RestTemplate(googleOpenId(), clientContext);
    }
}

そしてここに application.properties があります:

google.clientId=<your app clientId>
google.clientSecret=<your app clientSecret>
google.accessTokenUri=https://www.googleapis.com/oauth2/v3/token
google.userAuthorizationUri=https://accounts.google.com/o/oauth2/auth
google.redirectUri=http://localhost:8081/google-login

ご了承ください:

  • あなたは最初にあなたのGoogleウェブアプリのためにOAuth 2.0の資格情報を取得する必要があります

  • id token を得るためにスコープ openid__を使いました。

  • ユーザーの電子メールを含めるために追加のスコープ email を使用しました。

id token__識別情報。

GoogleのWebアプリで使用されています。

4カスタムOpenID Connectフィルタ

次のように、 id token から認証を抽出するために、独自のカスタム OpenIdConnectFilter__を作成する必要があります。

public class OpenIdConnectFilter extends AbstractAuthenticationProcessingFilter {

    public OpenIdConnectFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
        setAuthenticationManager(new NoopAuthenticationManager());
    }
    @Override
    public Authentication attemptAuthentication(
      HttpServletRequest request, HttpServletResponse response)
      throws AuthenticationException, IOException, ServletException {
        OAuth2AccessToken accessToken;
        try {
            accessToken = restTemplate.getAccessToken();
        } catch (OAuth2Exception e) {
            throw new BadCredentialsException("Could not obtain access token", e);
        }
        try {
            String idToken = accessToken.getAdditionalInformation().get("id__token").toString();
            String kid = JwtHelper.headers(idToken).get("kid");
            Jwt tokenDecoded = JwtHelper.decodeAndVerify(idToken, verifier(kid));
            Map<String, String> authInfo = new ObjectMapper()
              .readValue(tokenDecoded.getClaims(), Map.class);
            verifyClaims(authInfo);
            OpenIdConnectUserDetails user = new OpenIdConnectUserDetails(authInfo, accessToken);
            return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
        } catch (InvalidTokenException e) {
            throw new BadCredentialsException("Could not obtain user details from token", e);
        }
    }
}

そして、これが私たちのシンプルな OpenIdConnectUserDetails です:

public class OpenIdConnectUserDetails implements UserDetails {
    private String userId;
    private String username;
    private OAuth2AccessToken token;

    public OpenIdConnectUserDetails(Map<String, String> userInfo, OAuth2AccessToken token) {
        this.userId = userInfo.get("sub");
        this.username = userInfo.get("email");
        this.token = token;
    }
}

ご了承ください:

  • Spring Securityの JwtHelper id token__をデコードする。

  • id token は常に一意の識別子である " sub" __フィールドを含みます

ユーザーのために。

email スコープを追加したので、 id token にも " email__"フィールドが含まれます。

私たちの要求に。

4.1. IDトークンの確認

上記の例では、 JwtHelper decodeAndVerify() メソッドを使用して id tokenから情報を抽出しましたが、それを検証するためにも使用しました。

そのための最初のステップは、https://developers.google.com/identity/protocols/OpenIDConnect#discovery[Google Discovery]ドキュメントで指定されているいずれかの証明書で署名されていることを確認することです。

これらは1日に1回変わるので、それらを読むにはhttps://search.maven.org/classic/#search%7Cga%7C1%7Cjwks[jwks-rsa]というユーティリティライブラリを使用します。

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>jwks-rsa</artifactId>
    <version>0.3.0</version>
</dependency>

証明書を含むURLを application.properties ファイルに追加しましょう。

google.jwkUrl=https://www.googleapis.com/oauth2/v2/certs

これで、このプロパティを読んで RSAVerifier オブジェクトを構築できます。

@Value("${google.jwkUrl}")
private String jwkUrl;

private RsaVerifier verifier(String kid) throws Exception {
    JwkProvider provider = new UrlJwkProvider(new URL(jwkUrl));
    Jwk jwk = provider.get(kid);
    return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
}

最後に、デコードされたIDトークンのクレームも確認します。

public void verifyClaims(Map claims) {
    int exp = (int) claims.get("exp");
    Date expireDate = new Date(exp **  1000L);
    Date now = new Date();
    if (expireDate.before(now) || !claims.get("iss").equals(issuer) ||
      !claims.get("aud").equals(clientId)) {
        throw new RuntimeException("Invalid claims");
    }
}

verifyClaims() メソッドは、idトークンがGoogleによって発行されたもので、有効期限が切れていないことを確認しています。

この詳細については、https://developers.google.com/identity/protocols/OpenIDConnect#validatinganidtoken[Googleのドキュメント]を参照してください。

5セキュリティ設定

次に、セキュリティ設定について説明しましょう。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private OAuth2RestTemplate restTemplate;

    @Bean
    public OpenIdConnectFilter openIdConnectFilter() {
        OpenIdConnectFilter filter = new OpenIdConnectFilter("/google-login");
        filter.setRestTemplate(restTemplate);
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .addFilterAfter(new OAuth2ClientContextFilter(),
          AbstractPreAuthenticatedProcessingFilter.class)
        .addFilterAfter(OpenIdConnectFilter(),
          OAuth2ClientContextFilter.class)
        .httpBasic()
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/google-login"))
        .and()
        .authorizeRequests()
        .anyRequest().authenticated();
    }
}

ご了承ください:

  • カスタム OpenIdConnectFilter を後に追加しました

OAuth2ClientContextFilter ** 私達はユーザーをにリダイレクトするために簡単なセキュリティ設定を使用

Googleによる認証を受けるには「 /google-login

6. ユーザーコントローラー

次に、これが私たちのアプリをテストするためのシンプルなコントローラーです。

@Controller
public class HomeController {
    @RequestMapping("/")
    @ResponseBody
    public String home() {
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        return "Welcome, " + username;
    }
}

回答例(Googleにリダイレクトしてアプリ権限を承認した後):

Welcome,[email protected]----

[[sample]]

===  **  7.  OpenID Connectプロセスのサンプル**

最後に、OpenID Connect認証プロセスのサンプルを見てみましょう。

まず、** 認証リクエスト** を送信します。

[source,bash,gutter:,true]

https://accounts.google.com/o/oauth2/auth? client id=sampleClientID response type=code& scope=openid%20email& redirect__uri=http://localhost:8081/google-login& state=abc

応答(** ユーザー承認後** )は、次のものへのリダイレクトです。

[source,bash,gutter:,true]
次に、__code__をアクセストークンと__id__token__に交換します。

[source,bash,gutter:,true]

POST https://www.googleapis.com/oauth2/v3/token code=xyz& client id= sampleClientID& client secret= sampleClientSecret& redirect uri=http://localhost:8081/google-login& grant type=authorization__code

これがサンプルのレスポンスです。

[source,java,gutter:,true]

{ "access token": "SampleAccessToken", "id token": "SampleIdToken", "token type": "bearer", "expires in": 3600, "refresh__token": "SampleRefreshToken" }

最後に、実際の__id__token__の情報は次のようになります。

[source,bash,gutter:,true]

{ "iss":"accounts.google.com", "at hash":"AccessTokenHash", "sub":"12345678", "email verified":true, "email":"[email protected]", …​ }

そのため、トークン内のユーザー情報が私たち自身のアプリケーションに識別情報を提供するのにどれほど有用であるかがすぐにわかります。

===  **  8結論**

このクイックイントロチュートリアルでは、GoogleのOpenID Connect実装を使ってユーザーを認証する方法を学びました。

そして、いつものように、あなたはソースコードhttps://github.com/eugenp/tutorials/tree/master/spring-security-openid[GitHubについて]を見つけることができます。