SpringSecurity+MyBatis＋MySQLでログイン機能を作る※余計なものはナシ※

# 目標物
![Videotogif (6).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1044070/d4076d01-96bd-feba-95f2-a2723a96cf3d.gif)

- usernameとpasswordでログイン
- ログインができたら、helloに遷移
- helloではログインusernameを表示する
- ログアウトボタンでログアウトする

今回はvalidationやユーザの新規登録は実装しません。
ログイン⇒ログイン後画面⇒ログアウトのみの簡単な実装となります。

# 対象者
- MyBatisをある程度使える（わからない方は↓を参考にしてください）
https://qiita.com/sumichan/items/bdc2a0e909416ae55162

- SpringSecurityを初めて実装する人
- 簡単なものから初めてみたい人

# 使っている環境
■OS : Windows10
■IDE : Eclipse
■Maven
■SpringBoot
■DB : MySQL8.0.22

#構成
![スクリーンショット 2021-02-08 235818.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1044070/7978f192-9be7-c5b6-31a9-7075f9be7c88.png)


# pom.xml
楽ちんなのでlombokを使っています。

```xml


		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.4</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
                <dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
```

# application.properties

```
spring.datasource.url=jdbc:mysql://localhost:3306/XXXXX?serverTimezone=JST
spring.datasource.username=XXXXXX
spring.datasource.password=XXXXXX
spring.datasource.schema=classpath:schema.sql
spring.datasource.data=classpath:data.sql
spring.datasource.initialization-mode=always
```
# schem.sql

```sql

CREATE TABLE IF NOT EXISTS loginUser(
  userId BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(64) NOT NULL,
  password VARCHAR(128) NOT NULL
);
```

# data.sql

