Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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があるのでそちらから別途取得可能
inabajunmr
なんか間違ってたら教えてください
http://inabajunmr.hatenablog.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした