[JAVA] Try using Spring Boot Security

environment

Spring Boot 1.5.9.RELEASE Java 8 Maven 4.0.0

Overview

Use Spring Security to verify and authenticate with the login information in the DB. DB uses H2DB and ORM uses Doma.

Introduction

Generate a project with Spring Inirializr. spring-initializr.png

Since we will use Doma for ORM this time, set the annotation processing. 注釈処理.png ファクトリー・パス.png

code

Only those that have been changed from the default are listed.

pom.xml(※Excerpt)


	<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;

/**
 *Entity in USER table
 * @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;
	}
	/**
	 *Return the password to be checked by UserDetailsService
	 *If you are using Lombok, if the field has "password"
	 *GetPassword with @Getter()Is not required because it will generate
	 */
	@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;

/**
 *DAO accessing the USER table
 * @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 that handles authentication
 * @author T.Harao
 *
 */
@Service
public class AuthService implements UserDetailsService {

	@Autowired
	private UserDao dao;

	/**
	 *User loading
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

		if(username == null || "".equals(username)) {
			throw new UsernameNotFoundException("No user ID entered");
		}

		UserEntity user = dao.selectByUserId(username);
		if(user == null) {
			throw new UsernameNotFoundException("The user ID is invalid.");
		}

		return user;
	}

}

IndexForm.java


package com.example.springbootsecuritysample.form;

import org.hibernate.validator.constraints.NotEmpty;

import lombok.Getter;
import lombok.Setter;

/**
 *Form used by IndexController
 * @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;

/**
 *Login
 * @author T.Harao
 *
 */
@Controller
@RequestMapping({"/", "/index"})
public class IndexController {

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

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

	/**
	 *At the time of authentication error
	 * @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", "Incorrect login information");
		}

		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;

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

	/**
	 *Initial display
	 * @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;

/**
 *Handler when authentication fails
 * @author T.Harao
 *
 */
@Component
public class FailureHandler implements AuthenticationFailureHandler {

