spring boot + socialでgithubのOAuth認証をしよう!!

概要

Java初心者で右も左もわからないJavaのspring bootでOAuth認証を使ったログインを実装しようと思ったが調べても調べても良い文献がなく(初心者なので良い文献でも理解不能で実装できない...)困ったのでここに簡単な実装を記すことにする。

実装の手順

  1. gradleで必要なものを宣言する
  2. 認証用のgithubアプリを作る&設定ファイルに記述
  3. Springで受け取る用のアクションを宣言する
  4. 必要な画面を宣言する

1.gradleで必要なものを宣言する

今回僕はgradleを使っていたのでそこで使っていたものをココに記載する


implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-devtools'

//多分今回の認証で特に必要なのは下記の2つ
implementation 'org.springframework.social:spring-social-github:1.0.0.M4'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'


compileOnly 'org.projectlombok:lombok:1.18.2'

runtime 'mysql:mysql-connector-java'
runtime 'org.springframework.boot:spring-boot-devtools'

testCompile 'org.springframework.boot:spring-boot-starter-test'

testCompile group: 'junit', name: 'junit', version: '4.12'

2. 認証用のgithubアプリを作る&設定ファイルに記述

認証ようにアプリを制作してclientIdとSecretIdを取得します。 この手順はSpringにかかわらずいろんなところに記述してあるので割愛しますー

https://techium.hatenablog.com/entry/2016/04/11/090000

※一応参考になりそうな記事を張っておく

callbackURLは「http://localhost:3030/callback」 みたいな形で登録しておきましょう!(http://localhost:3030の部分は適宜読み替えてください)

取得したgithubのclientIdとSecretIdはapplication.propertiesに下記のように記述します。

// ココは設定しなくても良い(3030にすると調整する必要が無い)
server.port = 3030

//githubの設定内容
github.client=ココに取得したclientId
github.secret=ココに取得したsecretId
github.callbackUrl=http://localhost:3030/callback

callbackUrlはgithubで登録したものと同じものを設定

3. Springで受け取る用のアクションを宣言する

今回はファイル多くなると面倒でよくわからなくなるので1つコントローラーを作成して全てそこで完結させる。 (もうほんとにJavaはすぐファイルを作って分割するけどあの思想がどうも理解できない...by Java嫌いマンより)

package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.social.github.api.GitHub;
import org.springframework.social.github.api.impl.GitHubTemplate;
import org.springframework.social.github.connect.GitHubConnectionFactory;
import org.springframework.social.oauth2.GrantType;
import org.springframework.social.oauth2.OAuth2Operations;
import org.springframework.social.oauth2.OAuth2Parameters;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpSession;


@Controller
public class GithubController {
    private static final String TOKEN = "token";

    /**
     * githubの設定
     */
    @Value("${github.client}")
    private String client;

    @Value("${github.secret}")
    private String secret;

    @Value("${github.callbackUrl}")
    private String callbackUrl;

    private final HttpSession httpSession;

    @Autowired
    public GithubController(HttpSession httpSession) {
        this.httpSession = httpSession;
    }


    /**
     * トップページ
     * @return
     */
    @GetMapping("/")
    public String login() {
        return "index";
    }

    /**
     * ログインアクション
     * @return
     */
    @GetMapping("/login")
    public String github() {
        return "redirect:" + operations().buildAuthorizeUrl(GrantType.AUTHORIZATION_CODE, new OAuth2Parameters());
    }


    /**
     * githubからcallback時のアクション
     * @param code
     * @return
     */
    @GetMapping("/callback")
    public String githubCallback(String code) {
        String token = operations().exchangeForAccess(code, callbackUrl, null).getAccessToken();
        httpSession.setAttribute(TOKEN, token);
        return "redirect:/profile";
    }

    /**
     * 認証後にユーザー情報を取得するアクション
     * @param mav
     * @return
     */
    @GetMapping("/profile")
    public ModelAndView profile(ModelAndView mav) {
        Object userInfo = httpSession.getAttribute(TOKEN);
        if (userInfo != null){
            mav.setViewName("profile");
            GitHub gitHub = new GitHubTemplate(userInfo.toString());
            String userName = gitHub.userOperations().getUserProfile().getUsername();
            Long userId = gitHub.userOperations().getUserProfile().getId();
            mav.addObject("userName",userName);
            mav.addObject("userId",userId);
        }else{
            mav.setViewName("error/401");
        }
        return mav;
    }

    /**
     * 認証後にユーザー情報を取得するアクション
     * @return
     */
    @GetMapping("/logout")
    public String logout() {
        httpSession.removeAttribute(TOKEN);
        return "redirect:/";
    }

    /**
     * githubの認証のコア機能
     * @return
     */
    private OAuth2Operations operations() {
        GitHubConnectionFactory gitHubConnectionFactory = new GitHubConnectionFactory(client, secret);
        OAuth2Operations operations = gitHubConnectionFactory.getOAuthOperations();
        return operations;
    }

}

これで必要な処理の準備は完了したので、あとは必要な画面を制作して完了です。

4. 必要な画面を宣言する

今回必要になる画面は

index.html

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <link
            href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
            rel="stylesheet">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
    <meta charset="UTF-8">
</head>
<body>
<div class="container"  style="text-align: center;">
    <h1>ログインページ</h1>
    <button type="button" class="btn btn-dark" style="margin-top: 100px">
        <a href="/login">Github</a>
    </button>
</div>
</body>
</html>

profile.html

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <link
            href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
            rel="stylesheet">
    <meta charset="UTF-8">
</head>
<body>
    <div class="container">
        <h1>プロフィールページ</h1>
        <table class="table">
            <tbody>
                <tr>
                    <td>ユーザー名</td>
                    <td th:text="${userName}"></td>
                </tr>
                <tr>
                    <td>メールアドレス</td>
                    <td><img th:src="'https://avatars2.githubusercontent.com/u/'+${userId}"></td>
                </tr>
            </tbody>
        </table>
        <button type="button" class="btn btn-danger">
            <a href="/logout">logout</a>
        </button>

    </div>
</body>

</html>

error/401.html

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <link
            href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
            rel="stylesheet">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
    <meta charset="UTF-8">
</head>
<body>
<div class="container"  style="text-align: center;">
    <h1>401 Unauthorized</h1>
</div>
</body>
</html>

error.html

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <link
            href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
            rel="stylesheet">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
    <meta charset="UTF-8">
</head>
<body>
<div class="container"  style="text-align: center;">
    <h1>500 Internal Server Error</h1>
</div>
</body>
</html>

これで「http://localhost:3030/」にアクセスすればログイン画面が表示されるはず...!!!!!!