23
26

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.

Spring Boot Securityを使ってみる

Last updated at Posted at 2017-12-15

#環境
Spring Boot 1.5.9.RELEASE
Java 8
Maven 4.0.0

#概要
Spring Securityを使用し、DBにあるログイン情報と照合し認証をする。
DBはH2DBを使用し、ORMはDomaを使用します。

#はじめに
Spring Inirializrでプロジェクトを生成します。
spring-initializr.png

今回はORMにDomaを使用するので、注釈処理を設定しておきます。
注釈処理.png
ファクトリー・パス.png

#コード
デフォルトから変更しているもののみ記載します。

pom.xml(※一部抜粋)
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.seasar.doma.boot/doma-spring-boot-starter -->
		<dependency>
			<groupId>org.seasar.doma.boot</groupId>
			<artifactId>doma-spring-boot-starter</artifactId>
			<version>1.1.1</version>
		</dependency>
	</dependencies>
UserEntity.java
package com.example.springbootsecuritysample.entity;

import java.util.Collection;

import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.jdbc.entity.NamingType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Getter;
import lombok.Setter;

/**
 * USERテーブルのEntity
 * @author T.Harao
 *
 */
@Entity(naming = NamingType.SNAKE_UPPER_CASE)
@Getter
@Setter
public class UserEntity implements UserDetails {

	@Id
	private String userId;
	private String password;

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return null;
	}
	@Override
	public String getUsername() {
		return userId;
	}
	/**
	 * UserDetailsServiceでチェックするパスワードを返却する
	 * Lombokを使用している場合、フィールドに「password」があれば、
	 * @GetterでgetPassword()を生成してくれる為、不要
	 */
	@Override
	public String getPassword() {
		return password;
	}
	@Override
	public boolean isAccountNonExpired() {
		return true;
	}
	@Override
	public boolean isAccountNonLocked() {
		return true;
	}
	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}
	@Override
	public boolean isEnabled() {
		return true;
	}

}
UserDao.java
package com.example.springbootsecuritysample.dao;

import org.seasar.doma.Dao;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;

import com.example.springbootsecuritysample.entity.UserEntity;

/**
 * USERテーブルにアクセスするDAO
 * @author T.Harao
 *
 */
@Dao
@ConfigAutowireable
public interface UserDao {

	@Select
	public UserEntity selectByUserId(String userId);

}
AuthService.java
package com.example.springbootsecuritysample.service;

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.springbootsecuritysample.dao.UserDao;
import com.example.springbootsecuritysample.entity.UserEntity;

/**
 * 認証を扱うService
 * @author T.Harao
 *
 */
@Service
public class AuthService implements UserDetailsService {

	@Autowired
	private UserDao dao;

	/**
	 * ユーザー読み込み
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

		if(username == null || "".equals(username)) {
			throw new UsernameNotFoundException("ユーザーIDが未入力です");
		}

		UserEntity user = dao.selectByUserId(username);
		if(user == null) {
			throw new UsernameNotFoundException("ユーザーIDが不正です。");
		}

		return user;
	}

}
IndexForm.java
package com.example.springbootsecuritysample.form;

import org.hibernate.validator.constraints.NotEmpty;

import lombok.Getter;
import lombok.Setter;

/**
 * IndexControllerで使用するForm
 * @author T.Harao
 *
 */
@Getter
@Setter
public class IndexForm {

	@NotEmpty
	private String userId;
	@NotEmpty
	private String password;

}
IndexController.java
package com.example.springbootsecuritysample.controller;

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.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.example.springbootsecuritysample.form.IndexForm;

/**
 * ログイン
 * @author T.Harao
 *
 */
@Controller
@RequestMapping({"/", "/index"})
public class IndexController {

	@ModelAttribute
	public IndexForm initForm(){
		return new IndexForm();
	}

	/**
	 * 初期表示
	 * @param mv
	 * @return
	 */
	@RequestMapping(value = {"/", "/index"}, method = RequestMethod.GET)
	public ModelAndView index(ModelAndView mv) {
		mv.setViewName("index/index");
		return mv;
	}

	/**
	 * 認証エラー時
	 * @param mv
	 * @return
	 */
	@RequestMapping(value = {"/", "/index"}, method = RequestMethod.POST)
	public ModelAndView login(@ModelAttribute @Validated IndexForm form, BindingResult result, ModelAndView mv) {

		if(!result.hasErrors()) {
			mv.addObject("errorMessage", "ログイン情報が間違っています");
		}

		mv.setViewName("index/index");
		return mv;
	}

}
MenuController.java
package com.example.springbootsecuritysample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

/**
 * メニュー
 * @author T.Harao
 *
 */
@Controller
@RequestMapping("/menu")
public class MenuController {

