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トークンの確認
上記の例では、JwtHelperのdecodeAndVerify()メソッドを使用して、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を見つけることができます。