Spring Security 備忘録

##必ず必要なクラスや設定
|項目|ファイル名|説明|
|----|----|----|
|依存関係の設定|build.gradle||
|Spring Securityの設定|WebSecurityConfig.java||
|ログイン画面|login.html||
|ログインおよびユーザー登録|mapper.xml||
|ログインおよびユーザー登録|mapper.java||
|ユーザー登録|registerMember()|今回はビューから登録しない|
|ログイン情報のEntity|Account.java||
|ログイン情報の照合|DbUserDetailsService.java||
|ログイン情報の照合|DbUserDetails.java||



参考

https://spring.pleiades.io/guides/gs/securing-web/

```securingweb.java
package com.example.householdAccountBook;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HouseholdAccountBookApplication {
	public static void main(String[] args) {
		SpringApplication.run(HouseholdAccountBookApplication.class, args);
	}
}
```
##注意 securingweb.javaが不要な場合
上記のsecuringweb.javaに関しては既に他の<span>@</span>SpringBootApplicationのクラスがあれば作成は不要。

##依存関係の設定
```build.gradle 
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
```
##WebSecurityConfig.javaの作成
```WebSecurityConfig.java
package com.example.householdAccountBook.securingweb;

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.builders.WebSecurity;
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.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  UserDetailsService userDetailsService;
	
  //HTTPリクエストに関する設定
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/", "/home").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.loginProcessingUrl("/authenticate")//認証処理に移るためのURL
				.defaultSuccessUrl("/menu", true) //認証が成功した際に遷移するURL
        .usernameParameter("username")
        .passwordParameter("password")
				.permitAll()
				.and()
			.logout()
				.permitAll();
	}
	
  //認証に利用する様々な設定を生成する
  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(userDetailsService)
          .passwordEncoder(passwordEncoder());
  }
	
  //静的ファイルに関する設定
  @Override
  public void configure(WebSecurity web) throws Exception {
      web.ignoring().antMatchers(
                          "/images/**",
                          "/css/**",
                          "/javascript/**"
                          );
  }	
	
  //パスワードをハッシュ化するために使用する
  @Bean
  PasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
  }
  
}
```
##ポイント
|項目|説明|備考|
|----|----|----|
|<span>@</span>Configuration|Springにコンフィグファイルであることを伝えるためのアノテーション|アノテーション|
|<span>@</span>EnableWebSecurity|<span>@</span>Configurationに付随する。|アノテーション|
|継承クラス|WebSecurityConfigurerAdapterを継承させること|<b>configureメソッド</b>をオーバーライドして認証・認可処理の設定していく|
|configure(HttpSecurity http)|httpリクエスト関連の部分についての設定。※詳細後述|configureメソッド|
|configure(AuthenticationManagerBuilder)|認証に利用する様々な設定を生成する|configureメソッド<br>https://anveloper.com/2021/01/21/authenticationmanagerbuilder%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/|
|configure(WebSecurity web)|静的ファイル(image,css,javascript)のリクエストまで弾いてしまわないための設定|configureメソッド|
|passwordEncoder|今回はパスワードのハッシュ化にBCryptPasswordEncoderを使用する||

##configure(HttpSecurity http)の詳細
|項目|説明|備考|
|----|----|----|
|authorizeRequests()|||
|antMatchers("/", "/home").permitAll()|||
|anyRequest().authenticated()|全てのリクエストに対して認証をかけるようにする。||
|and()|||
|formLogin()|||
|loginPage("/login")|ログインページを指定。||
|defaultSuccessUrl("/menu", true)|認証が成功した際に遷移するURL||
|permitAll()|ログインページへのアクセスは全員許可する。||
|and()|||
|logout()|||
|permitAll();|||

```WebSecurityConfig.java
//（ログインテスト用）
	@Bean
	@Override
	public UserDetailsService userDetailsService() {
	UserDetails user =
	User.withDefaultPasswordEncoder()
	.username("activeUser")
	.password("sendPassword")
	.roles("USER")
	.build();
	return new InMemoryUserDetailsManager(user);
	}
```
|項目|説明|備考|
|----|----|----|
|userDetailsService|同じくWebSecurityConfig.javaに上記メソッドを記述すると、DBを使わずにログインさせられる。ログインテストに軽く使用したが、今回はDBを使うので、テスト後にこのメソッドは削除した。（例では.username("activeUser").password("sendPassword")としている）||

##うまくいかないとき
|症状|原因|備考|
|----|----|----|
|DBからデータをとってこない。（エラーは吐かない）|config.javaをAPの外側に作成していた。|APのディレクトリよりも下の階層に作成しないとうまく動作しない。|

##注意 MvcConfig.javaが不要な場合
このクラスは他の<span>@</span>Controllerを設定したクラスでリクエスト処理が可能なので、その場合は不要になる。

```MvcConfig.java
package com.example.householdAccountBook.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("/").setViewName("home");
		registry.addViewController("/login").setViewName("login");
	}
}
```
##ポイント
|項目|説明|備考|
|----|----|----|
|ログインフォームに誘導しようとした際、View名返すだけのController|別途コントローラークラスで制御している場合、MvcConfig.javaはいらない。|https://uchi-fashion.hatenablog.com/entry/2016/09/13/015015|

