【SpringBoot入門】Spring Securityで認証機能

#目的

[Spring Quickstart Guide](https://spring.io/quickstart)を取り組み終えた方、SpringBootを学び始めた方、復習をしたい方に向けて、

公式ガイド[Securing a Web Application](https://spring.io/guides/gs/securing-web/)を実際に取り組み学んだことを共有します。


完成形を確認します。
トップページがあり、hereをクリックすると、
![スクリーンショット 2020-07-13 11.59.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/69486c15-1cb8-bca9-7585-5098acddbe4d.png)


ログインページに移動します。テストユーザー用のユーザー名とパスワードを入力し、Sign inボタンをクリックすると、
![スクリーンショット 2020-07-13 11.59.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/f84f7482-a80d-c421-a822-6b2bfa3648a1.png)


認証された人しか閲覧できないページに遷移します。ログインされていない状態ではこの画面にアクセスは出来ません。
![スクリーンショット 2020-07-13 12.00.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/12c0c097-50ec-5258-3d49-8da401466bd5.png)


開発環境、これまでのおさらいは以下になります。

```text:開発環境
OS： macOS Mojave バージョン10.14.6
テキストエディタ： Visual Studio Code（以下VSCode）
Java: 11.0.2
```

[QuickstartGuide](https://qiita.com/morioheisei/items/59384f015e9e3801c4a7)
[Building a RESTful Web Service編](https://qiita.com/morioheisei/items/03106a823063677fe2f7)
[Consuming a RESTful Web Service編](https://qiita.com/morioheisei/items/c0a7432ad0f7281730d5)
[Accessing Data with JPA編](https://qiita.com/morioheisei/items/3d93da3757f27a158466)
[Handling Form Submission編](https://qiita.com/morioheisei/items/96d71871dbd798bc5cac)
[Securing a Web Application編](https://qiita.com/morioheisei/items/1d02fa6644d2c355df6e)


#1.SpringBoot projectを始めよう！

まずは、[spring initializr](https://start.spring.io/) にアクセスします。

1.ADD DEPENDENCIESボタンをクリックして、`Spring Web`と`Thymeleaf`を追加。
2.Artifact, Nameは、`securing-web`に変更。
3.`Javaを11`に変更。

そして`GENERATE`ボタンをクリックしてZipファイルをダウンロードします。

![スクリーンショット 2020-07-13 13.17.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/b9030af4-ffca-17b7-318d-8482e492f507.png)


ダウンロードしたZipファイルを展開したら準備完了です。

#2.コードを追加しよう！

先ほどのフォルダをVSCodeで開きます。
拡張機能のJava Extension Packのインストールの推奨します。と言われるのでインストールしておきましょう。

![スクリーンショット 2020-06-30 10.08.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/80331ebc-b6e3-af7b-181a-3c8921288b00.png)

##home.htmlを作成しよう！

`src/main/resources/templates/` にhome.htmlファイルを作成します。


![スクリーンショット 2020-07-13 13.29.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/1e279011-d680-d1d9-426f-7f0a24afeca6.png)

公式を参考にコードを追加します。トップページになります。

```html:home.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
  <head>
    <title>Spring Security Example</title>
  </head>
  <body>
    <h1>Welcome!</h1>

    <p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
  </body>
</html>
```

htmlタグの部分は、`th:〇〇`や`sec:〇〇`と記述出来るようにする為のものです（thymeleafやSpringSecurityのタグを用いた記述方法が可能になる）。


では、追加したコードのthymeleafの記述について深掘りしていきます。

> thymeleafとは、springbootで扱う事が出来るテンプレートエンジンです。th:〇〇と記述します。
> 日本語で書かれた[thymeleafチュートリアル](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html#thymeleaf%E3%81%AE%E7%B4%B9%E4%BB%8B)もあります！


**th:href**

aタグのhref属性にセットするタグです。記述の仕方は、`th:href="@{}"`です。
`{}`の中はパスを指定します。今回は`/hello`ですので、タグをクリックすると、`http://localhost:8080/hello`に遷移する事になります。


##hello.htmlを作成しよう！


`src/main/resources/templates/` にhello.htmlファイルを作成します。


![スクリーンショット 2020-07-13 14.21.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/d6cfa066-ae8d-72ca-8606-825f9a5093ca.png)


公式を参考にコードを追加します。

```html:hello.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
  <head>
    <title>Hello World!</title>
  </head>
  <body>
    <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
    <form th:action="@{/logout}" method="post">
      <input type="submit" value="Sign Out"/>
    </form>
  </body>
</html>
```

`th:inline`は、タグのテキスト内に変数の値を展開する為のものです。表示したい変数を`[[...]]`で囲みます。
今回は、`${#httpServletRequest.remoteUser}`なので、認証されているユーザーの名前を表示するようになっています。

`Sign Out`ボタンが押されると、`/login?logout`にリダイレクトにされます。


##MvcConfig.javaを作成しよう！

`src/main/java/com/example/securingweb/` にMvcConfig.javaファイルを作成します。

![スクリーンショット 2020-07-13 14.43.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/b9dcf84c-22c2-1ea4-7f08-c6812f0b7923.png)

公式を参考にコードを追加します。

```java:MvcConfig.java
package com.example.securingweb;

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");
  }

}
```

**`@Configuration`**アノテーションは、Springの色々な設定をJavaのコード上で行えるようにするためにクラスに付与するものです。（設定クラス）
WebMvcConfigurerインターフェースを実装したMvcConfigクラスを作成し、addViewControllersメソッドをオーバーライドしています。


**registry.addViewController("/home").setViewName("home");**

別のControllerを用意して、`@GetMapping`アノテーションを用いてView名をreturnするやり方するのではなく、
上記記述によって、

`http://localhost:8080/home`というURLの時、**home.html**というテンプレートを参照するようになります。
URLとテンプレートがマッピングされるようにしているみたいです。
login.htmlの実装は後ほど。


##pom.xmlにSpring Securityを追加しよう！

Spring Securityを使用する為に、pom.xmlに以下を追加してください。

> Spring Securityとは、Webアプリケーションにセキュリティ機能を追加するためのフレームワーク。
> 認証、認可、一般的な悪用に対する保護の包括的なサポートを提供します。

```xml:pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
		
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <scope>test</scope>
</dependency>
```

![スクリーンショット_2020-07-13_15_32_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/402d8d24-dfb0-01d5-b580-58b203aa95b1.png)


##WebSecurityConfig.javaを作成しよう！

`src/main/java/com/example/securingweb/` にWebSecurityConfig.javaファイルを作成します。

![スクリーンショット 2020-07-13 15.48.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/e8086e5c-30e1-3661-ea2e-c9f06552c40b.png)

公式を参考にコードを追加します。


```java:WebSecurityConfig.java
package com.example.securingweb;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;


@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
        .antMatchers("/", "/home").permitAll()
        .anyRequest().authenticated()
        .and()
      .formLogin()
        .loginPage("/login")
        .permitAll()
        .and()
      .logout()
        .permitAll();
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    String password = passwordEncoder().encode("password");

    auth.inMemoryAuthentication()
        .passwordEncoder(passwordEncoder())
        .withUser("user").password(password).roles("USER");
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
  }

}
```

追加したコードを深掘りしていきます。

###①`@EnableWebSecurity`と`WebSecurityConfigurerAdapter`クラスの継承

```java:WebSecurityConfig.java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  // 省略
}
```

`@EnableWebSecurity`アノテーションを付与する事により、Spring Securityが有効になります。
そして、`WebSecurityConfigurerAdapter`という抽象クラスを継承しています。このクラスにある、**configureメソッド**をオーバーライドして、
ページのアクセス制限の有無を実装していきます。

###②ページのアクセス制限を設定する

```java:WebSecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
  http
    .authorizeRequests()
      .antMatchers("/", "/home").permitAll() // アクセス制限が必要のないURL
      .anyRequest().authenticated()  // その他URLは、認証が必要。認証済みではないとアクセスできない
      .and()
    .formLogin()
      .loginPage("/login") // ログイン画面もアクセス制限なし
      .permitAll() // 認証は不要
      .and()
    .logout()
      .permitAll(); // 認証は不要
}
```

**configureメソッド**をオーバーライドしています。
引数でHttpSecurityを受け取っており、httpリクエスト関連の部分についての設定を記述するためのメソッドになっています。
ログイン、ログアウト、その他のページのアクセス時に認証は必要であるのかどうかを設定しています。


###③認証するユーザーの設定

この部分の実装は、公式ガイド通りに進めるとdeprecated（非推奨）と言われてしまいました。
ですので、少し違う実装になっています。

```java:WebSecurityConfig.java

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

// パスワードをエンコード
String password = passwordEncoder().encode("password");

// インメモリの認証を行うための設定
auth.inMemoryAuthentication()
    .passwordEncoder(passwordEncoder())
    .withUser("user").password(password).roles("USER");
}

@Bean
public PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
}
```

**`PasswordEncoder passwordEncoder()`**は、パスワードのハッシュ化をする為のメソッドを定義しています。
そして、文字列のpasswordを`String password = passwordEncoder().encode("password");`の部分でハッシュ化しています。

AuthenticationManagerBuilderは、メモリ内認証を可能にする為のクラスで、

```java:
// インメモリの認証を行うための設定
auth.inMemoryAuthentication()
    .passwordEncoder(passwordEncoder())
    .withUser("user").password(password).roles("USER");
}
```
ここでそれを実装しており、認証するユーザーの設定を行っています。


##login.htmlを作成しよう！

`src/main/resources/templates/` にlogin.htmlファイルを作成します。

![スクリーンショット 2020-07-14 10.45.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/83e22841-2fab-dc29-0ec6-bf377ad1fec5.png)

公式を参考にコードを追加します。ログインページになります。

```html:login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
  <head>
    <title>Spring Security Example </title>
  </head>
  <body>
    <div th:if="${param.error}">
      Invalid username and password.
    </div>
    <div th:if="${param.logout}">
      You have been logged out.
    </div>
      <form th:action="@{/login}" method="post">
        <div><label> User Name : <input type="text" name="username"/> </label></div>
        <div><label> Password: <input type="password" name="password"/> </label></div>
        <div><input type="submit" value="Sign In"/></div>
      </form>
  </body>
</html>
```

User NameとPasswordを入力する欄があります。
`th:if="${param.error}`は、ユーザー認証が失敗した時に表示される領域で、
`th:if="${param.logout}`は、サインアウトをした際に表示される領域となります。（hello.htmlでSign Outボタンが押された後に表示される）


#3.実行してみよう！

アプリケーション実行の準備が出来たので確認しましょう。

ターミナルで以下のコマンドを入力してEnterしてください。

```terminal:ターミナル
$ ./mvnw spring-boot:run
```

トップページが表示されます。

![スクリーンショット 2020-07-14 13.34.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/c05220ab-c478-7a9f-e513-b8cff5bc9942.png)

hereをクリックすると、ログインページが表示されます。
![スクリーンショット 2020-07-14 13.34.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/29f1dea5-5180-f21a-8576-73b5edb60a4a.png)

適当なUser NameとPasswordを入力してSign Inボタンを押してみると、
![スクリーンショット 2020-07-14 13.37.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/8a5732f0-f1b5-66ae-2747-86fe3e42fd3d.png)

ログインページが再表示され、エラーメッセージも表示されていますね。
次に、Uesr Nameに`user`、Passwordに`password`と入力してSign Inボタンを押してみると、

![スクリーンショット 2020-07-14 13.39.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/b05ac9e7-6ac8-11bc-d891-3e633407dcc7.png)

認証された人しか閲覧できないページに遷移出来ました。
![スクリーンショット 2020-07-14 13.39.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/fc9d2777-2e80-834c-119a-486a36285739.png)

Sign Outボタンを押すと、

![スクリーンショット 2020-07-14 13.41.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555244/37e94c83-7131-a924-03f8-0e03bf7d03ee.png)

先ほどのログインページにリダイレクトされ、メッセージも表示されています。

お疲れ様でした。完成です！



#参考サイト

**[User.withDefaultPasswordEncoder()のdeprecatedの代替案](https://stackoverflow.com/questions/49847791/java-spring-security-user-withdefaultpasswordencoder-is-deprecated)**
**[【Spring Security はじめました】#2 ユーザー認証](https://b1tblog.com/2020/02/14/spring-security-2/)**
**[Spring Security で「Hello World！」](https://qiita.com/ryotaro76/items/6c8405fada7a6fead7cc)**