Spring Security の実装

# 概要
VS CodeSpringBootアプリケーションを作成し簡易なSpringSecurityを実装する。
また、インメモリにユーザ情報を持たせアクセス制御も行う
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459413/331f96c9-77ee-8237-d9b0-af317cf3752e.png)

参考記事
[Spring Security 6.0 入門](https://zenn.dev/kktworks/books/spring_security_6_sample_book)
[初めての人のためのSpring Security](https://www.docswell.com/s/MasatoshiTada/KGVY9K-spring-security-intro)


# 目次
1. [環境情報](#環境情報)
1. [Spring Bootアプリケーションの作成](#springbootアプリケーションの作成)
1. [View作成](#view作成)
1. [Controller作成](#controller作成)
1. [Spring Securityの設定](#springsecurityの設定)
1. [参考資料](#参考資料)
1. [付録(権限ごとのViewの表示変更)](#付録)

## 環境情報
項目|バージョン
---|---
Java|17
SpringBoot|3.2.4
SpringSecurity|6

## SpringBootアプリケーションの作成
VS Codeで以下の依存関係を追加したSpringBootアプリケーションを作成する

|dependencies|備考|
|---|---|
|Spring Web|自己完結型の HTTP サーバーを作成できる|
|Thymeleaf|Javaのウェブ開発用テンプレートエンジン|
|SpringSecurity|Spring Securityを用いた認証・認可に利用|

[参考: VS CodeでSpring Bootアプリケーション作成 とDB連携](https://qiita.com/yukiya2392/items/74213aed0c927b98ca5e)

## View作成
権限によるアクセス制御を実装するため、
「index.html」, 「user.html」, 「admin.html」の３つのページを用意する。
```html:index.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ホーム</title>
</head>

<body>
  <h1>index.html</h1>
  <a href="./user.html" th:href="@{/user}">ユーザページ</a>
</body>

</html>
```
```html:user.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ユーザーページ</title>
</head>

<body>
  <h1>user.html</h1>
  <a href="./admin.html"th:href="@{/admin}">管理者ページ</a><br>
  <a href="./logout.html" th:href="@{/logout}">ログアウト</a>
  </form>
</body>

</html>
```
```html:admin.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>管理者ページ</title>
</head>

<body>
  <h1>admin.html</h1>
  <a href="./user.html" th:href="@{/}">ユーザーページ</a><br>
  <a href="./logout.html" th:href="@{/logout}">ログアウト</a>
</body>

</html>
```

## Controller作成
「LoginController.java」を作成する
```java:LoginController.java
package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("")
public class LoginController {

  @GetMapping("/user")
  public String user(){
    return "/user";
  }

  @GetMapping("/admin")
  public String admin(){
    return "/admin";
  }
}
```

アプリケーションを起動し、「http://localhost:8080/」 に接続するとSpring Securityによりログイン画面が表示される
（SpringSecurityにはデフォルトで「/login」と「/logout」が用意されている
現時点では、SpringSecurityの設定を行なっていないため、全てのページに対してSecurityがかかっている状態）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459413/c6c34a11-eac2-669f-1415-b0c9088ebaa2.png)

Username: user
Password: 起動時のコンソールに出力される（実行ごとに異なる）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459413/a9a5442d-eea5-fbbc-6cb6-78085a34feee.png)

「Sign in」するとユーザーページ(index.html)に遷移する
サインイン後は、全てのページへのアクセスが可能となる

## SpringSecurityの設定
「SecurityConfig.java」を作成し、ユーザー情報と各ページの参照権限を設定する

アクセス制御設定
| ユーザー/ページ | 未ログイン | user1 | admin |
|----------------|:---------:|:-----:|:-----:|
| index.html     |    ⚪️     |   ⚪️   |   ⚪️   |
| user.html      |    ❌      |   ⚪️   |   ⚪️   |
| admin.html     |    ❌      |   ❌    |   ⚪️   |
```java:SecurityConfig.java
package com.example.demo.common;

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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  // パスワードの暗号化
  @Bean
  PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  InMemoryUserDetailsManager userDetailsService() {
    // 管理者設定
    UserDetails admin = User
        .withUsername("admin")
        .password(passwordEncoder().encode("admin"))
        .roles("ADMIN")
        .build();

    // ユーザー設定
    UserDetails user = User
        .withUsername("user")
        .password(passwordEncoder().encode("user"))
        .roles("USER")
        .build();
    return new InMemoryUserDetailsManager(admin, user);
  }

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

    http
        // ログインページの許可設定
        .formLogin(login -> login // フォーム認証を使う
            .defaultSuccessUrl("/user") // 認証成功時のデフォルトの遷移先
            .permitAll())

        // リクエストの許可設定
        .authorizeHttpRequests(authz -> authz
            // index.html の参照権限
            .requestMatchers("/")
            .permitAll()
            // user.html の参照権限
            .requestMatchers("/user")
            .hasAnyRole("USER","ADMIN")
            // admin.html の参照権限
            .requestMatchers("/admin")
            .hasRole("ADMIN"));

    return http.build();
  }

}
```

アプリケーションを再起動し、以下内容が実装できたことを確認する
1. http://localhost:8080/ に接続
1. 表示されたindex.html で「ユーザーページ」を押下
1. ログイン画面が表示される
1. 「user」, 「admin」でログインし、アクセス制御が正しく行われていることを確認する

[今回のコードサンプル(GitHub)](https://github.com/yukiya-rp/SpringSecurity_introduction)

## 参考資料
[SpringSecurityのアーキテクチャー](https://spring.pleiades.io/spring-security/reference/servlet/architecture.html)
[インメモリ認証（InMemoryUserDetailsManager）](https://spring.pleiades.io/spring-security/reference/servlet/authentication/passwords/in-memory.html)
[User(org.springframework.security.core.userdetails.User)](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/User.html)

## 付録
ログインしているユーザーの権限によって、Viewで表示させるコンテンツを変更する
htmlタグに以下の記載を追加
```xmlns:sec="http://www.thymeleaf.org/extras/spring-security"```
管理者ページへのリンクに以下の記載を追加
```sec:authorize="hasAuthority('ROLE_ADMIN')"```

上記の記載により、ADMIN権限でログインした場合のみリンクが表示されるようになる

```html:user.html
<!DOCTYPE html>
<!-- 修正 -->
<html lang="ja" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> 

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ユーザーページ</title>
</head>

<body>
  <h1>user.html</h1>
  <!-- 修正 -->
  <a sec:authorize="hasAuthority('ROLE_ADMIN')" href="./admin.html" th:href="@{/admin}">管理者ページ</a><br>
  <a href="./logout.html" th:href="@{/logout}">ログアウト</a>
</body>

</html>
```