	/**
	 * 初期表示
	 * @param mv
	 * @return
	 */
	@RequestMapping(value = {"/", "/index"}, method = RequestMethod.GET)
	public ModelAndView index(ModelAndView mv) {
		mv.setViewName("menu/index");
		return mv;
	}

}
FailureHandler.java
package com.example.springbootsecuritysample.config.handler;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

/**
 * 認証失敗時のハンドラ
 * @author T.Harao
 *
 */
@Component
public class FailureHandler implements AuthenticationFailureHandler {

	/**
	 * 認証失敗時
	 */
	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {

		//「/」にForwardする
		RequestDispatcher dispatch = request.getRequestDispatcher("/");
		dispatch.forward(request, response);

	}

}
SuccessHandler.java
package com.example.springbootsecuritysample.config.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
/**
 * 認証成功時のハンドラ
 * @author T.Harao
 *
 */
@Component
public class SuccessHandler implements AuthenticationSuccessHandler {

	/**
	 * 認証成功時
	 */
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {

		//「/menu/」にリダイレクトする
		response.sendRedirect(request.getContextPath() + "/menu/");

	}

}
SecurityConfig.java
package com.example.springbootsecuritysample.config;

import org.springframework.beans.factory.annotation.Autowired;
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.password.NoOpPasswordEncoder;

import com.example.springbootsecuritysample.config.handler.FailureHandler;
import com.example.springbootsecuritysample.config.handler.SuccessHandler;
import com.example.springbootsecuritysample.service.AuthService;

/**
 * セキュリティ設定
 * @author T.Harao
 *
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private AuthService service;

	@Autowired
	private FailureHandler failureHandler;

	@Autowired
	private SuccessHandler successHandler;

	/**
	 * WebSecurityの設定
	 */
	@Override
    public void configure(WebSecurity web) throws Exception {

		// 静的リソース(images、css、javascript)とH2DBのコンソールに対するアクセスはセキュリティ設定を無視する
		web.ignoring().antMatchers("/css/**", "/fonts/**", "/images/**", "/js/**", "/h2-console/**");

	}

