SpringBootアプリケーションでAuth0を試してみた【kotlin】

# 世界の全てをKotlinで
最近Windows Serviceをkotlin nativeで書いてみたりとかアホみたいな事をやっている程度にkotlinを愛してやまない私としては、取り敢えずkotlinでも（やっぱり）楽勝でAuth0を使えました、という内容で記事を書いてみる事にする。Auth0のサイトで丁寧にJava+SpringBootの説明があるから楽勝なのは当たり前なんだが。

この記事は、**アマギフ目当てで**エンジニアフェスタ2021に参加している。

https://qiita.com/official-events/5fcd3867b233a9228fd0

かいつまんで言うと、素敵記事を書いた2名に25000円分のアマギフくれるって話っぽいのよね。で、現在参加者11名だって。これダーツで決めるなら俺もワンチャンあんだけどな。

# まずはAuth0の無料トライアルでダッシュボードの表示
https://auth0.com/jp

# ダッシュボードからアプリケーションの作成をする
メニューのApplicationsを選択して、右上のCreate applicationボタンをクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/33467e8a-903f-7ea2-932d-934a65ab8da3.png)

出来ました、と。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/72d6a81c-1b17-c94c-0440-b7b176c7881a.png)

# 次からの手順で作るファイルはGithubに置いてます
https://github.com/mr-certain-a/auth0

設定ファイル(application.yml)だけ編集すれば使える形。
これを使う人は [Auth0側の設定](#auth0側の設定) までジャンプしてどうぞ。

# じゃあ作ってみよう
## 取り敢えずbuild.gradle
```build.gradle
buildscript {
    ext.kotlin_version = '1.5.30-M1'
    ext.springboot_version = '2.5.3'
    repositories {
        mavenCentral()
    }
}

plugins {
    id 'org.jetbrains.kotlin.jvm' version "$kotlin_version"
    id 'org.springframework.boot' version "$springboot_version"
}

apply plugin: 'kotlin'

repositories {
    mavenCentral()
}

dependencies {
    implementation(
              "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
            , "org.springframework.boot:spring-boot-starter-web:$springboot_version"
            , "org.springframework.boot:spring-boot-starter-oauth2-client:$springboot_version"
            , "org.springframework.boot:spring-boot-starter-thymeleaf:$springboot_version"
            , 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
    )
}
```

## 作るファイルは6ファイル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/e2dd668d-a6cd-d7da-1c46-818e260eae08.png)

こんな感じ。
kotlinのソースが4ファイル、htmlと設定ファイル。

## SpringBootのメイン
```MainActivator.kt
package monte

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer


@SpringBootApplication
open class MainActivator : SpringBootServletInitializer() {
    override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
        return application.sources(MainActivator::class.java)
    }

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            SpringApplication.run(MainActivator::class.java, *args)
        }
    }
}
```

クラスにopenをつけてやるのがコツ。

## ログアウトハンドラ
```LogoutHandler.kt
package monte

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.core.Authentication
import org.springframework.security.oauth2.client.registration.ClientRegistration
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
import org.springframework.stereotype.Controller
import org.springframework.web.servlet.support.ServletUriComponentsBuilder
import org.springframework.web.util.UriComponentsBuilder
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse


@Controller
class LogoutHandler
    @Autowired constructor(private val clientRegistrationRepository: ClientRegistrationRepository) :
    SecurityContextLogoutHandler() {

    override fun logout(
        httpServletRequest: HttpServletRequest, httpServletResponse: HttpServletResponse,
        authentication: Authentication
    ) {
        super.logout(httpServletRequest, httpServletResponse, authentication)

        httpServletResponse.sendRedirect(UriComponentsBuilder
            .fromHttpUrl("${clientRegistration.providerDetails.configurationMetadata["issuer"]}v2/logout?client_id={clientId}&returnTo={returnTo}")
            .encode()
            .buildAndExpand(clientRegistration.clientId, ServletUriComponentsBuilder.fromCurrentContextPath().build().toString())
            .toUriString())
    }

    private val clientRegistration: ClientRegistration
        get() = clientRegistrationRepository.findByRegistrationId("auth0")
}
```
これはもう殆どサンプルそのまんま。javaがkotlinになってるだけ。

## ログインとログアウトが出来るようにする
```SecurityConfig.kt
package monte

import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.web.util.matcher.AntPathRequestMatcher

@Configuration
open class SecurityConfig(private val logoutHandler: LogoutHandler) : WebSecurityConfigurerAdapter() {
    override fun configure(http: HttpSecurity) {
        http.oauth2Login()
            .and().logout()
            .logoutRequestMatcher(AntPathRequestMatcher("/logout"))
            .addLogoutHandler(logoutHandler)
    }
}
```
これもクラスにopenをつけるのがコツ。

## ルートで押し返すhtmlをマッピング
```MainController.kt
package monte

import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

@Controller
class MainController {
    @GetMapping("/")
    fun home(model: Model, @AuthenticationPrincipal principal: OidcUser?): String {
        model.addAttribute("message", "ログインできたら表示")
        principal?.let {
            model.addAttribute("profile", it.claims);
        }
        return "hello"
    }
}
```

## 最後にHTMLを作成
resources/templatesを掘って、hello.htmlを配置。

```hello.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8" />
    <title>Tried Auth0</title>
</head>
<body>
    <div sec:authorize="!isAuthenticated()">
<!-- ログインしてなければログインのリンクを表示-->
        <a th:href="@{/oauth2/authorization/auth0}">Log In</a>
    </div>
    <div sec:authorize="isAuthenticated()">
<!-- ログインしてたらプロフィールとログアウトのリンクを表示-->
        <h1 th:text="${message}"></h1>
        <img th:src="${profile.get('picture')}" th:attr="alt=${profile.get('name')}"/>
        <h2 th:text="${profile.get('name')}"></h2>
        <p th:text="${profile.get('email')}"></p>
        <a th:href="@{/logout}">Log Out</a>
    </div>
</body>
</html>
```

# Auth0側の設定
ダッシュボードのSettingsの画面を開く。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/ecc21226-d415-8734-e7ea-fa27276192b5.png)

