第1回：Spring Security 5でサポートされるOAuth 2.0 LoginをSpring Bootで使ってみる

Spring Security 5の目玉であるOAuth 2.0(& OpenID Connect 1.0)の認可サーバと連携したログイン機能を、Spring Boot上で使う方法を紹介します。（今回はSpring Boot上で使う方法の紹介だけにとどめます→仕組みの話は乞うご期待！！）

# 動作検証バージョン

* Spring Boot 2.0.0.M7
* Spring Security 5.0.0.RELEASE
* Tomcat 8.5.23

> **NOTE:**
>
> 2017/12/2 : Spring Boot 2.0.0.M7 (Spring Security 5.0.0.RELEASE)ベースに書き換えました。

# OAuthプロバイダ

* GitHub

> **Note:**
>
> ちなみに・・・Spring Securityは、GitHubの他に、Google、Facebook、Oktaをデフォルトでサポートしています。

# デモアプリケーションの画面遷移

以下の画面遷移をもつデモアプリケーションを作ります。なお、以下の画面遷移は、デモアプリケーションとGitHubにログインしていない時の遷移になります。（本エントリーでは、ログインしている状態での遷移の説明は割愛します）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/117313/6c234d84-47ad-d79e-227d-34e0a14f25d5.png)

# 開発プロジェクトを作る

