Spring Security 5のOAuth 2.0 Login
ものすごく大雑把に言ってしまえば、Spring Security 5のOAuth 2.0 Loginは以下だけで外部IdP(下記はGitHub)と連携してログインを実装できます。
####################################################################################################
# spring
####################################################################################################
spring:
security.oauth2.client.registration:
github:
client-id: xxxxx
client-secret: xxxxx
で、そのときにユーザの情報も取得できるのですが、GoogleとGitHubアカウントでのログインの実装をしたところ、GitHubだけメールアドレスが取得できないという問題が起きました。
なぜ?
CommonOAuth2Providerを見ると、GitHubの場合はhttps://api.github.com/user
のAPIを実行することでアカウント情報を取得しているのですが、このエンドポイントではGitHubでpublicになっているemailだけが取得できます。
なので、単純にログインしてOAuth2AuthenticationTokenからemailを取得しようとすると、アカウントがメールアドレスを公開していない場合、emailの値がnullになります。
どうするか
GitHubにはhttps://api.github.com/user/emails
というprivateなemailも取得できるAPIがあります。
なので、以下の流れでGitHubからメールアドレスを取得してみました。
- Spring Security 5のOAuth 2.0 Loginでログインし、Authenticationを取得
- Authenticationからアクセストークンを取得
- トークンをRestTemplateにセットして、
https://api.github.com/user/emails
からメールアドレスを取得
参考:
https://stackoverflow.com/questions/35373995/github-user-email-is-null-despite-useremail-scope
実装
application.yml
####################################################################################################
# spring
####################################################################################################
spring:
security.oauth2.client.registration:
github:
client-id: xxxxx
client-secret: xxxxx
scope: user:email
追加でscopeとしてuser:email
を指定する必要があります。
Controllerの実装
package work.inabajun.redman.web;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
/**
* Controller for login.
*/
@Controller
@Slf4j
public class LoginController {
@Autowired
private OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
@GetMapping("/")
@ResponseBody
public String index(final OAuth2AuthenticationToken authentication) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
// ログイン時に取得した情報を表示(https://api.github.com/user/emails)
log.info(objectMapper.writeValueAsString(authentication.getPrincipal().getAttributes()));
OAuth2AuthorizedClient client =
oAuth2AuthorizedClientService.loadAuthorizedClient(
authentication.getAuthorizedClientRegistrationId(),
authentication.getName());
// トークンを取得
String accessToken = client.getAccessToken().getTokenValue();
RestTemplate restTemplate = new RestTemplate();
// RestTemplateにトークンをセット
restTemplate.getInterceptors()
.add(getBearerTokenInterceptor(accessToken));
// メールアドレスを取得(https://api.github.com/user/emails)
GitHubEmail[] forObject = restTemplate.getForObject("https://api.github.com/user/emails", GitHubEmail[].class);
log.info(objectMapper.writeValueAsString(forObject));
return "test";
}
private ClientHttpRequestInterceptor getBearerTokenInterceptor(String accessToken) {
ClientHttpRequestInterceptor interceptor =
(request, bytes, execution) -> {
request.getHeaders().add("Authorization", "Bearer " + accessToken);
return execution.execute(request, bytes);
};
return interceptor;
}
/**
* Response for https://api.github.com/user/emails.
*/
@Value
static class GitHubEmail{
private String email;
private boolean verified;
private boolean primary;
private String visibility;
}
}
トークンを取得してRestTemplateにセットする流れは以下を参考にさせていただきました。
参考:Using Spring Security 5 to integrate with OAuth 2-secured services such as Facebook and GitHub
まとめ
- Spring Security 5のOAuth 2.0 LoginはGitHubをIdPに指定するとメールアドレスが取得できない可能性がある
- 別のprivateなメールアドレスを取得できるAPIがあるのでそちらから別途取得可能