0
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SpringBoot基礎メモ

Last updated at Posted at 2020-11-10

SpringBoot解体新書でお勉強したことをまとめておきます。

環境

Eclipse 2020-6
SpringBoot 2.3.5
Thymeleaf

参考

SpringBoot解体新書
TERASOLUNA Global Framework Development Guideline

SpringBootプロジェクトの新規作成

ファイル→新規→その他→Springスターター・プロジェクト→プロジェクト名入力→次へ
→project dependencies画面(ライブラリ選択)
→DevTool/Lombok/H2database/JDBC API/Thymeleaf/Spring Web/検証/MySQL Driver を選択
→完了

ライブラリの説明

・DevTool
自動でアプリを再起動してくれる。
コードを修正して保存すると自動再起動。

・Lombok
setter getterを自動で実装。

・JDBC API
Springが用意したJDBCが使える。
通常のJDBCより簡単にSQLを実行可能。

・Thymeleaf(タイムリーフ)
SpringBoot標準で使われるテンプレートエンジン。
JSPの問題点であったHTMLの中にJavaのコードが入って可読性が悪くなる点を改善したもの。

・Spring Web
SpringMVC,SpringBootが使えるようになる。

・検証
バリデーションができるようになる。

・MySQL Driver
MySQLがつかえるようになる。

SpringBootでCSSやjsファイルを読み込む

staticフォルダの中に入れないと読み込まない。
パス指定はstaticフォルダ以下を書く↓

hello.html
<link th:href="@{/css/style.css}" rel="stylesheet"> 
<script th:src="@{/js/test.js}"></script>

※注意
SpringSecurityを入れている場合は別途設定が必要。
セキュリティ設定クラスをいじる。

GETリクエスト

http://localhost:8080/loginにアクセス
@GetMapping("/login)がリクエストを受け取る
→return "login";でlogin.htmlをレスポンスとして返す。
→login.htmlが画面に表示される。
※htmlファイルはtemplateディレクトリ内に置く。

loginController.java
@GetMapping("/login") 
public String findAccount() { 
        //ログイン画面へ遷移 
	return "login"; 
}

POSTリクエスト

submitボタン押す
@PostMapping("/login")がリクエストを受け取る
@RequestParamで値を受け取る
→値をModelクラスに加える
→htmlでModelクラスの値を表示

login.html
<!DOCTYPE html> 
<html xmlns:th="http://www.thymeleaf.org"> 
<head> 
<meta charset="UTF-8"> 
<title>ログイン</title> 
</head> 
<body> 
<form method="post" action="/login" name="loginform"> 
<input type="text" name="accountEmail" th:value="${accountEmail}" required> 
<input type="submit" value="送信"> 
</form> 
</body> 
</html>

Logincontroller.java
@Controller
public class LoginController {
       @PostMapping("/login") 
	public String executeSQL( 
			@RequestParam("accountEmail") String accountEmail,  //@RequestPramで値受け取り
			Model model) {

        //modelに値をセット
		model.addAttribute("accountEmailModel", accountEmail);  
			return "loginsuccess"; 
		} 
	}
}

loginsuccess.html
<!DOCTYPE html> 
<html xmlns:th="http://www.thymeleaf.org"> 
<head> 
<meta charset="UTF-8"> 
<title>ログイン成功</title> 
</head> 
<body> 
ここにアカウントEメールが表示↓↓ 
<p th:text="${accountEmailModel}"></p> 
</body> 
</html>

バリデーションの基本

・ポイント
@Validatedにモデルクラス(javabeans的なもの)を渡すこと。

