【Spring Boot】Spring Securityによるログイン認証フィルターの実装(第2編)

Spring Securityによるログイン 認証フィルターの実装(第2編) バックエンド
スポンサーリンク

はじめに

こんにちは。みどりがめです🐢
本記事ではSpring Securityを使用したログイン認証フィルターについてまとめています。

この記事で分かること
  • Spring Securityの導入方法
  • Spring Securityを使用したログイン認証の実装方法
  • ログインユーザーのロールによる画面制御方法

なお、本記事は2つの記事に分割しておりますので、お手数ですが下記の記事も合わせてご確認いただくようお願いします。

ドメインクラスの実装

Userクラスを作成します。DB情報に沿った通常のドメインです。

package jp.co.example.domain;

/**
 * ユーザー情報を表すドメインクラス.
 *
 */
public class UserEntity {

	private String name;
	private String password;
	private String email;
	private String zipcode;
	private String address;
	private String telephone;

	public String getName() {
		return name;
	}

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

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getZipcode() {
		return zipcode;
	}

	public void setZipcode(String zipcode) {
		this.zipcode = zipcode;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getTelephone() {
		return telephone;
	}

	public void setTelephone(String telephone) {
		this.telephone = telephone;
	}
}

ログインに使用するドメインクラスを作成します。userdetails.Userクラスを継承します。

●権限付与について(下記コード中の「Collection<GrantedAuthority> authorityList」)
Spring Secutiryでは認証情報に権限を付与することが必須となっています。第一編の「WebSecutiryConfig.java」にて記載しています。「USER」や「ADMIN」などの権限を付与し、権限により表示ページを変化させるために使用します。

package jp.co.example.domain;

import java.util.Collection;

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

public class LoginUser extends User {
	
      private final UserEntity userEntity;  //ログインに使用するユーザー情報をもたせる。
        //コンストラクタにユーザー情報と権限付与を行うためのリストを渡す。
	public LoginUser(UserEntity userEntity, Collection<GrantedAuthority> authorityList) {
		super(userEntity.getEmail(), userEntity.getPassword(), authorityList);
		this.userEntity = userEntity;
	}
       
	public UserEntity getUserEntity() {
		return userEntity;
	}
}

リポジトリクラスの実装

リポジトリクラスの実装を行います。
今回の場合、メールアドレスをもとにユーザーを検索するメソッドのみで問題ございません。

/**
 * ユーザー情報を扱うリポジトリクラス.
 *
 */
@Repository
public class UserRepository {
	@Autowired
	private NamedParameterJdbcTemplate template;

	/**
	 * Userオブジェクトを生成するローマッパー.
	 */
	private static final RowMapper<UserEntity> USER_ROW_MAPPER = new BeanPropertyRowMapper<>(UserEntity.class);

	/**
	 * メールアドレスをもとにユーザーを検索します.
	 * 
	 * @param email メールアドレス
	 * @return ユーザー情報
	 */
	public UserEntity findByEmail(String email) {
		StringBuilder sql = new StringBuilder();
		sql.append("SELECT id,name,email,password,zipcode,address,telephone from users WHERE email=:email;");
		SqlParameterSource param = new MapSqlParameterSource().addValue("email", email);
		List<UserEntity> userList = template.query(sql.toString(), param, USER_ROW_MAPPER);
		if (userList.size() == 0) {
			return null;
		}
		return userList.get(0);
	};
}

サービスクラスの実装

UserDetailServiceインターフェイスを実装したサービスクラスを作成します。
このクラスではログイン後のメンバー情報に権限情報を付与する役割を担います。
UserDetailServiceインターフェースを実装すると、ログインボタン押下時、自動的にSpringから呼ばれます。

package jp.co.example.service;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 jp.co.example.domain.LoginUser;
import jp.co.example.domain.UserEntity;
import jp.co.example.repository.UserRepository;

@Service
public class LoginUserService implements UserDetailsService {

	@Autowired
	private UserRepository userRepository;
	
	@Override
	public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
            //メールアドレスによりユーザーを検索
		UserEntity userEntity = userRepository.findByEmail(email);
           //バリデーションチェック 本記事の趣旨とは異なるので実装の詳しい説明は省略。 
		if(userEntity == null) {
			throw new UsernameNotFoundException("そのEmaiは登録されていません");
		}
           //権限情報を格納するためのリストの作成、権限の付与
		Collection<GrantedAuthority> authorityList = new ArrayList<>();
		authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
//		if(member.isAdmin()) {
//		authorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN")); // 管理者権限付与
//	}
           //ログインユーザーのコンストラクタを使用してインスタンスを生成する。
        return new LoginUser(userEntity,authorityList);
	}
}