まず、[SPRING INITIALIZR](https://start.spring.io/)にて、Dependenciesに「Web」「Security」「Thymeleaf」を選択してプロジェクトを作成します。（本エントリではMavenプロジェクト前提での説明になります）
次に、SPRING INITIALIZRで作成したプロジェクトに、「spring-security-oauth2-client」「spring-security-oauth2-jose」「thymeleaf-extras-springsecurity4」を追加します。

```xml:pom.xml
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
```

# Controllerを作る

開発プロジェクトの準備ができたら、「Index Page」と「User Attributes Page」を表示するためのControllerクラスを作ります。

今回のデモアプリケーションでは、

* 「Index Page」を表示するためのエンドポイント: `GET /`
* ログイン時のユーザ属性を表示するためのエンドポイント: `GET /attributes`
* 最新のユーザ属性を表示するためのエンドポイント: `GET /attributes/latest`

を作成します。


```java:src/main/java/com/example/demooauth2login/DemoController.java

package com.example.demooauth2login;

import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.Map;

@Controller
public class DemoController {

  private final RestOperations restOperations = new RestTemplate();

  private final OAuth2AuthorizedClientService authorizedClientService;

  // 認可済みのクライアント情報(クライアント情報、認可したリソースオーナ名、アクセストークンなど)は
  // OAuth2AuthorizedClientService経由で取得できる
  public DemoController(OAuth2AuthorizedClientService authorizedClientService) {
    this.authorizedClientService = authorizedClientService;
  }

  @GetMapping("/")
  public String index(OAuth2AuthenticationToken authentication, Model model) {
    // 画面に表示するために、OAuth2AuthorizedClientService経由で認可済みのクライアント情報を取得しModelに格納
    model.addAttribute("authorizedClient", this.getAuthorizedClient(authentication));
    return "index";
  }

  @GetMapping("/attributes")
  public String userAttributeAtLogin(@AuthenticationPrincipal OAuth2User oauth2User, Model model) {
    // 認証が制した時点のユーザ属性であれば、プロバイダに問い合わせなくても認証情報から取得できる。
    // 注：あくまで認証時点での情報なので、情報は古くなっている可能性がある
    model.addAttribute("attributes", oauth2User.getAttributes());
    return "userinfo";
  }

  @GetMapping("/attributes/latest")
  public String userLatestAttribute(OAuth2AuthenticationToken authentication, Model model) {
    // 最新のユーザ属性が必要な場合は、認証時に取得したアクセストークンを付与してプロバイダから再取得する
    // ここでは、Spring Framework提供のRestTemplateを使用してユーザ属性の取得を行っている。
    // ちなみに・・・Spring Securityのデフォルト実装では、「Nimbus OAuth 2.0 SDK」を使用してアクセストークン及びユーザ属性の取得を行っている。
    OAuth2AuthorizedClient authorizedClient = this.getAuthorizedClient(authentication);
    String userInfoUri = authorizedClient.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri();
    RequestEntity<Void> requestEntity = RequestEntity.get(URI.create(userInfoUri))
        .header(HttpHeaders.AUTHORIZATION, "Bearer " + authorizedClient.getAccessToken().getTokenValue())
        .build();
    model.addAttribute("attributes", restOperations.exchange(requestEntity, Map.class).getBody());
    return "userinfo";
  }

  private OAuth2AuthorizedClient getAuthorizedClient(OAuth2AuthenticationToken authentication) {
    return this.authorizedClientService.loadAuthorizedClient(
        authentication.getAuthorizedClientRegistrationId(), authentication.getName());
  }

}
```

# View(ThymeleafのHTMLテンプレート)を作る

「Index Page」と「User Attributes Page」を表示するViewを作ります。なお、今回のデモアプリケーションでは、ViewにはThymeleafを使います。

## Index Page

「Index Page」には、

* 認証ユーザのユーザ名(=リソースオーナの識別子)
* 認証ユーザの氏名(=リソースオーナの氏名)
* 認可済みクライアント情報からクライアント登録情報のクライアント名（=プロバイダを区別する名前）
* 「User Attributes Page」を表示するためのリンク
* ログアウトボタン

を表示及び配置します。


```html:src/main/resources/templates/index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
  <title>Demo Application OAuth 2.0 Login with Spring Security 5</title>
  <meta charset="utf-8"/>
</head>
<body>
<div style="float: right" th:fragment="logout" sec:authorize="isAuthenticated()">
  <div style="float:left">
    <span style="font-weight:bold">User: </span><span sec:authentication="name">Taro Yamada</span>

  <div style="float:left">
    <span style="font-weight:bold">User Id:</span>
    <span sec:authentication="name">1000000</span> <!-- 認証ユーザのユーザ名(ユーザの識別子) -->
    <span style="font-weight:bold">User Name:</span>
    <span sec:authentication="principal.attributes['name']">Taro Yamada</span> <!-- 認証ユーザの氏名 -->
  </div>
  <div style="float:none">&nbsp;</div>
  <div style="float:right">
    <form action="#" th:action="@{/logout}" method="post">
      <input type="submit" value="Logout"/> <!-- ログアウトボタン -->
    </form>
  </div>
</div>
<h1>Welcome Demo Application OAuth 2.0 Login with Spring Security 5 !!</h1>
<div>
  You are successfully logged in via the OAuth 2.0 Client
  -<span style="font-weight:bold" th:text="${authorizedClient.clientRegistration.clientName}">Demo Client</span>- <!-- クライアント登録情報のクライアント名 -->
</div>
<div>&nbsp;</div>
<div>
  <!-- 「User Attributes Page」を表示するためのリンク -->
  <ul>
    <li>
      <a href="/templates/userinfo.html" th:href="@{/attributes}">Display User Attributes at login</a>
    </li>
    <li>
      <a href="/templates/userinfo.html" th:href="@{/attributes/latest}">Display latest User Attributes</a>
    </li>
  </ul>
</div>
</body>
</html>
```

## 「User Attributes Page」

「User Attributes Page」には、

* 認証ユーザのユーザ名(=リソースオーナの識別子)
* 認証ユーザの氏名(=リソースオーナの氏名)
* ログアウトボタン
* ユーザ属性の一覧

を表示及び配置します。

```html:src/main/resources/templates/userinfo.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Demo Application OAuth 2.0 Login with Spring Security 5 - User Attributes -</title>
  <meta charset="utf-8"/>
</head>

<body>
<div th:replace="index::logout"></div> <!-- 認証ユーザの名前、ログアウトボタン -->
<h1>Demo Application OAuth 2.0 Login with Spring Security 5 - User Attributes -</h1>
<div>
  <span style="font-weight:bold">User Attributes:</span>
  <!-- ユーザ属性の一覧 -->
  <ul>
    <li th:each="attribute : ${attributes}">
      <span style="font-weight:bold" th:text="${attribute.key}"></span>: <span th:text="${attribute.value}"></span>
    </li>
  </ul>
</div>
</body>
</html>
```

# 作成したアプリをGitHubに登録する

OAuth 2.0の仕組みを利用したLogin機能を使うためには、プロバイダ(=今回はGitHub)に作成した（する）アプリケーションを事前にクライアント登録し、クライアントIDとクライアントシークレットを取得しておく必要があります。
GitHubの場合は、[Registering OAuth Apps](https://developer.github.com/apps/building-integrations/setting-up-and-registering-oauth-apps/registering-oauth-apps/)に登録方法が紹介されています。

わたしは・・・

* "Application name" : `oauth-login-demo`
* "Homepage URL" : `http://kazuki43zoo.github.io/`
* "Application description" :
* "Authorization callback URL" : `http://localhost:8080/`

にしました。

# クライアントIDとクライアントシークレットをSpring Bootに設定する

GitHub上でクライアント登録が完了すると、クライアントIDとクライアントシークレットが画面に表示されるので、Spring BootのプロパティファイルにGitHub用の設定を追加します。

```properties:src/main/resources/application.properties
spring.security.oauth2.client.registration.github.clientId={your github client id}
spring.security.oauth2.client.registration.github.clientSecret={your github client secret}
```
yamlファイルで表現すると・・・

```yaml:src/main/resources/application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: {your github client id}
            client-secret: {your github client secret}
```

# ログインする

Spring Bootアプリケーションを起動し、GitHub連携してSpring Bootアプリケーションにログインしてみます。なお、冒頭で示した画面遷移になるようにするために、GitHubからログアウトしておいてください！！

## Spring Bootの起動

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v2.0.0.M7)

2017-12-02 16:55:55.838  INFO 88957 --- [           main] c.e.d.DemoOauth2LoginApplication         : Starting DemoOauth2LoginApplication on Kazuki-no-MacBook-Pro.local with PID 88957 (/Users/xxx/git/demo-oauth2-login/target/classes started by xxx in /Users/xxx/git/demo-oauth2-login)
2017-12-02 16:55:55.844  INFO 88957 --- [           main] c.e.d.DemoOauth2LoginApplication         : No active profile set, falling back to default profiles: default
2017-12-02 16:55:55.948  INFO 88957 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@359f7cdf: startup date [Sat Dec 02 16:55:55 JST 2017]; root of context hierarchy
2017-12-02 16:55:57.352  INFO 88957 --- [           main] o.h.v.i.engine.ValidatorFactoryImpl      : HV000238: Temporal validation tolerance set to 0.
2017-12-02 16:55:57.620  INFO 88957 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2017-12-02 16:55:57.631  INFO 88957 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2017-12-02 16:55:57.632  INFO 88957 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.23
2017-12-02 16:55:57.642  INFO 88957 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/xxx/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]
2017-12-02 16:55:57.714  INFO 88957 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-12-02 16:55:57.715  INFO 88957 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1773 ms
2017-12-02 16:55:57.862  INFO 88957 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-12-02 16:55:57.863  INFO 88957 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-12-02 16:55:57.863  INFO 88957 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-12-02 16:55:57.863  INFO 88957 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-12-02 16:55:57.864  INFO 88957 --- [ost-startStop-1] .s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
2017-12-02 16:55:57.864  INFO 88957 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-12-02 16:55:58.112  INFO 88957 --- [           main] o.h.v.i.engine.ValidatorFactoryImpl      : HV000238: Temporal validation tolerance set to 0.
2017-12-02 16:55:58.269  INFO 88957 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@359f7cdf: startup date [Sat Dec 02 16:55:55 JST 2017]; root of context hierarchy
2017-12-02 16:55:58.341  INFO 88957 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/],methods=[GET]}" onto public java.lang.String com.example.demooauth2login.DemoController.index(org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken,org.springframework.ui.Model)
2017-12-02 16:55:58.343  INFO 88957 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/attributes],methods=[GET]}" onto public java.lang.String com.example.demooauth2login.DemoController.userAttributeAtLogin(org.springframework.security.oauth2.core.user.OAuth2User,org.springframework.ui.Model)
2017-12-02 16:55:58.343  INFO 88957 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/attributes/latest],methods=[GET]}" onto public java.lang.String com.example.demooauth2login.DemoController.userLatestAttribute(org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken,org.springframework.ui.Model)
2017-12-02 16:55:58.350  INFO 88957 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-12-02 16:55:58.352  INFO 88957 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-12-02 16:55:58.386  INFO 88957 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-12-02 16:55:58.387  INFO 88957 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-12-02 16:55:58.431  INFO 88957 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-12-02 16:55:58.517  INFO 88957 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
2017-12-02 16:55:58.674  INFO 88957 --- [           main] b.a.s.AuthenticationManagerConfiguration : 

