Spring公式ガイドに沿ってSpring Securityでログイン・ログアウト機能を作る【初心者向け】

***追記：home.htmlの記述とスクショの画像が間違っていたため修正しました。***

### 前提・環境
Spring Securityを使ってログイン・ログアウト機能を作りたかったのですが色々とハマって2日ほど詰んでしまい、
最終的にSpring公式のガイドに従って作っていくことにしました。
→https://spring.io/guides/gs/securing-web/

全文英語なのでちまちま意味を調べながら、書きながら…で何とか理解できたので、初心者の方向けに共有します。

#### 環境：
- Windows 10
- Eclipse
- Spring Boot 2.1.4 
- Java 8
- Mavenでビルド
- テンプレートエンジンはThymeleaf


### Spring Bootでプロジェクト作成

新規作成→Spring スターター・プロジェクト
を選択し、以下の項目で作成します。

- Java 8
- 型：Maven
- 依存関係はThymeleaf,Webのみ選択

これが絶対！って訳ではありませんが、環境によって動きが変わってきちゃうと思うので、とりあえずコピペしたい人は合わせた方が良いと思います。

### SpringSecurityの導入
では、作成したプロジェクトに手を加えていきます。

まずは、pom.xmlに「SpringSecurity使うよ！」という記述を追加します。
（Spring公式ではHello Worldアプリケーション作成後に行っていますが、今回は最初にやってしまいます）
pom.xmlって何？って場合は、ぜひググってみてください。
ざっくり言うと、pom.xmlはMavenの設定ファイル的なものです。（間違ってたらごめんなさい）

``` pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>login-Training-SpringBoot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>login-Training-SpringBoot</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        //ここに追加！
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
```

`<dependencies></dependencies>`の間に

```
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
</dependency>
```
を追加します。
これで、「Spring Security使うよ！」という設定ができました。



#### 最初に、セキュリティがかかっていないアプリケーションを作成する

まずはセキュリティのない状態でHello Worldするアプリケーションを作っていきます。

##### home.htmlの作成
Thymeleafテンプレートを使うので、

`src/main/resources/templates/home.html`

のようにtemplatesフォルダ配下にHTMLファイルを作成します。
※4/25 修正しました

``` home.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-springsecurity3">
	<head>
		<meta charset="UTF-8">
		<title>Hello,SpringSecurity!</title>
	</head>
	<body>
		<h1>WELCOME!</h1>
		<br>
        <p>ログインは<a th:href="@{/hello}">こちら</a></p>
　　　　 <p><a th:href="@{/}">新規ユーザー登録</a></p>
	</body>
</html>
```

1. htmlタグ内の記述：Thymeleaf使う宣言、Security使う宣言、XHTMLのネームスペース
2. "/hello"へのリンク

