Spring Security 5のOAuth 2.0 LoginでGitHubを利用したときにemailがnullになる問題をなんとかする

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になります。

参考:CommonOAuth2Provider

どうするか

GitHubにはhttps://api.github.com/user/emailsというprivateなemailも取得できるAPIがあります。

なので、以下の流れでGitHubからメールアドレスを取得してみました。

  1. Spring Security 5のOAuth 2.0 Loginでログインし、Authenticationを取得
  2. Authenticationからアクセストークンを取得
  3. トークンを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があるのでそちらから別途取得可能
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.