Using default security password: e4e9003d-5ef9-4b12-b6f2-f55d56053c21

2017-12-02 16:55:59.239  INFO 88957 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@70101687, org.springframework.security.web.context.SecurityContextPersistenceFilter@4bb003e9, org.springframework.security.web.header.HeaderWriterFilter@72e789cb, org.springframework.security.web.csrf.CsrfFilter@10ee04df, org.springframework.security.web.authentication.logout.LogoutFilter@b016b4e, org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@69e49a81, org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter@1b7cae6f, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@26f7cdf8, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@18eec010, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@58e92c23, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@70e889e9, org.springframework.security.web.session.SessionManagementFilter@43034809, org.springframework.security.web.access.ExceptionTranslationFilter@3157e4c0, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1fcf9739]
2017-12-02 16:55:59.346  INFO 88957 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-12-02 16:55:59.425  INFO 88957 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2017-12-02 16:55:59.431  INFO 88957 --- [           main] c.e.d.DemoOauth2LoginApplication         : Started DemoOauth2LoginApplication in 4.108 seconds (JVM running for 5.199)
```

## Index Pageの表示要求を行う

まず、ブラウザを開き「 http://localhost:8080/ 」にアクセスを試みます。すると・・・以下のようにログインを促す画面が表示されますが、こんな画面作ってませんよね？
細かい説明は今回は省きますが・・・この画面はSpring Securityのデフォルトログイン画面生成機能(具体的には[DefaultLoginPageGeneratingFilter](https://github.com/spring-projects/spring-security/blob/84bb8508babbee2ddae61657528a3100468ec1f9/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageGeneratingFilter.java#L282-L293))によって表示されています。

<img width="1635" alt="oauth2-login-page.png" src="https://qiita-image-store.s3.amazonaws.com/0/117313/ee7f94a5-aba9-4058-f90b-160863ec938b.png">

## OAuth 2.0ログインを行う

ログインページにて、GitHubのリンクを押下してOAuth 2.0ログインを行います。すると・・・まず、GitHubへのログインを促すページが表示されるので、GitHubへログインしてください。

<img width="1663" alt="oauth2-github-login-page.png" src="https://qiita-image-store.s3.amazonaws.com/0/117313/944f9273-ea16-c4ad-d2e8-08117c865224.png">

GitHubへのログインが成功すると、デモアプリケーションに対してGitHubで管理しているユーザ情報(リソースオーナのユーザ情報)へのアクセスを許可するための画面が表示されます。

<img width="1663" alt="oauth2-github-authorization-page.png" src="https://qiita-image-store.s3.amazonaws.com/0/117313/f47b2529-c433-e349-0e45-b4a3c466799a.png">

この画面でAuthorizeボタンを押下すると、GitHubのユーザ情報へのアクセスを許可したことを示すコード(認可コード)がデモアプリケーション側に渡されます。デモアプリケーションは、リソースオーナによって払い出された認可コードを使用してアクセストークンを取得し、取得したアクセストークンを指定してリソースオーナのユーザ情報にアクセスすることでログイン処理を完了させます。 


<img width="1663" alt="oauth2-index-page.png" src="https://qiita-image-store.s3.amazonaws.com/0/117313/ddc1aea8-f983-79b9-56c1-fd94ff01d21f.png">



ログイン処理が完了すると、最初に表示要求した「Index Page」が表示されます！！

# ユーザ情報(ユーザ属性)にアクセスする

まず、「Display User Attributes at login」リンクを押下して、ログイン成功時点のユーザ情報にアクセスしてみます。

<img width="1856" alt="oauth2-attributes-login-page.png" src="https://qiita-image-store.s3.amazonaws.com/0/117313/49d44f42-3862-95e3-e5c0-1e9920888366.png">


つぎに、「Display latest User Attributes」リンクを押下して、最新のユーザ情報にアクセスしてみます。
最新のユーザ情報が取得できていることを確認するために、GitHubのユーザ情報をかえておきます。（私はlocationを「Tokyo」から「Tokyo, Japan」に変更してみました）

<img width="1856" alt="oauth2-attributes-latest-page.png" src="https://qiita-image-store.s3.amazonaws.com/0/117313/a05542ff-ed81-9ea9-9148-5453ec7f5d7d.png">

わかりずらいかもしれませんが、画面の中央より少し下にあるlocationが「Tokyo, Japan」になり、updated_atの時間も更新されていることが確認できます。
ちなみに・・・キャプチャは載せませんが、「Display User Attributes at login」リンクを再度押下するとログイン時点の情報のままであることも確認できます。

# ログイン中にアクセストークンを破棄してみる

ログイン中にアクセストークンを破棄するとどうなるのでしょうか？
Spring Securityのデフォルト動作では、認証が成功すると認証情報がHTTPセッションで管理される仕組みになっているため、ログアウトまたはセッションタイムアウトするまでログイン状態は維持されます。よって、「Display User Attributes at login」リンクはアクセストークン破棄後も表示できてしまいます。いっぽう「Display latest User Attributes」リンクを押下すると、アクセストークンが破棄されているため、最新のユーザ情報の取得に失敗します。

<img width="1856" alt="oauth2-error-page-after-revoke.png" src="https://qiita-image-store.s3.amazonaws.com/0/117313/b84f3e07-eb51-e985-d03a-5dd9b414d1a2.png">



ログをみると、`RestTemplate`を使用してGitHubから最新のユーザ情報を取得する時に認証エラー(401: Unauthorized)になっていることがわかります。

```
...
2017-12-02 17:16:34.286 ERROR 88957 --- [io-8080-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 401 Unauthorized] with root cause

org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94) ~[spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:79) ~[spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63) ~[spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:772) ~[spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:725) ~[spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:699) ~[spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:657) ~[spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at com.example.demooauth2login.DemoController.userLatestAttribute(DemoController.java:48) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_121]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_121]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) ~[spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) ~[spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:871) ~[spring-webmvc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:777) ~[spring-webmvc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) ~[spring-webmvc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) ~[spring-webmvc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978) ~[spring-webmvc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:870) ~[spring-webmvc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
...
```

# まとめ

すご〜く簡単に、OAuth 2.0の認可サーバと連携したログイン機能を実現することができることがわかったと思います。実際のアプリケーションだとSpring Security提供のデフォルトログイン画面をそのまま使うことはないと思いますが、認可コードグラントフローの制御や認証処理はノンプログラミングで実現できると思います。Spring Security 5.0では、OAuth 2.0の認可サーバと連携したログイン機能のサポートだけのようですが、5.1以降で様々な機能追加や機能改善が行われていくことが予想されるので、今後の動向にも注目です。

# デモアプリケーション

* https://github.com/kazuki43zoo/demo-oauth2-login


# 参考サイト

* https://github.com/spring-projects/spring-security/tree/master/samples/boot/oauth2login