バリデーションの流れ
0、Mavenに依存関係を追記。
1、モデルクラスに@Sizeとかつける。
2、コントローラクラスの引数に(@ModelAttribute @Vlidated)つける
3、messages.properties変える
4、htmlにthymeleafでエラーメッセージの表示コードを書く (spring解体新書p2286

0、Mavenに依存関係を追記。

pom.xml
<!-- Spring Boot Starter Validation -->
<dependency> 
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring-boot-starter-validation</artifactId> 
</dependency>

1、モデルクラスのフィールドに@Sizeとかつける。

AccountModel.java
import javax.validation.constraints.NotBlank; 
import javax.validation.constraints.Pattern; 
import javax.validation.constraints.Size; 
public class TestModel { 

	@NotBlank 
	@Size(min = 3, max = 15) 
	@Pattern(regexp = "^[ぁ-んー]*$") 
	private String name; 

	public String getName() { 
		return name; 
	} 
	public void setName(String name) { 
		this.name = name; 
	} 
}

2、コントローラクラスのメソッド引数に(@ModelAttribute @Vlidated バリデーションしたいモデルクラス)つける

HelloController.java
import org.springframework.stereotype.Controller; 
import org.springframework.ui.Model; 
import org.springframework.validation.BindingResult; 
import org.springframework.validation.annotation.Validated; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.ModelAttribute; 
import org.springframework.web.bind.annotation.PostMapping; 
@Controller 
public class HelloController { 

	@GetMapping("/hello") 
	public String getHello( 
			@ModelAttribute TestModel tModel) { 
		return "hello"; 
	} 
        //「@Validated TestModel」でTestModelクラスをバリデーション指定。
	@PostMapping("/hello") 
	public String postHello( 
			@ModelAttribute @Validated TestModel tModel, 
			BindingResult result, 
			Model model) { 
		System.out.println("post"); 

//入力エラーのときはgetHelloを呼び出してhello.htmlに戻る。
		if (result.hasErrors()) { 
			return getHello(tModel); 
		} 
		return "success"; 
	} 
}

3、エラーメッセージを変更する。
messages.propertiesを変更。

messages.properties
# ================================================= 
# バリデーションエラーメッセージ 
# ================================================= 
# 名前 
name=名前 
NotBlank.name={0}を入力してください。 
Size.name={0}は{2}文字以上{1}文字以下で入力してください。 
Pattern.name={0}は全角ひらがな入力してください。漢字不可。

{0}には フィールド名=XXX のXXXが入る。ここだと「名前」
{1}{2}にはアノテーション名が昇順で入る。ここだとmin max なので{1}がmax、{2}がmin

4-1、htmlにthymeleafでエラーメッセージの表示コードを書く

hello.html
<body> 
<form method="POST" action="/hello" name="nameform"> 
<input type="text"  name="name" th:value="${name}"> 
<input type="submit"> 

ここにエラーメッセージが表示↓
<span 
th:if="${#fields.hasErrors('testModel.name')}" 
th:errors="*{testModel.name}">
</span> 
</form> 
</body>

4-2、ブートストラップバージョン
入力エラー時にis-invalidクラスを付与する。
エラーメッセージを赤字表示。

hello.html
<body> 
	<form method="POST" action="/hello" name="nameform"> 
		<div class="form-group"> 
			<label for="name">・名前(bootstrapバージョン)</label> <input 
				class="form-control" type="text" 
				th:classappend="${#fields.hasErrors('testModel.name2')} ? 'is-invalid'" 
				id="name" name="name2" th:value="${name2}" placeholder="Enter name"> 
			<small id="nameHelp" class="form-text text-muted">3~15文字</small> <span 
				class="text-danger" th:if="${#fields.hasErrors('testModel.name2')}" 
				th:errors="*{testModel.name2}"> name error </span> 
		</div> 
		<input type="submit"> 
	</form> 
</body>

例外処理の基本

〇流れ
1、共通エラーページを用意
2、エラーメッセージを変更するには@ControllerAdvice クラスを用意

1、共通エラーページを用意
templatesディレクトリにerror.htmlを作る。
これだけでエラー出るとこのページが勝手に表示される。すごい。

error.html
<!DOCTYPE html> 
<html xmlns:th="http://www.thymeleaf.org"> 
<head> 
<meta charset="UTF-8"> 
<title>Error</title> 
</head> 
<body> 
<h1 th:text="${status} + ':' + ${error}"></h1> 
<p th:text="${message}"></p> 
<form method="POST" th:action="@{/login}"> 
<button class="btn btn-link" type="submit">ログイン画面に戻る</button> 
</form> 
</body> 
</html>

2、エラーメッセージを変更するには@ControllerAdvice クラスを用意
@ControllerAdvice
@ControllerAdviceクラスでアプリケーション全体の例外処理を実装する。

@ExcetionHandler
@ExcetionHandlerを付けたメソッドを作るとExceptionクラス毎の例外処理を実装できる。
(NullPointerExceptionとか)

GlobalControllAdvice.java
import org.springframework.http.HttpStatus; 
import org.springframework.stereotype.Component; 
import org.springframework.ui.Model; 
import org.springframework.web.bind.annotation.ControllerAdvice; 
import org.springframework.web.bind.annotation.ExceptionHandler; 
@ControllerAdvice 
@Component 
public class GlobalControllAdvice { 
	@ExceptionHandler(Exception.class) 
	public String ExceptionHandler(Exception e, Model model) { 
		model.addAttribute("error", "内部サーバーエラー"); 
		model.addAttribute("message", "サーバーエラーが発生しました。"); 
		model.addAttribute("status", HttpStatus.INTERNAL_SERVER_ERROR); 
		return "error"; 
	} 

@ExceptionHandler(NullPointerException.class) 
	public String NullPointerExceptionHandler(Exception e, Model model) { 
		model.addAttribute("error", "ぬるぽ"); 
		model.addAttribute("message", "■━⊂( ・∀・) 彡 ガッ"); 
		model.addAttribute("status", HttpStatus.INTERNAL_SERVER_ERROR); 
		return "error"; 
	}
}

セキュリティの基本(Spring Secrity)

〇ポイント
パスワードは暗号化必須。
デフォルトでCSRF対策が入っている。
ロール名はROLE_ADMINやROLE_GENERALのように、ROLE_ で始める。

ログイン・ログアウト

1、Mavenに依存関係を追記。
2、セキュリティ設定クラスにアクセス可否などを書く。
3、htmlでログインエラーメッセージ表示。

1、Mavenに依存関係を追記。

pom.xml
<!-- SpringSecurity-->
<dependency> 
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring-boot-starter-security</artifactId> 
</dependency> 

<!-- Thyemeleaf拡張--> 
<dependency> 
	<groupId>org.thymeleaf.extras</groupId> 
	<artifactId>thymeleaf-extras-springsecurity5</artifactId> 
</dependency>

2、セキュリティ設定クラスにアクセス可否などを書く。
セキュリティ設定用クラスには@EnableWebSecurityを付けます。

SecurityConfig.java
import javax.sql.DataSource;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private DataSource dataSource;

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

	@Override
	public void configure(WebSecurity web) throws Exception {
		//静的リソースを除外 。wejars配下とcss配下を除外。
		web.ignoring().antMatchers("/webjars/**", "/css/**");

	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		/* 注意1
		 * ログイン不要ページの設定
		 * 順番が大事。
		 * メソッドチェーンは上から順に設定される。
		 * anyRequest().authenticated().antMatchers("xxx").permitAll()
		 * にすると全リクエストに認証が必要になってしまう。
		 */
		http
				.authorizeRequests()
				.antMatchers("/webjars/**").permitAll() //webjarsへアクセス許可
				.antMatchers("/css/**").permitAll() //cssへアクセス許可
				.antMatchers("/login").permitAll() //ログインページは直リンクOK
				.antMatchers("/signup").permitAll() //ユーザー登録画面は直リンクOK
				.antMatchers("/index").permitAll() //ホーム画面は直リンクOK
				.antMatchers("/rest/**").permitAll() //RESTは直リンクOK
				.antMatchers("/adminpage").hasAuthority("ROLE_ADMIN") //アドミンページアクセスをアドミンに許可
				.anyRequest().authenticated(); //それ以外は直リンク禁止

		//ログイン処理
		http
				.formLogin()
				.loginProcessingUrl("/login") //ログイン処理のパス
				.loginPage("/login") //ログインページの指定
				.failureUrl("/login") //ログイン失敗時の移動先
				
				/*※注意2
				 * usernameParameterとpasswordParameterの引数は
				 * htmlのname属性と同じにする。
				 * ここではlogin.htmlのinputタグのname
				 */
				.usernameParameter("accountEmail") //ログインページのユーザーID
				.passwordParameter("accountPassword") //ログインページのパスワード
				.defaultSuccessUrl("/index", true); //ログイン成功後の移動先

		//ログアウト処理
		http
				.logout()
				.logoutRequestMatcher(new AntPathRequestMatcher("/logout")) //GETでログアウトする用。普通はPOSTなので不要。
				.logoutUrl("/logout")
				.logoutSuccessUrl("/login");

		//CSRF対策をOFFにする。デフォルトではON
		//http.csrf().disable();
	}

	// ログイン時のアカウント情報を、DBから取得する
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {

		auth.jdbcAuthentication()
				.dataSource(dataSource)
				
				/* 注意3
				 * ユーザーIDとパスワードを取得するSQL文を引数に入れる
				 * ここでは、accountEmailをユーザーIDとしている。
				 * where句の後にand句を付け足すとエラー出る。これでハマった・・・。
				 * 
				 * ユーザ情報を取得するクエリを設定する。
				 * 取得するデータは、「ユーザID」、「パスワード」、「有効フラグ」の順とする。
				 * 「有効フラグ」による認証判定を行わない場合には、「有効フラグ」のSELECT結果を「true」固定とする。
				 * なお、ユーザを一意に取得できるクエリを記述すること。複数件数取得された場合には、1件目のレコードがユーザとして使われる。
				 */
				.usersByUsernameQuery("SELECT accountEmail, accountPassword, true FROM account WHERE accountEmail = ?")

				/* 注意4
				 *  ユーザーのロールを取得するSQL文を引数に入れる
				 *  ユーザの権限を取得するクエリを設定する。取得するデータは、「ユーザID」、「権限ID」の順とする。
				 *  認可の機能を使用しない場合は、「権限ID」は任意の固定値でよい。
				 */
				.authoritiesByUsernameQuery("SELECT accountEmail, accountRole FROM account WHERE accountEmail = ?")
				.passwordEncoder(passwordEncoder());
	}
}

