Spring SecurityとOpenID Connect

Spring SecurityとOpenID Connect

1. 概要

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

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

さらに具体的には、OpenID Connect implementation fromGoogleを使用してユーザーを認証する方法を学習します。

2. Mavenの構成

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


    org.springframework.boot
    spring-boot-starter-security


    org.springframework.security.oauth
    spring-security-oauth2

3. IDトークン

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

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

まず、ID機能を使用するために、openidと呼ばれる新しいOAuth2スコープを利用します。 This will result in an extra field in our Access Token – “id_token“.

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

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

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=
google.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 Developers ConsoleからGoogleウェブアプリのOAuth2.0認証情報を取得する必要があります。

  • スコープopenidを使用してid_tokenを取得しました。

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

  • リダイレクトURIhttp://localhost:8081/google-loginは、Googleウェブアプリで使用されているものと同じです。

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

次に、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 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 userInfo, OAuth2AccessToken token) {
        this.userId = userInfo.get("sub");
        this.username = userInfo.get("email");
        this.token = token;
    }
}

ご了承ください:

  • id_tokenをデコードするためのSpring SecurityJwtHelper

  • id_tokenには、ユーザーの一意の識別子である「sub”」フィールドが常に含まれます。

  • emailスコープをリクエストに追加したため、id_tokenには「email」フィールドも含まれます。

4.1. IDトークンの確認

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

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

これらは1日に約1回変更されるため、jwks-rsaというユーティリティライブラリを使用して読み取ります。


    com.auth0
    jwks-rsa
    0.3.0

証明書を含む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 documentationにあります。

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認証プロセスのサンプルを見てみましょう。

まず、Authentication Requestを送信します。

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

応答(after user approval)は次へのリダイレクトです:

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

次に、codeをアクセストークンと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のOpenID Connect実装を使用してユーザーを認証する方法を学びました。

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