まずネームスペースについて。
ネームスペースとは、マークアップ言語を自由に設計できるXML上で、複数の語彙を組み合わせてもコンピュータが識別できるように、と作られた「名前空間」のことを指します。（文字通り）
あまりにも概念過ぎて理解しづらいですが、
名前空間についてはこの記事が分かりやすいです。
[XML名前空間の簡単な説明](https://www.kanzaki.com/docs/sw/names.html)

今回使う"xmlns"は「XML名前空間」と言い、語彙のタイプとURIを組み合わせることでコンピュータが識別できるようになっている、らしいです。

また、

>
多くの場合、名前空間を示すURIにhttp:スキームが使われているため、何かを取り出せるという印象があるのですが、これは第一義的にはその名のとおり「ID」の役割を果たすものと理解してください

とのことで、まぁここではタグ識別IDと思っていて間違いないでしょう。

ここではデフォルトの名前空間を` xmlns= ""`で設定し、
` xmlns:th= ""`でth:の設定、
` xmlns:sec= ""`でsec:の設定をしています。


次に、"/hello"へのリンクですが、
ここではThymeleaf記法でリンクを貼っています。
th:href属性の@{...}の部分がリンク先。
この場合、「localhost:8080/hello」にリンクされているということになります。
（新規ユーザー登録のリンクも記述していますが、表示しているだけで未実装なので、今回は使いません）

Thymeleafの書き方参考：[必要最小限のサンプルでThymeleafを完全マスター](http://masatoshitada.hatenadiary.jp/entry/2016/12/11/100528)

#####  [hello.html]
では、先程リンクを記述したhello.htmlを作成します

``` hello.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <meta charset="UTF-8">
        <title>Hello,SpringSecurity!</title>
    </head>
    <body>
        <h3>ようこそ！</h3>
    </body>
</html>
```

ここでは一旦、単純にようこそ！を表示するだけにしておきます。

#####  MvcConfigクラスの作成
続いてはMVCの設定をするクラスを作成。
`src/main/java/hello/MvcConfig.java`
に置きます。

``` MvcConfig.java
package hello;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
    }
}
```

1. @Configurationアノテーションをつける
2. WebMvcConfigurerをimplements

implementsについて：
[インターフェースを実装！Javaでimplementsを使う方法【初心者向け】](https://techacademy.jp/magazine/17414)

ざっくり説明すると、
interface(仕様)→implementsしたクラス（仕様に実際の定義を入れ込む）→そのメソッドを使う
といったイメージですね。


#### アプリケーションにセキュリティをかけていく

##### セキュリティ設定クラス（WebSecurityConfig.java）を作成

WebSecurityConfigurerAdapterクラスを継承した設定のクラスを作成します。
`src/main/java/hello/WebSecurityConfig.java`
このように配置。

``` WebSecurityConfig.java
package hello;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http
            .authorizeRequests()
                // "/" と /home は全ユーザーがアクセス可能
                .antMatchers("/","/home").permitAll()
                //上記以外へのアクセスは認証が必要
                .anyRequest().authenticated()
                .and()
            //ログイン、ログアウトのURL指定と全ユーザーへのアクセス許可
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
                User.withDefaultPasswordEncoder()
                    .username("user")
                    .password("pass")
                    .roles("USER")
                    .build();
        return new  InMemoryUserDetailsManager(user);
    }
}
```

1. WebSecurityConfigurerAdapterを継承
2. @EnableWebSecurityアノテーションを記述
3. URLパスとアクセス権限を定義
4. userDetailsService()では@Beanアノテーションを使い、ユーザー名とパスワードを設定する

まず、継承は、クラス作るときに継承すればいいです。

で、@EnableWebSecurityですが、これを記述することでWebSecurity機能を使うことができるみたいです。まぁ、Webでのセキュリティ機能だよー！という記述ってことです。正確な定義はGoogle先生に譲ります。。

URLのパスとアクセス権限については、コメントに書いている通り。
permitAll()で全ユーザーにアクセスを許可し、
authenticated()で認証を必要とする
って感じです。

最後にuserDetailsService()の部分ですが、
まず@BeanはJavaBeansと一緒で、
@Beanアノテーションを使うことでユーザーのBean(具体的な中身)を生成できます。
また、UserDetailsServiceクラスをオーバーライドし、ここでユーザーネームやパスワードを指定します。
ここでは、usernameはusername,passwordはpass,role(役割)はUSERとします。

##### login.htmlを作成

``` login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <meta charset="UTF-8">
        <title>LoginPage</title>
    </head>
    <body>
        <div th:if="${param.error}">
            ユーザー名かパスワードが違います
        </div>
        <div th:if="${param.logout}">
            ログアウト済みです
        </div>
        <form th:action="@{/login}" method="post">
            <div><label>ユーザー名：<input type="text" name="username" /></label></div>
            <div><label>パスワード：<input type="password" name="password" /></label></div>
            <div><input type="submit" value="ログイン" /></div>
        </form>
    </body>
</html>
```

`src/main/resources/templates/login.html`
に作成。

${param}は「暗黙オブジェクト」とかいうよくわからない物体。
ざっくり噛み砕いて説明すると、.（ドット）の後のパラメータを取得してくれるものです。（「${param} EL式」とかで検索すると出てくる）
なので、`${param.error}`で、errorのパラメータを取得することができます。
この場合はerrorがtrueならエラーメッセージを、logoutがtrueならその旨のメッセージを表示するように記述しています。

また、Spring公式によると、ログイン失敗すると`"/login?error"`にリダイレクトするようになっているようです。
>
If the user fails to authenticate, the page is redirected to "/login?error" and our page displays the appropriate error message.

ちなみに、XMLの書き方だとinputタグの最後に/をつける必要があるようです。
[Spring Boot2でinputタグのしめに「/>」とスラッシュをつけないとエラーになる理由](https://teratail.com/questions/113120)

##### 最後に、hello.htmlにユーザー名とログアウトリンクを表示

``` hello.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <meta charset="UTF-8">
        <title>Hello,SpringSecurity!</title>
    </head>
    <body>
        <h2>Hello!</h2>
        <h1 th:inline="text">Hello[[${#httpServletRequest.remoteUser}]]!</h1>
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="ログアウト" />
        </form>
        <p></p>
    </body>
</html>
```

bodyの表示部分を書き換えました。

1. `[[${#httpServletRequest.remoteUser}]]`でユーザー名表示
2. ログアウトボタンを作成

`[[${#httpServletRequest.remoteUser}]]`の部分でユーザー名を取得して画面表示します。

>
We display the username by using Spring Security’s integration with HttpServletRequest#getRemoteUser(). 
（公式ガイドより）

ここでは、 HttpServletRequestのgetRemoteUser()メソッドを使ってusernameを取得するよ！と書かれてあります。
この考え方を使って、
httpServletRequestでremoteUserのデータを取得する
といったイメージですね。
二重括弧 [ [ ] ] は、調べた限りだとデータ変換用のようです。
取得する→出力用にフォーマットする　の2段階踏むからかな。多分。


##### アプリケーションの実行用クラスを作成する
やっとここまできました！
あとはプロジェクトを実行できるよう設定するだけです。

公式ではhelloパッケージに新たに実行用クラスApplication.javaを作成していますが、すでにデフォルトでcom.example.demoパッケージにあるんだよなぁ…と思いつつ、
読んだら「helloパッケージにあることでどうたらこうたら」みたいなこと書いてあったので念のため作成。
（ちゃんと訳してないので意味ないかもｗ）
>
@ComponentScan tells Spring to look for other components, configurations, and services in the hello package, allowing it to find the controllers.


`src/main/java/hello/Application.java`
に作ります。

``` Application.java
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <meta charset="UTF-8">
        <title>Hello,SpringSecurity!</title>
    </head>
    <body>
        <h2>Hello!</h2>
        <h1 th:inline="text">Hello[[${#httpServletRequest.remoteUser}]]!</h1>
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="ログアウト" />
        </form>
        <p></p>
    </body>
</html>
```

デフォの実行用クラスは消してしまってもいいと思います。
私はなんか怖いので全文コメントアウトしましたｗ


### 実行！
やっと完成しました！
これで実行してみましょう。

プロジェクトを右クリック→実行→Spring Bootアプリケーション　をクリックで起動できます。
その後、ブラウザのURL入力欄に`http://localhost:8080`を打ち込んでアクセスしてみてください。

※4/25 修正しました

home.html
![SS-home画面.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312956/68427317-d579-8362-c0b6-0620d5e88a32.png)


最初にhome画面が出力されました。
ログインページへのリンクが表示されていますね。
ここではまだ使いませんが、新規ユーザー登録も表示されています。


ログアウトを押します。

login.html
![SS-login画面.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312956/f1346f9a-fd0f-4d73-0c33-cfe0995cac2d.png)

login画面に遷移しました。
ユーザー情報は「ログアウト済み」になっているので、「ログアウト済みです」と表示されています。


次は、自分で指定したユーザー名（user）とパスワード（pass）を入力してログインしてみましょう。

hello.html
![SS-hello画面.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312956/cc707eb3-f9ba-65f6-a514-f6f479f22d0d.png)

ログインできました！
ユーザー名は「user」となっているので、ちょっとわかりにくいですが「Hello+user+!」の形になっています。


では、間違ったユーザー名orパスワードを入力してみましょう。

login.html
![SS-loginerror画面.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312956/e23fb487-a535-69c1-b718-d1f692c09d8f.png)

ちゃんとエラーが表示されていますね。



はーーここまで長かった。
仕組みが分かった上でこれができれば、応用も効きそうです。

お疲れさまでした！