注意1
ログイン不要ページの設定。
順番が大事。
メソッドチェーンは上から順に設定される。
例えば、anyRequest().authenticated().antMatchers("xxx").permitAll() にすると全リクエストに認証が必要になってしまう。

http
				.authorizeRequests()
				.antMatchers("/webjars/**").permitAll() //webjarsへアクセス許可
				.antMatchers("/css/**").permitAll() //cssへアクセス許可
				.antMatchers("/login").permitAll() //ログインページは直リンクOK
				.antMatchers("/signup").permitAll() //ユーザー登録画面は直リンクOK
				.antMatchers("/index").permitAll() //ホーム画面は直リンクOK
				//.antMatchers("/rest/**").permitAll() //RESTは直リンクOK
				.antMatchers("/adminpage").hasAuthority("ROLE_ADMIN") //アドミンページをアドミンユーザーに許可
				.anyRequest().authenticated(); //それ以外は直リンク禁止

注意2
usernameParameterとpasswordParameterの引数はhtmlのname属性と同じにする。
ここではlogin.htmlのinputタグのname。

.usernameParameter("accountEmail") //ログインページのユーザーID 
.passwordParameter("accountPassword") //ログインページのパスワード 
.defaultSuccessUrl("/index", true); //ログイン成功後の移動先