ここの"**Domain**", "**Client ID**", "**Client Secret**" をapplication.ymlに記述する。

```application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          auth0:
            client-id: 【ここ】
            client-secret: 【ここ】
            scope:
              - openid
              - profile
              - email
        provider:
          auth0:
            # trailing slash is important!
            issuer-uri: https://【ここ】/
```
GithubのOAuth2.0認証でもこんなの書いたな。


ほんでSettingsの画面をグルグルグルっと下にスクロールして、**Application URIs**の**Allowed Callback URLs**と**Allowed Logout URLs**にそれぞれ、以下のURLを追加する。

- Application URIs
-- http://localhost:8080/login/oauth2/code/auth0
- Allowed Logout URLs
-- http://localhost:8080

以下の画面で怒られるのは、大体ここのURLの設定ミス。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/8f78013d-89f0-c865-b8b1-61d2d8cd8c9d.png)

# 早速動かしてみる
```./gradlew bootRun```で起動。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/b7aa2431-0926-947f-cd08-03fd598d4884.png)

起動したら、ブラウザで http://localhost:8080 にアクセスする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/c29fbf71-669e-2c98-b870-4d9bb42ce365.png)
という事で、ログインリンクを踏んでみると。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/bc5cb9e9-6a0a-9b3e-6692-2f9539b7422a.png)

取り合えずGoogleの認証が使えのか。ふむふむ。
早速試しにGoogleアカウントでログインしてみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/668c0c73-b598-65cf-31de-34ee4126e873.png)

# ログイン画面の見た目を触ってみる
メニューから**Universal Login**で、**Login**タブから**Customize Login Page**のスイッチをオンにする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/12fabc9a-e7b8-6cd7-86e6-b721bb18ccea.png)

下のHTMLを触れるようになるので、28行目と58行目を編集する。

- 28行目
var language;　→　var language='ja';
- 58行目
logo:にロゴ画像のURLを設定

※今回は[いらすとや](https://www.irasutoya.com/)さんの画像をお借りして[EasyUploader](https://uploader.xzy.pw/)さんに画像を乗っけさせてもらった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/f194d3a4-136b-0e99-0889-de71c58944c9.png)

**Save Changes**で保存。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/9488a224-7a11-6514-feff-5292655b5c05.png)

日本語になってロゴも変わった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/040b4b53-e521-0f4b-a9b6-a4c580491c36.png)

# やってみた感想
正直、**この記事を書く方が時間が掛かったくらい**にハマリどころが無かった。
導入方法はQuick Startに丁寧に書かれているので、躓くポイントも特にない。
JavaをKotlinにしたのは**殆どIntelli Jがやってくれたので**私は何もしていない気がするが、逆に言えば殆ど何もしなくても信頼できる認証をお手軽に組み込めるという事でいいんじゃないかな。多分。

デフォルトでブルートフォースアタックとかボットやスクリプトの攻撃に対してバリア貼ってくれてるのが嬉しい。

ちょっと機能を見てたら、Magic linkとか確認コード送ったりとかSMSもあるみたい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117698/8e0e66bd-49dd-0ebf-a85a-5500ba7898c2.png)

こんなん自分トコで実装して評価してとかやってたらアホみたいに予算が飛んでいくからね。「認証機能に１人月かかります」とか見積もり出されたら、「こういうものがあるようなので研究してみてください」と言える程度には理解できたような気がする私なのでした。