コントローラークラスの作成

今回は仕様把握しやすくするため、
ログイン前でも通れるパスを扱うコントローラーと、ログイン後しか通れないパスを扱うコントローラーでクラスを分けて作成します。

●ログイン前でも通れるパスを扱うコントローラー

package jp.co.example.controller;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import jp.co.example.domain.UserEntity;
import jp.co.example.form.RegisterUserForm;
import jp.co.example.service.UserService;

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

	@Autowired
	private UserService userService;

	/**
	 * ログイン画面へ遷移します.
	 *
	 * @return login.html
	 */
	@RequestMapping(path = "/")
      // 設定ファイルでログイン失敗時にはerror=tureを渡すようにしている。
     //   ⇒コンソールに「ログインに失敗しました」と表示される。(ログイン成功時には何も表示されない。)
	public String showLogin(@RequestParam(required = false) String error) {
		System.err.println("login error:" + error);
		if (error != null) {
			System.err.println("ログインに失敗しました。");
		}
		return "login";
	}
}

●ログイン後しか通れないパスを扱うコントローラー

package jp.co.example.controller;

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

import jp.co.example.domain.LoginUser;

@Controller
@RequestMapping("/afterLogin")
public class AfterLoginController {
	
	/**
	 * トップページに遷移します.
	 * (引数はログインしたユーザーを取得するための記述)
	 * @return top.html
	 */
	@RequestMapping(path = "/top")
	public String showTop(Model model,@AuthenticationPrincipal LoginUser loginUser) {
            // ログインしたユーザー情報を画面に表示するために記述。
		model.addAttribute("loginUserName", loginUser.getUsername());
		return "top";
	}

	/**
	 * コンテンツページに遷移します.
	 * 
	 * @return content.html
	 */
	@GetMapping("/contents")
	public String showContent() {
		return "content";
	}
}

htmlの作成

●ログイン画面

2つ注意点(ソースコード中赤字)があります。
①name属性は設定ファイルに記述したものと合わせる必要があります。
フォームの送信先をth:action属性で指定する。⇒Spring SecurityはCSRF対策が有効となっています。Thymeleafでth:actionとしてあげることでトークンを生成してURLに乗せてくれます。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>セキュリティサンプル</title>
</head>
<body>
	<div class="container">
		<!-- login form -->
		<div class="row">
			<div
				class="col-lg-offset-3 col-lg-6 col-md-offset-2 col-md-8 col-sm-10 col-xs-12">
				<div class="well">
					<form method="post" th:action="@{/login}">
						<fieldset>
							<legend> ログイン </legend>
							<div class="form-group">
								<label for="inputEmail">メールアドレス:</label> <input type="text"
									id="email" class="form-control" placeholder="Email"
									name="email">
							</div>
							<div class="form-group">
								<label for="inputPassword">パスワード:</label> <input type="password"
									id="password" class="form-control" placeholder="Password"
									name="password">
							</div>
							<div class="form-group">
								<button type="submit" class="btn btn-primary">ログイン</button>
							</div>
						</fieldset>
					</form>
				</div>
			</div>
		</div>
		<div class="row">
			<div class="text-center">
				<a href="register_user.html" th:href="@{/register}">ユーザ登録はこちら</a>
			</div>
		</div>
	</div>
</body>
</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 http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>セキュリティサンプル</title>
</head>
<body>
	<div class="container">
		<p>ログイン成功</p>
		<p>ログイン者:<span th:text="${loginUserName}"></span></p>
		<p><span sec:authorize="hasRole('ROLE_USER')"><span>ユーザーロールを持っています</span></span></p>
		<p><span sec:authorize="hasRole('ROLE_ADMIN')"><span>管理者ロールを持っています</span></span></p>
		<a th:href="@{/afterLogin/contents}">コンテンツページへ</a>
	</div>
	<!-- end container -->
</body>
</html>

●ログイン後ページ②(ログアウトリンクを設置)

ログアウトのためのフォームを設置しています。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>セキュリティサンプル</title>
</head>
<body>
	<div class="container">
		ログインしていないと見れません。
		<form th:action="@{/logout}" method="GET">
			<button type="submit">ログアウト</button>
		</form>
		</div>
</body>
</html>

終わりに

最後までご覧いただきありがとうございました。ご指摘等がございましたら頂けますと嬉しいです。
引き続き、プログラミングについて定期的に発信していきますのでよろしくお願いします!
また、もしよろしければtwitterもフォローしていただけると嬉しいです!🐢
Twitterリンク

コメント