注意3
ユーザIDとパスワードを取得するSQL文を引数に入れる。
ここでは、accountEmailをユーザーIDとしている。
where句の後にand句を付け足すとエラー出る。
以下、TERASOLUNA Global Framework Development Guidelineから引用。

ユーザ情報を取得するクエリを設定する。
取得するデータは、「ユーザID」、「パスワード」、「有効フラグ」の順とする。
「有効フラグ」による認証判定を行わない場合には、「有効フラグ」のSELECT結果を「true」固定とする。
なお、ユーザを一意に取得できるクエリを記述すること。複数件数取得された場合には、1件目のレコードがユーザとして使われる。

.usersByUsernameQuery("SELECT accountEmail, accountPassword, true FROMaccount WHERE accountEmail = ?")

注意4
ユーザーのロールを取得するSQL文を引数に入れる。
以下、TERASOLUNA Global Framework Development Guidelineから引用。

ユーザの権限を取得するクエリを設定する。取得するデータは、「ユーザID」、「権限ID」の順とする。
認可の機能を使用しない場合は、「権限ID」は任意の固定値でよい。

.authoritiesByUsernameQuery("SELECT accountEmail, accountRole FROM account WHERE accountEmail = ?")
.passwordEncoder(passwordEncoder());

3、htmlでログインエラーメッセージの表示

login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form method="post" th:action="@{/login}">
		<input type="email" name="accountEmail" th:value="${accountEmail}">
		<input type="password" name="accountPassword">
		<button type="submit">ログイン</button>
	</form>

<!-- ログインエラーメッセージ --> 
<p th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null" 
    th:text="${session['SPRING_SECURITY_LAST_EXCEPTION'].message}" 
    class="text-danger">ログインエラーメッセージ</p>
</body>
</html>

4、ログアウトボタンの設置

<form method="post" th:action="@{/logout}">
	<button type="submit">ログアウト</button> 
</form>

サインアップ

・パスワードを暗号化

SignupController.java
@Controller
public class SignupController {

	@Autowired
	AccountMapper accountMapper;

        //パスワードエンコーダー
	@Autowired
	PasswordEncoder passwordEncoder;

	@GetMapping("/signup")
	public String getSignupPage(@ModelAttribute AccountModel acModel, Model model) {
		return "signup";
	}

	@PostMapping("/signup")
	public String executeSignUp(
			@ModelAttribute @Validated AccountModel acModel,
			BindingResult result,
			Model model) {

		if (result.hasErrors()) {
			System.out.println(result);
			return getSignupPage(acModel, model);
		}

		//アカウント新規登録処理
		accountMapper.insertAccount(
				acModel.getAccountEmail(),
                                
                                // パスワードを暗号化
				passwordEncoder.encode(acModel.getAccountPassword()),
				acModel.getAccountName(),
				acModel.getAccountRole());

		//登録したアカウント情報を取得
		AccountModel am = accountMapper.getAccount(
				acModel.getAccountEmail(), acModel.getAccountPassword());

		//アカウント情報をModelクラスに格納
		model.addAttribute("accountModel", am);

		return "index";
	}
}



画面表示の認可

ROLE_ADMINでアクセスした時のみ「消える管理者画面ボタン」を表示する。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
		 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
	<div>
		<form method="get" th:action="@{/adminpage}" sec:authorize="hasRole('ADMIN')">
			<button type="submit">消える管理者画面ボタン</button>
		</form>
	</div>
</body>
</html>

ログイン情報の取得

      Authentication auth = SecurityContextHolder.getContext().getAuthentication();
      String name = auth.getName();//ユーザーネームを取得
0
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?