##ユーザー情報をハッシュ化してDBへ登録
https://qiita.com/yamateion/items/c1affc4fa8defa814a41
##登録までの大まかな流れ
今回はブラウザからユーザーを登録されると困るので、下記HouseWorkListServiceクラスのregisterMemberメソッドを何らかの処理の途中に走らせてユーザー情報を登録したのち、コメントアウトさせた。いい方法があれば、更新しようと思う。
<b>DB作成</b>

```sql:users.sql
DROP TABLE IF EXISTS `housework_users`;
CREATE TABLE `housework_users` (
  `user_id` int(5) NOT NULL AUTO_INCREMENT,
  `user_name` VARCHAR(50) NOT NULL,
  `password` VARCHAR(255),
  PRIMARY KEY (`user_id`)
) COLLATE utf8mb4_ja_0900_as_cs_ks;
```
|ポイント|説明|備考|
|----|----|----|
|password文字数|ハッシュ化した場合の文字数は最低でも60文字必要||

```HouseWorkListService.java
import org.springframework.security.crypto.password.PasswordEncoder;
/**
省略
*/
@Service
public class HouseWorkListService {

	@Autowired
	HouseworkListMapper hWLtMapper;
	
	@Autowired
    PasswordEncoder passwordEncoder;

/**
省略
*/

// 会員情報をDBに登録。
public void registerMember() {
	//パスワードをハッシュ化して、insertMemberInfo()に渡すオブジェクトにセット。
	Account entity = new Account();
	entity.setUsername("testUser");
	entity.setPassword("testPasword");
	entity.setPassword(passwordEncoder.encode(entity.getPassword()));
	hWLtMapper.insertMemberInfo(entity.getUsername(),entity.getPassword());
}
```
```Account.java
package com.example.householdAccountBook.entity;
// ログインに必要な会員情報を格納するクラス。
import lombok.Data;

@Data
public class Account {
	private String username;
	private String password;
}
```
```Mapper.java
//アカウントの登録
void insertMemberInfo(@Param("username")String username,@Param("password")String password);
```
```Mapper.xml
<!-- IDとパスワードの登録 -->
<insert id="insertMemberInfo">
INSERT 
INTO housework_users (user_name,password)
VALUES(
	#{username},
	#{password})
</insert>
```

##ログインテスト
https://qiita.com/yamateion/items/c1affc4fa8defa814a41

```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}">
	ユーザー名もしくはパスワードが違います。
	</div>
	<div th:if="${param.logout}">
	ログアウトしました。
	</div>
	<form th:action="@{/authenticate}" method="post">
	<div><label> User Name : <input type="text" id="user" name="username"/> </label></div>
	<div><label> Password: <input type="password" id="password" name="password"/> </label></div>
	<div><input type="submit" value="Sign In"/></div>
	</form>
</body>
</html>
```
|ポイント|説明|備考|
|----|----|----|
|「User Name」と「Password」|WebSecurityConfig.javaで設定した.usernameParameter("username")と.passwordParameter("password")に合わせる。||

```DbUserDetailsService.java
package com.example.householdAccountBook.service;
import java.util.Collection;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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 org.springframework.transaction.annotation.Transactional;

import com.example.householdAccountBook.entity.Account;
import com.example.householdAccountBook.mapper.HouseworkListMapper;
import com.example.householdAccountBook.securingweb.DbUserDetails;

@Service
public class DbUserDetailsService implements UserDetailsService{
	@Autowired
	HouseworkListMapper hWLtMapper;
	
	@Transactional(readOnly = true)
	public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {
		//DBからユーザ情報を取得。
		Account account = Optional.ofNullable(hWLtMapper.findAccount(username))
		.orElseThrow(() -> new UsernameNotFoundException("User not found."));
		return new DbUserDetails(account, getAuthorities(account));
	}

	/**
	* 認証が通った時にこのユーザに与える権限の範囲を設定する。
	* @param account DBから取得したユーザ情報。
	* @return 権限の範囲のリスト。
	*/
	private Collection<GrantedAuthority> getAuthorities(Account account) {
		//認証が通った時にユーザに与える権限の範囲を設定する。
		return AuthorityUtils.createAuthorityList("ROLE_USER");
	}
	
}
```
```DbUserDetails.java
package com.example.householdAccountBook.securingweb;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import com.example.householdAccountBook.entity.Account;

public class DbUserDetails extends User {
	//ユーザ情報。 
	private final Account account;

	public DbUserDetails(Account account,Collection<GrantedAuthority> authorities) {
		super(account.getUsername(), account.getPassword(),
		true, true, true, true, authorities);
		this.account = account;
	}

	public Account getAccount() {
		return account;
	}
}
```

```mapper.xml
<!-- IDとパスワード照合 -->
<select id="findAccount" resultType="com.example.householdAccountBook.entity.Account" parameterType="String">
SELECT
	user_name,
	password
FROM
	housework_users
WHERE
	user_name = #{username}
</select>
```
```mapper.java
	//アカウントの検索
	Account findAccount(String username);
```

##ログイン中のユーザー名を取得する方法
```service.java
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
省略
*/
//ログイン中のユーザーネームを取得する
public String rLoginUserName() {
	Authentication auth = SecurityContextHolder.getContext().getAuthentication();
	String name = auth.getName();
	return name;
}
```
https://ja.getdocs.org/spring-security-get-current-logged-in-username-in-spring-security/