Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

@inabajunmr

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があるのでそちらから別途取得可能
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
4
Help us understand the problem. What are the problem?