	/**
	 *When authentication fails
	 */
	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {

		//「/Forward to
		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;
/**
 *Handler when authentication is successful
 * @author T.Harao
 *
 */
@Component
public class SuccessHandler implements AuthenticationSuccessHandler {

	/**
	 *When authentication is successful
	 */
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {

		//「/menu/Redirect to
		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;

/**
 *security settings
 * @author T.Harao
 *
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private AuthService service;

	@Autowired
	private FailureHandler failureHandler;

	@Autowired
	private SuccessHandler successHandler;

	/**
	 *Web Security settings
	 */
	@Override
    public void configure(WebSecurity web) throws Exception {

		//Static resources(images、css、javascript)And access to the H2DB console ignores security settings
		web.ignoring().antMatchers("/css/**", "/fonts/**", "/images/**", "/js/**", "/h2-console/**");

	}

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

		//Authorization settings
		http.authorizeRequests()
			//Set a URL that can be accessed without authentication
			.antMatchers("/", "/index/**").permitAll()
			//Settings other than the above required for authentication
			.anyRequest().authenticated();

		//Login settings
		http.formLogin()
			//Set the path for authentication processing
			.loginProcessingUrl("/index/login")
			//Set login form path
			.loginPage("/")
			.loginPage("/index/**")
			//Set the URL to redirect when authentication is successful
			.defaultSuccessUrl("/menu/")
			//Set the URL to forward when authentication fails
			.failureForwardUrl("/")
			//Set the URL to forward when authentication is successful
			//.successForwardUrl("/")
			//Set the handler class to be called when authentication is successful
			//.successHandler(successHandler)
			//Set the URL to redirect when authentication fails
			//.failureUrl("/menu/")
			//Set handler class to be called when authentication fails
			//.failureHandler(failureHandler)
			//Set user name and password parameter name
			.usernameParameter("userId").passwordParameter("password");

	}

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

		//Set "NoOpPasswordEncoder" because the password is registered in the DB in plain text.
		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 test</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>Login</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">User ID</label>
				<div class="col-sm-5">
					<input type="text" class="form-control" id="user-id" th:field="*{userId}" placeholder="User 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">password</label>
				<div class="col-sm-5">
					<input type="password" class="form-control" id="password" th:field="*{password}" placeholder="password" />
				</div>
			</div>
			<div class="form-group">
				<input type="submit" class="btn btn-primary col-sm-2 col-sm-offset-2" name="login" value="Login" />
				<input type="reset" class="btn btn-default col-sm-2 col-sm-offset-1" name="clear" value="clear" />
			</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>menu</title>
</head>
<body>
	<div layout:fragment="contents">
		<h2>menu</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');

The folder structure is as follows. パッケージエクスプローラー.PNG

Operation check

Go to [http: // localhost: 8080 / security-sample /](http: // localhost: 8080 / security-sample /) and go to Enter test as the user ID and pass as the password to move to the menu screen. Login screen ログイン.PNG Menu screen メニュー.PNG

Post-authentication processing

The processing when authentication succeeds or fails is described in the following part of SecurityConfig.java.

SecurityConfig.java


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

		//Authorization settings
		http.authorizeRequests()
			//Set a URL that can be accessed without authentication
			.antMatchers("/", "/index/**").permitAll()
			//Settings other than the above required for authentication
			.anyRequest().authenticated();

		//Login settings
		http.formLogin()
			//Set the path for authentication processing
			.loginProcessingUrl("/index/login")
			//Set login form path
			.loginPage("/")
			.loginPage("/index/**")
			//Set the URL to redirect when authentication is successful
			.defaultSuccessUrl("/menu/")
			//Set the URL to forward when authentication fails
			.failureForwardUrl("/")
			//Set the URL to forward when authentication is successful
			//.successForwardUrl("/")
			//Set the handler class to be called when authentication is successful
			//.successHandler(successHandler)
			//Set the URL to redirect when authentication fails
			//.failureUrl("/menu/")
			//Set handler class to be called when authentication fails
			//.failureHandler(failureHandler)
			//Set user name and password parameter name
			.usernameParameter("userId").passwordParameter("password");

	}

I have commented out, but when authentication succeeds and fails, You can redirect, forward, and delegate processing to the handler class.

The project created this time is in here

Recommended Posts

Try using Spring Boot Security
Spring Boot Tutorial Using Spring Security Authentication
Try using Spring Boot with VS Code
Try using Spring JDBC
Part 1: Try using OAuth 2.0 Login supported by Spring Security 5 with Spring Boot
Try LDAP authentication with Spring Security (Spring Boot) + OpenLDAP
Try Spring Boot on Mac
Try using DI container with Laravel and Spring Boot
Try to work with Keycloak using Spring Security SAML (Spring 5)
Try using Maven
Try using powermock-mockito2-2.0.2
Try using GraalVM
Try using jmockit 1.48
Try using sql-migrate
Try Spring Security AES256 string encryption / decryption
Challenge Spring Boot
Spring Boot @WebMvcTest test enables Spring Security default security
Try Spring Boot 1 (Environment construction ~ Tomcat startup)
Spring Boot Form
Asynchronous processing with Spring Boot using @Async
Spring Boot Memorandum
gae + spring boot
Achieve BASIC authentication with Spring Boot + Spring Security
Hash passwords with Spring Boot + Spring Security (with salt, with stretching)
[FCM] Implementation of message transmission using FCM + Spring boot
Create a Spring Boot application using IntelliJ IDEA
Try to implement login function with Spring Boot
[Java / Spring Boot] Spring security ⑤ --Implementation of logout processing
Try to automate migration with Spring Boot Flyway
Apply Twitter Bootstrap 4 to Spring Boot 2 using Webjars
[Introduction to Spring Boot] Authentication function with Spring Security
Create Spring Cloud Config Server with security with Spring Boot 2.0
Try using Axon Framework
SPRING BOOT learning record 01
Spring Boot + Heroku Postgres
Try using JobScheduler's REST-API
About Spring Security authentication
Try using java.lang.Math methods
Try using PowerMock's WhiteBox
Spring boot memo writing (1)
First Spring Boot (DI)
SPRING BOOT learning record 02
Spring Boot2 cheat sheet
Spring Boot exception handling
Spring Boot Servlet mapping
Spring boot development-development environment-
Spring Boot learning procedure
Learning Spring Boot [Beginning]
Spring boot memo writing (2)
Spring Boot 2.2 Document Summary
[Spring Boot] DataSourceProperties $ DataSourceBeanCreationException
Try using Talend Part 1
Spring Boot 2.3 Application Availability
Spring boot tutorials Topics
Try using F # list
Try using each_with_index method
Download with Spring Boot
Create a portfolio app using Java and Spring Boot
Testing JPA entities and repositories using Spring Boot @DataJpaTest
[Note] Configuration file when using Logback with Spring Boot
Spring Security usage memo: Cooperation with Spring MVC and Boot