	/**
	 * HttpSecurityの設定
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {

		//認可の設定
		http.authorizeRequests()
			//認証無しでアクセスできるURLを設定
			.antMatchers("/", "/index/**").permitAll()
			//上記以外は認証が必要にする設定
			.anyRequest().authenticated();

		//ログイン設定
		http.formLogin()
			//認証処理のパスを設定
			.loginProcessingUrl("/index/login")
			//ログインフォームのパスを設定
			.loginPage("/")
			.loginPage("/index/**")
			//認証成功時にリダイレクトするURLを設定
			.defaultSuccessUrl("/menu/")
			//認証失敗時にforwardするURLを設定
			.failureForwardUrl("/")
			//認証成功時にforwardするURLを設定
			//.successForwardUrl("/")
			//認証成功時に呼ばれるハンドラクラスを設定
			//.successHandler(successHandler)
			//認証失敗時にリダイレクトするURLを設定
			//.failureUrl("/menu/")
			//認証失敗時に呼ばれるハンドラクラスを設定
			//.failureHandler(failureHandler)
			//ユーザー名、パスワードのパラメータ名を設定
			.usernameParameter("userId").passwordParameter("password");

	}

	/**
	 * 設定
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {

		//パスワードは平文でDBに登録する為、「NoOpPasswordEncoder」を設定する
		auth.userDetailsService(service)
			.passwordEncoder(NoOpPasswordEncoder.getInstance());

	}


}
layout.html
<!DOCTYPE html>
<html
	xmlns        = "http://www.w3.org/1999/xhtml"
	xmlns:th     = "http://www.thymeleaf.org"
	xmlns:layout = "http://www.ultraq.net.nz/thymeleaf/layout"
>
<head>
	<meta charset="UTF-8" />
	<title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">Spring Securityテスト</title>
	<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" media="all" />
	<link rel="stylesheet" type="text/css" href="/css/bootstrap-theme.min.css" th:href="@{/css/bootstrap-theme.min.css}" media="all" />

	<script type="text/javascript" src="/js/jquery-1.12.4.min.js" th:src="@{/js/jquery-1.12.4.min.js}"></script>
	<script type="text/javascript" src="/js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
</head>
<body>
	<div class="contents" layout:fragment="contents"></div>
</body>
</html>
index/index.html
<!DOCTYPE html>
<html
	xmlns        = "http://www.w3.org/1999/xhtml"
	xmlns:th     = "http://www.thymeleaf.org"
	xmlns:layout = "http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorator="layout"
	>
<head>
	<title>ログイン</title>
</head>
<body>
	<div layout:fragment="contents">
		<form class="form-horizontal" method="POST" action="/index/login/" th:action="@{/index/login}" th:object="${indexForm}">
			<div th:text="${errorMessage}?: ''" class="col-sm-offset-2 text-danger"></div>
			<div class="form-group">
				<p th:if="${#fields.hasErrors('*{userId}')}" th:errors="*{userId}" class="col-sm-offset-2 text-danger"></p>
				<label for="user-id" class="col-sm-2 control-label">ユーザーID</label>
				<div class="col-sm-5">
					<input type="text" class="form-control" id="user-id" th:field="*{userId}" placeholder="ユーザーID" />
				</div>
			</div>
			<div class="form-group">
				<p th:if="${#fields.hasErrors('*{password}')}" th:errors="*{password}" class="col-sm-offset-2 text-danger"></p>
				<label for="password" class="col-sm-2 control-label">パスワード</label>
				<div class="col-sm-5">
					<input type="password" class="form-control" id="password" th:field="*{password}" placeholder="パスワード" />
				</div>
			</div>
			<div class="form-group">
				<input type="submit" class="btn btn-primary col-sm-2 col-sm-offset-2" name="login" value="ログイン" />
				<input type="reset" class="btn btn-default col-sm-2 col-sm-offset-1" name="clear" value="クリア" />
			</div>
		</form>
	</div>
</body>
</html>
menu/index.html
<!DOCTYPE html>
<html
	xmlns        = "http://www.w3.org/1999/xhtml"
	xmlns:th     = "http://www.thymeleaf.org"
	xmlns:layout = "http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorator="layout"
	>
<head>
	<title>メニュー</title>
</head>
<body>
	<div layout:fragment="contents">
		<h2>メニュー</h2>
	</div>
</body>
</html>
application.yaml
#spring
spring:
  profiles:
    active: dev
  datasource:
    url: jdbc:h2:./db

#server
server:
  contextPath: /security-sample

#doma
doma:
  dialect: h2
application-dev.yaml
#spring
spring:
  h2:
    console:
      enabled: true
  thymeleaf:
    cache: false
application-production.yaml
#spring
spring:
  h2:
    console:
      enabled: false
  thymeleaf:
    cache: true
selectByUserId.sql
select
	user_id
	,password
from
	user
where
	user_id = /*userId*/''
schema.sql
--drop table if exists user;
create table if not exists user (
	user_id		varchar(30)	not null	primary key
	,password	varchar(30)	not null
);
data.sql
insert into user (user_id,password) values ('test','pass');

フォルダ構成は以下の通りです。
パッケージエクスプローラー.PNG

#動作確認
http://localhost:8080/security-sample/にアクセスし、
ユーザーIDにtest、パスワードにpassを入力するとメニュー画面に遷移します。
ログイン画面
ログイン.PNG
メニュー画面
メニュー.PNG

#認証後の処理について
認証成功時、失敗時の処理についてはSecurityConfig.javaの以下の部分に記載します。

SecurityConfig.java
	/**
	 * HttpSecurityの設定
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {

		//認可の設定
		http.authorizeRequests()
			//認証無しでアクセスできるURLを設定
			.antMatchers("/", "/index/**").permitAll()
			//上記以外は認証が必要にする設定
			.anyRequest().authenticated();

		//ログイン設定
		http.formLogin()
			//認証処理のパスを設定
			.loginProcessingUrl("/index/login")
			//ログインフォームのパスを設定
			.loginPage("/")
			.loginPage("/index/**")
			//認証成功時にリダイレクトするURLを設定
			.defaultSuccessUrl("/menu/")
			//認証失敗時にforwardするURLを設定
			.failureForwardUrl("/")
			//認証成功時にforwardするURLを設定
			//.successForwardUrl("/")
			//認証成功時に呼ばれるハンドラクラスを設定
			//.successHandler(successHandler)
			//認証失敗時にリダイレクトするURLを設定
			//.failureUrl("/menu/")
			//認証失敗時に呼ばれるハンドラクラスを設定
			//.failureHandler(failureHandler)
			//ユーザー名、パスワードのパラメータ名を設定
			.usernameParameter("userId").passwordParameter("password");

	}

コメントアウトをしていますが、認証成功時、失敗時それぞれで
リダイレクト、フォワード、ハンドラクラスへの処理委譲が行えます。

今回作成したプロジェクトはここにあります

23
26
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
23
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?