パスワードの変換は [こちら](http://www.tekboy.net/bcrypt-calculator) を使いました。

- username hoge
- password 12345

※今回は簡単なusername,passwordですが、本当に使うときはセキュリティ強化してください。

```sql

INSERT INTO loginUser(username, password) VALUES('hoge', '$08$yI3HNSrpzBKPbSUaAtTxB.DgkicwoYkLFoIsupYMV6BqJ1x9zeyHO');
```

# SecurityConfig.java
WebSecurityConfigurerAdapterを継承すると、security関係の設定ができるクラスになります。

- BCryptでパスワードを暗号化します
- セキュリティの設定をします
- 認証方法の設定をします

詳細はコメントアウトの通りです。

```java
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private UserDetailsService userDetailsService;

	@Bean //パスワードのエンコード
	public BCryptPasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Override //securityの設定をする
	protected void configure(HttpSecurity http) throws Exception {

		http
				.authorizeRequests()//ルール、アクセスポリシーの設定
				.antMatchers("/login").permitAll()//loginは認証なしでaccessできる
				.anyRequest().authenticated()//↑以外のすべてのURLリクエストをloginしないと見れない
				.and()
				.formLogin()//ログインの設定
				.loginPage("/login")
				.defaultSuccessUrl("/hello", true)//ログインが成功したら/helloにいく
				.and()
				.logout()//ログアウトの設定
				.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));//logoutのURLを/logoutにする
	}

	@Override //認証方法の設定を行う
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {

		auth
				//userDetailsServiceを使って、認証を行う
				.userDetailsService(userDetailsService);
	}
}
```

# MyUser.java

UserDetailsはインターフェースなので、処理は何も書いていないメソッドだけのクラスです。
なので実装する必要があります。

```java
package com.example.demo.securityUser;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Getter;
import lombok.Setter;


public class MyUser implements UserDetails{

	@Getter
	@Setter
	private String username;

	@Getter
	@Setter
	private String password;

	private Collection<GrantedAuthority> authorities;

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}

	@Override
	public boolean isAccountNonExpired() {
		// TODO 自動生成されたメソッド・スタブ
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		// TODO 自動生成されたメソッド・スタブ
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		// TODO 自動生成されたメソッド・スタブ
		return true;
	}

	@Override
	public boolean isEnabled() {
		// TODO 自動生成されたメソッド・スタブ
		return true;
	}

}
```


今回はusernameとpasswordだけしか使わないので、このふたつのgetter,setterを書きます。
その他は今回は使いませんが、実装は必要なのでtrueにしておいてください。
（実装されていないメソッドの追加をすると、勝手に書いてくれます。）
[インターフェース UserDetails公式説明](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/UserDetails.html)  
![スクリーンショット 2021-02-17 233459.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1044070/bfabff2d-fe4d-1d3c-72e4-b8cc9e8de425.png)

#UserDetailsServiceImpl
UserDetailsServiceを実装したクラスを作る。
必ず`loadUserByUsername(String username)`が必要になります。
入力したusernameがDBにあるか探して、UserDetailsに結果を返します。

```java
package com.example.demo.SecurityUserDetails;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.example.demo.securityMapper.UserMapper;
import com.example.demo.securityUser.MyUser;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

	@Autowired //mapperをインスタンス化。DB接続をするMapperクラスを参照
	private UserMapper userMapper;

	//loadUserByUsernameは実装が必要
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

		//findByUsernameで見つけてきたユーザ情報をmyUserに入れる
		MyUser myUser = userMapper.findByUsername(username);

		//UserDetailsにreturn
		return myUser;

	}
}

```

# UserMapper.java
Mapperクラスなのでinterfaceにします。
入力されたusernameで、一致したレコードを取ってきています。

```java
package com.example.demo.securityMapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.example.demo.securityUser.MyUser;

@Mapper
public interface UserMapper {

	//select文。userテーブルから、usernameが一致しているものを検索
	@Select("select * from loginuser where username = #{username}")
	public MyUser findByUsername(String username);//識別する

}

```

# SecurityController.java
独自のログイン画面に行く指示と、ログインが成功したらhello.htmlに行く指示をしています。
helloに行くときはusernameを表示できるように、Authenticationに入っているログイン情報を"username"に詰めます。

```java
package com.example.demo.securityController;

import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SecurityController {

	@GetMapping("/login") //configのloginPageでloginに来るように指定したので
	public String login() {
		return "login";//自作のloginページを表示
	}

	@GetMapping("/hello") //configのdefaultSuccessUrlでhelloに来るように指定したので
	public String hello(Authentication loginUser,Model model) {

		/*AuthenticationでログインUserの情報を使うことができるので
		modelを使って、"username"にusernameを詰める*/

		model.addAttribute("username",loginUser.getName());

		return "hello";//helloを表示。usernameをhelloで表示できる
	}

}
```
#login.html
なんでもいいのでform画面を作ります。
`th:action="@{/login}" method="post"`は絶対postにしてください。
デフォルトだとgetになってしまっているので、SpringSecurityの認証機能が使えなくなります。

```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>


	<div class="wrapper">
		<form th:action="@{/login}" method="post" name="Login_Form"
			class="form-signin">
			<h2 class="form-signin-heading">ログインしてください</h2>

			<hr class="colorgraph">
			<br>
			<input type="text"  name="username" placeholder="username" /><br>
			<input type="password"  name="password" placeholder="password" /> <br>
			<button name="Submit" value="Login" type="Submit">Login</button>
		</form>
		<br>
	</div>
</body>
</body>
</html>
```


#hello.html
なんでもいいのでログイン成功後の画面を作ります。
controllerでusernameに名前を詰めたので、`th:text="${username}"`で表示できます。
`th:action="@{/logout}"`と書けばログアウトしてくれます。（勝手にやってくれる、すごい）

```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>

	<h1>ログイン成功</h1>
	<h2><span th:text="${username}" ></span> さんHello!!!!</h2>

	<form th:action="@{/logout}" method="post">
	<button>Logout</button>
	</form>

</body>
</html>
```

#`http://localhost:8080`を実行する
port番号を指定していたら変えてください。
無事にログイン⇒ログアウトできれば成功です！
お疲れ様でした！

#ソースコード
https://github.com/IwaiSumire/trial/tree/main/Qiita210216

#参考
[YouTube](https://youtu.be/V3Ri5bV6IT4)
[書籍：Spring Boot 2 入門: 基礎から実演まで](https://www.amazon.co.jp/Spring-Boot-2-%E5%85%A5%E9%96%80-%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E5%AE%9F%E6%BC%94%E3%81%BE%E3%81%A7-ebook/dp/B0893LQ5KY/ref=sr_1_2?dchild=1&hvadid=335595188291&hvdev=c&jp-ad-ap=0&keywords=spring%E5%BE%B9%E5%BA%95%E5%85%A5%E9%96%80&qid=1613649075&sr=8-2&tag=yahhyd-22)
[書籍：Spring解体新書](https://www.amazon.co.jp/%E3%80%90%E5%BE%8C%E6%82%94%E3%81%97%E3%81%AA%E3%81%84%E3%81%9F%E3%82%81%E3%81%AE%E5%85%A5%E9%96%80%E6%9B%B8%E3%80%91Spring%E8%A7%A3%E4%BD%93%E6%96%B0%E6%9B%B8-Boot2%E3%81%A7%E5%AE%9F%E9%9A%9B%E3%81%AB%E4%BD%9C%E3%81%A3%E3%81%A6%E5%AD%A6%E3%81%B9%E3%82%8B%EF%BC%81Spring-Security%E3%80%81Spring-JDBC%E3%80%81Spring-MyBatis%E3%81%AA%E3%81%A9%E5%A4%9A%E6%95%B0%E8%A7%A3%E8%AA%AC%EF%BC%81-ebook/dp/B07H6XLXD7/ref=sr_1_3?dchild=1&hvadid=335595188291&hvdev=c&jp-ad-ap=0&keywords=spring%E5%BE%B9%E5%BA%95%E5%85%A5%E9%96%80&qid=1613649075&sr=8-3&tag=yahhyd-22)
[公式チュートリアル](https://spring.pleiades.io/guides/gs/securing-web/)

#はまったところ
- Userクラスをimplementsしたクラスを作っていなかった。（作る意味が分からなかった）
- login.htmlが th:action="@{/login}になっておらず、CSRF対策用になっていなかった（thがなかった。formに書いておくと勝手にCSRF対策してくれるようになる。）   

CSRF・・・ログイン情報を保持したまま、悪意のある人がURLをクリックした場合、意図せず情報・リクエスト送信されてしまうこと。

#最後に
スッキリしたログイン機能を作ってみました。
SpringSecurity、裏で色々やってくれすぎて難しいですね。