このコンテンツは古く、レガシーOAuthスタックを使用していることに注意してください。 SpringSecurityの最新のOAuthサポートをご覧ください。

1. 概要

このクイックチュートリアルでは、SpringSecurityOAuth2実装を使用したOpenIDConnectのセットアップに焦点を当てます。

OpenID Connect は、OAuth2.0プロトコルの上に構築されたシンプルなIDレイヤーです。

さらに具体的には、 GoogleのOpenIDConnect実装を使用してユーザーを認証する方法を学習します。

2. Maven構成

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

<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とどのように相互作用するかを簡単に見てみましょう。

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

まず、ID機能を使用するために、openidと呼ばれる新しいOAuth2スコープを利用します。 これにより、アクセストークンに「id_token」という追加のフィールドが作成されます。

id_token はJWT(JSON Web Token)であり、IDプロバイダー(この場合はGoogle)によって署名されたユーザーに関するID情報が含まれています。

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

3. OAuth2クライアント構成

次に、OAuth2クライアントを次のように構成しましょう。

@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 DevelopersConsoleからGoogleWebアプリのOAuth2.0クレデンシャルを取得する必要があります。
  • スコープopenidを使用して、id_tokenを取得しました。
  • また、追加のスコープ email を使用して、id_tokenID情報にユーザーの電子メールを含めました。
  • リダイレクトURIhttp:// localhost:8081 / google-login は、Googleウェブアプリで使用されているものと同じです。

4. カスタムOpenID接続フィルター

次に、独自のカスタム OpenIdConnectFilter を作成して、 id_token から認証を抽出する必要があります–次のようになります。

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;
    }
}

ご了承ください:

  • id_tokenをデコードするためのSpringSecurityJwtHelper
  • id_token には、ユーザーの一意の識別子である「sub」フィールドが常に含まれています。
  • id_token には、 email スコープをリクエストに追加したため、「email」フィールドも含まれます。

4.1. IDトークンの確認

上記の例では、 JwtHelperdecodeAndVerify()メソッドを使用して、 id_token、から情報を抽出するだけでなく、それを検証しました。

このための最初のステップは、 GoogleDiscoveryドキュメントで指定されている証明書の1つで署名されていることを確認することです。

これらは1日に約1回変更されるため、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によって発行され、有効期限が切れていないことを確認しています。

詳細については、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();
    }
}

ご了承ください:

  • OAuth2ClientContextFilterの後にカスタムOpenIdConnectFilterを追加しました
  • シンプルなセキュリティ構成を使用して、ユーザーを「 / google-login 」にリダイレクトし、Googleによる認証を受けました。

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

次に、アプリをテストするための簡単なコントローラーを次に示します。

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

サンプルの応答(アプリの権限を承認するためにGoogleにリダイレクトした後):

Welcome, [email protected]

7. OpenID接続プロセスのサンプル

最後に、OpenIDConnect認証プロセスの例を見てみましょう。

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

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

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

http://localhost:8081/google-login?state=abc&code=xyz

次に、コードをアクセストークンとid_tokenに交換します。

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

応答の例は次のとおりです。

{
    "access_token": "SampleAccessToken",
    "id_token": "SampleIdToken",
    "token_type": "bearer",
    "expires_in": 3600,
    "refresh_token": "SampleRefreshToken"
}

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

{
    "iss":"accounts.google.com",
    "at_hash":"AccessTokenHash",
    "sub":"12345678",
    "email_verified":true,
    "email":"[email protected]",
     ...
}

したがって、トークン内のユーザー情報が、独自のアプリケーションにID情報を提供するのにどれほど役立つかをすぐに確認できます。

8. 結論

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

そして、いつものように、GitHubでソースコードを見つけることができます。