[SpringBoot2.4.3] DB認証のログイン処理(※2回目)

### Entity
com.example.demo.account

package com.example.demo.account;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

import javax.persistence.*;

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

@Entity
@Table(name="accounts")
public class Account implements UserDetails{
	
	private static final long serialVersionUID = 1L;
	
	public enum Authority {ROLE_USER, ROLE_MANAGER, ROLE_ADMIN}
	
	@Id
	@Column(nullable = false, unique = true)
	private String username;
	
	@Column(nullable = false)
	private String password;
	
	@Column(nullable = false, unique = true)
	private String mailAddress;
	
	@Column(nullable = false)
	private boolean mailAddressVerified;
	
	@Column(nullable = false)
	private boolean enabled;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date createdAt;
	
	@ElementCollection(fetch = FetchType.EAGER)
	@Enumerated(EnumType.STRING)
	@Column(nullable = false)
	private Set<Authority> authorities;
	
	protected Account() {}
	
	public Account(String username, String password, String mailAddress) {
		this.username = username;
		this.password = password;
		this.mailAddress = mailAddress;
		this.mailAddressVerified = false;
		this.enabled = true;
		this.authorities = EnumSet.of(Authority.ROLE_USER);
	}
	
	@PrePersist
	public void prePersist() {
		this.createdAt = new Date();
	}
	
	public boolean isAdmin() {
		return this.authorities.contains(Authority.ROLE_ADMIN);
	}
	
	public void setAdmin(boolean isAdmin) {
		if(isAdmin) {
			this.authorities.add(Authority.ROLE_MANAGER);
			this.authorities.add(Authority.ROLE_ADMIN);
		} else {
			this.authorities.remove(Authority.ROLE_ADMIN);
		}
	}
	
	public boolean isManager() {
		return this.authorities.contains(Authority.ROLE_MANAGER);
	}
	
	public void setManager(boolean isManager) {
		if(isManager) {
			this.authorities.add(Authority.ROLE_MANAGER);
		} else {
			this.authorities.remove(Authority.ROLE_MANAGER);
			this.authorities.remove(Authority.ROLE_ADMIN);
		}
	}

	@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (Authority authority : this.authorities) {
            authorities.add(new SimpleGrantedAuthority(authority.toString()));
        }
        return authorities;
    }
    
    @Override
    public boolean isAccountNonExpired() {
    	return true;
    }
    
    @Override
    public boolean isAccountNonLocked() {
    	return true;
    }
    
    @Override
    public boolean isCredentialsNonExpired() {
    	return true;
    }
    
    @Override
    public String getUsername() {
    	return username;
    }
    
    public void setUsername(String username) {
    	this.username = username;
    }
    
    @Override
    public String getPassword() {
    	return password;
    }
    
    public void setPassword(String password) {
    	this.password = password;
    }
    
    @Override
    public boolean isEnabled() {
    	return this.enabled;
    }
    
    public void setEnabled(boolean enabled) {
    	this.enabled = enabled;
    }
    
    public String getMailAddress() {
    	return mailAddress;
    }
    public void setMailAddress(String mailAddress) {
    	this.mailAddress = mailAddress;
    }
    public boolean isMailAddressVerified() {
    	return mailAddressVerified;
    }
    public void setMailAddressVerified(boolean mailAddressVerified) {
    	this.mailAddressVerified = mailAddressVerified;
    }
    public Date getCreatedAt() {
    	return createdAt;
    }	
}

Repository

package com.example.demo.account;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface AccountRepository extends CrudRepository<Account, Long>{
	public Account findByUsername(String username);
}

Service

package com.example.demo.account;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountService implements UserDetailsService {
	
	@Autowired
	private AccountRepository repository;
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	@Override
	public Account loadUserByUsername(String username) throws UsernameNotFoundException {
		if(username == null || "".equals(username)) {
			throw new UsernameNotFoundException("Username is empty");
		}
		
		Account user = repository.findByUsername(username);
		if(user == null) {
			throw new UsernameNotFoundException("User not found: " + username);
		}
		return user;
	}
	
	@Transactional
	public void registerAdmin(String username, String password, String mailAddress) {
		Account user = new Account(username, passwordEncoder.encode(password), mailAddress);
		user.setAdmin(true);
		repository.save(user);
	}
	
	@Transactional
	public void registerManager(String username, String password, String mailAddress) {
		Account user = new Account(username, passwordEncoder.encode(password), mailAddress);
		user.setManager(true);
		repository.save(user);
	}
	
	@Transactional
	public void registerUser(String username, String password, String mailAddress) {
		Account user = new Account(username, passwordEncoder.encode(password), mailAddress);
		repository.save(user);
	}
}

AuthoController.java

package com.example.demo.account;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class AuthController {
	
	@RequestMapping("/")
	public String index() {
		return "redirect:/top";
	}
	
	@GetMapping("/login")
	public String login() {
		return "login";
	}
	
	@PostMapping("/login")
	public String loginPost() {
		return "redirect:/login-error";
	}
	
	@GetMapping("/login-error")
	public String loginError(Model model) {
		model.addAttribute("loginError", true);
		return "login";
	}
	
	@RequestMapping("/top")
	public String top() {
		return "/top";
	}
}

WebSecurityConfig.java

package com.example.demo;

import com.example.demo.account.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.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;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private AccountService userService;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
				.authorizeRequests()
				.antMatchers("/login", "/login-error").permitAll()
				.antMatchers("/**").hasRole("USER")
				.and()
				.formLogin()
				.loginPage("/login").failureUrl("/login-error");
		
	}
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth
				.userDetailsService(userService)
				.passwordEncoder(passwordEncoder());
		userService.registerAdmin("admin", "secret", "admin@localhost");
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="utf-8">
	<title>Login page</title>
    <style>
.alert-danger{color:red;}
</style>
</head>
<body>
	<h2>ログイン画面</h2>
	<form th:action="@{/login}" method="post">
		<div th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null" class="alert-danger">
		<span th:text="ユーザ名またはパスワードに誤りがあります"></span>
		</div>
		<div style="width:160px;"><label for="username">ユーザ名:</label></div>
		<input type="text" name="username" autofocus="autofocus">
		<br>
		<div style="width:160px;"><label for="password">パスワード:</label></div>
		<input type="password" name="password" />
		<br>
		<p><input type="submit" value="ログイン"></p>
</body>
</html>

top.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
	<meta charset="UTF-8" />
	<title>メニュー画面</title>
</head>
<body>
	<h2>ログイン画面</h2>
	<div th:fragment="logout" sec:authorize="isAuthenticated()">
		<p>こんにちは:
			<span sec:authentication="name"></span>さん</p>
		<p>mail:
			<span sec:authentication="principal.mailAddress"></span></p>
		<p>権限:
			<span sec:authentication="principal.authorities"></span></p>
		<form action="#" th:action="@{/logout}" method="post">
			<input type="submit" value="ログアウト">
		</form>
	</div>
</body>
</html>

application.properties

spring.jpa.database=POSTGRESQL
spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=root
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

hibernate.properties

hibernate.jdbc.lob.non_contextual_creation = true

test=> select * from accounts;
username | created_at | enabled | mail_address | mail_address_verified | password
———-+————+———+—————–+———————–+————————————————————–
admin | | t | admin@localhost | f | $2a$10$19UBN8MoIHBihXXDs2uyreHU4DlAow7jNfUwtChyAw6pp5THYM8N.
(1 row)

あああああああああああ
AccountService.javaで登録してんのか。

@Transactional
public void registerAdmin(String username, String password, String mailAddress) {
Account user = new Account(username, passwordEncoder.encode(password), mailAddress);
user.setAdmin(true);
repository.save(user);
}

なるほど、
spring.jpa.hibernate.ddl-auto=update で、自動でテーブルが作成されんのか。。

insert into accounts (username, enabled, mail_address, mail_address_verified, password) values
(‘user1′,TRUE,’user1@gmail.com’, FALSE,’$2a$10$BuoyNf/vQZB5cS.eCEtW7uHtFTMq0SwLKXblcOeGCIJCpqeCnQH2S’),
(‘user2′,TRUE,’user2@gmail.com’, FALSE,’$2a$10$BuoyNf/vQZB5cS.eCEtW7uHtFTMq0SwLKXblcOeGCIJCpqeCnQH2S’);

insert into account_authorities (account_username, authorities) values
(‘user1′,’ROLE_USER’);


やべえええええええええええええ
Spring Securityできたやんか🔥🔥🔥🔥
他にやりたい事もあるけど、Javaでなんか作るかー。

[SpringBoot2.4.3] DB認証によるログイン処理を実装したい

WebSecurityConfig.java

package com.example.demo;

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.password.NoOpPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return NoOpPasswordEncoder.getInstance();
	}
	
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/css/**", "/resources/**");
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/", "/home").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.loginProcessingUrl("/login")
				.usernameParameter("username")
				.passwordParameter("password")
				.successForwardUrl("/home")
				.failureUrl("/login?error")
				.permitAll()
				.and()
			.logout()
				.logoutUrl("/logout")
				.permitAll()
				.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
	}
}

DemoController.java
L UserModelは後から作ります

package com.example.demo;

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

@Controller
public class DemoController {
	@RequestMapping(value = {"/", "/home"})
	public String home() {
		return "home";
	}
	
	@RequestMapping(value = "/hello")
	public String hello(Authentication authentication, Model model) {
		UserModel userModel = (UserModel)authentication.getPrincipal();
		model.addAttribute("name", userModel.getUsername());
		
		return "hello";
	}
}

pom.xml
L jdbcとpostgres追加

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jdbc</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>

application.properties
L jdbc, postgres追加

spring.jpa.database=POSTGRESQL
spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=root
spring.datasource.password=

$ psql -U root test
test=> \d

create table t_user (
	id serial primary key,
	name varchar(255),
	password varchar(255),
	enabled boolean DEFAULT true
);

insert into t_user (name, password) values
('user1','password'),
('user2','password');

select * from t_user;

UserModel.java

package com.example.demo;

import java.util.Collection;

import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.GrantedAuthority;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class UserModel implements UserDetails{

	private String id;
	private String name;
	private String password;
	private boolean enabled;
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities(){
		return null;
	}
	
	@Override
	public String getPassword() {
		return this.password;
	}
	
	@Override
	public String getUsername() {
		return this.name;
	}
	
	@Override
	public boolean isAccountNonExpired() {
		return true;
	}
	
	@Override
	public boolean isAccountNonLocked() {
		return true;
	}
	
	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}
	
	@Override
	public boolean isEnabled() {
		return this.enabled;
	}
}

UserRepository.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.UserRepository">
	<select id="selectByUser" parameterType="com.example.demo.UserRepository" resultType="com.example.demo.UserModel">
		SELECT id, name, password, enabled FROM t_user where id = #{id};
	</select>
</mapper>

UserRepository.java

package com.example.demo;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserRepository {
	public UserModel selectByUser(String username);
}

UserDetailsServiceImpl.java

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

@Component
public class UserDetailsServiceImpl implements UserDetailsService {
	
	@Autowired
	private UserRepository userRepository;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		if(StringUtils.isEmpty(username)) throw new UsernameNotFoundException("");
		
		UserModel userModel = userRepository.selectByUser(username);
		
		if(userModel == null) throw new UsernameNotFoundException("");
		if(!userModel.isAccountNonExpired() || !userModel.isAccountNonLocked() || 
				!userModel.isCredentialsNonExpired() || !userModel.isEnabled())
			throw new UsernameNotFoundException("");
		return userModel;
	}

}

Description:

Field userRepository in com.example.demo.UserDetailsServiceImpl required a bean of type ‘com.example.demo.UserRepository’ that could not be found.

なんでや。。。なんでや。。。。
もう一回やるか、次ラストで。

[SpringBoot2.4.3] ログイン後のリダイレクト先指定

WebSecurityConfig.java
L .defaultSuccessUrl(“/test1/index”)でログイン後のURLを指定する

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/","/home").permitAll()
				// 上記以外は認証が必要
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.permitAll()
				.defaultSuccessUrl("/test1/index")
				.and()
			.logout()
				.permitAll();
	}
	@Bean
	@Override
	public UserDetailsService userDetailsService() {
		UserDetails user =
				User.withDefaultPasswordEncoder()
					.username("user")
					.password("pass")
					.roles("USER")
					.build();
		return new InMemoryUserDetailsManager(user);
	}	
}

なるほど。
ログイン認証をpostgresでやりたいな。

[SpringBoot2.4.3] Jsonを返すシンプルなスクリプト

HelloController.java

package com.example.demo;

import java.util.Collections;
import java.util.Map;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

	@RequestMapping("/hello")
	public Map<String, String>hello(){
		return Collections.singletonMap("message", "Hello, World!");
	}
}

ここまでは特に何でもありません。

[SpringBoot2.4.3] ログイン機能を実装する2

psql -U root test

CREATE TABLE employee (
id SERIAL NOT NULL,
name varchar(255),
password varchar(255),
PRIMARY KEY(id)
);

EmployeeMapper.java

@Select({
	"select * from employee where name = #{name} limit 1"
})
Employee selectByName(String name);

SecurityConfig.java

package com.example.demo.security;

import org.springframework.context.annotation.Bean;
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;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/webjars/**", "/css/**");
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/login").permitAll()
				.anyRequest().authenticated()
			.and()
			.formLogin()
				.loginProcessingUrl("/login")
				.loginPage("/login")
				.failureUrl("/login?error")
				.defaultSuccessUrl("/menu", true)
				.usernameParameter("name")
				.passwordParameter("password")
			.and()
			.logout()
				.logoutSuccessUrl("/login");
	}
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

LoginUserDetails.java

package com.example.demo.security;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;

import com.example.demo.domain.Employee;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper=false)
public class LoginUserDetails extends User {
	private final Employee employee;
	
	public LoginUserDetails(Employee employee, String role) {
		super(employee.getName(), employee.getPassword(), AuthorityUtils.createAuthorityList(role));
		this.employee = employee;
	}
}

LoginUserDetailsService.java

package com.example.demo.security;

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.demo.Employee;
import com.example.demo.mybatis.mapper.EmployeeMapper;

@Service
public class LoginUserDetailsService implement UserDetailsService {
	@Autowired
	EmployeeExample employeeExample;
	
	@Autowired
	EmployeeMapper employeeMapper;
	
	@Override
	public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
		Employee employee = employeeMapper.selectByName(name);
		
		if (employee == null) {
			throw new UsernameNotFoundException("Wrong email or password");
		}
		
		String role = "ROLE_ADMIN";
		
		return new LoginUserDetails(employee, role);
	}
}

[SpringBoot2.4.3] ログイン機能を実装する

starter projectでプロジェクトを作ります。
dependencyにdevtool, jpa, postgres, spring security, thymeleaf, web, sessionを入れます。

com.exqmple.demo/WebSecurityConfig.java

package com.example.demo;

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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

//import com.example.demo.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Autowired
	private UserDetailsServiceImpl userDetailsService;
	
	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		return bCryptPasswordEncoder;
	}
	
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatcher(
				"/images/**",
				"/css/**",
				"/javascript/**"
				);
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.loginProcessingUrl("/sign_in")
				.usernameParameter("username")
				.passwordParameter("password")
				.successForwardUrl("/hello")
				.failureUrl("/login?error")
				.permitAll()
				.and()
			.logout()
				.logoutUrl("/logout")
				.logoutSuccessUrl("/login?logout")
				.permitAll();
	}
	
	@Autowired
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth
			.inMemoryAuthentication()
				.withUser("user").password("{noop}password");
	}
	
	
}

login.html
L Spring-SecurityのCSRF対策の為、th:action=”{}”と書く

	<body>
		<div th:if="${param.error}">
			Invalid username and password.
		</div>
		<div th:if="${param.logout}">
			You have been logged out.
		</div>
		<form th:action="@{/sign_in}" method="post">
		<div><label>User Name: <input type="text" name="username"></label></div>
		<div><label>Password: <input type="text" name="password"></label></div>
		<div><input type="submit" value="Login"></div>
		</form>
	</body>

UserDetailsServiceImpl.java

package com.example.demo;

import java.util.ArrayList;
import java.util.List;

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.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import com.example.demo.LoginUserDao;
import com.example.demo.LoginUser;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
	
	@Autowired
	private LoginUserDao userDao;
	
	@Override
	public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException{

		LoginUser user = userDao.findUser(userName);
		
		if(user == null) {
			throw new UsernameNotFoundException("User" + userName + "was not found in the database");
		}
		
		List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
		GrantedAuthority authority = new SimpleGrantedAuthority("USER");
		grantList.add(authority);
		
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		
		UserDetails userDetails = (UserDetails) new User(user.getUserName(), encoder.encode(user.getPassword()),grantList);
		
		return userDetails;
	}
}

### セッション
application.properties

spring.session.store-type=jdbc

pom.xml

		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-jdbc</artifactId>
		</dependency>
@RequestMapping("/hello")
	private String init(Model model) {
	Authentication auth = SecurityContextHolder.getContext().getAuthentication();
	
	String userName = auth.getName();
	model.addAttribute("userName", userName);
	return "hello";
}

うーん、Serviceの使い方などよくわからんな。

[SpringBoot2.4.2] Spring Securityによる認証を実装

src/main/resources/templates/ にhome.htmlを作ります。
home.html

<!Doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
	xmlns:sec="https://www.thymeleaf.org/thymeleaf-extas-springsecurity3">
	<head>
		<title>Spring Security Example</title>
	</head>
	<body>
		<h1>Welcome!</h1>
		<p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
	</body>
</html>

hello.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>Hello World!</title>
	</head>
	<body>
		<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
		<form th:action="@{/logout}" method="post">
			<input type="submit" value="Sign Out">
		</form>
	</body>
</html>

com.example.demo
MvcConfig.java
L 設定クラス

package com.example.demo;

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("/home").setViewName("home");
		registry.addViewController("/").setViewName("home");
		registry.addViewController("/hello").setViewName("hello");
		registry.addViewController("/login").setViewName("login");
	}
}

pom.xml
L spring securityを追加

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

WebSecurityConfig.java

package com.example.demo;

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

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/", "/home").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.permitAll()
				.and()
			.logout()
				.permitAll();
	}
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		String password = passwordEncoder().encode("password");
		
		auth.inMemoryAuthentication()
			.passwordEncoder(passwordEncoder())
			.withUser("user").password(password).roles("USER");
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

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</title>
	</head>
	<body>
		<div th:if="${param.error}">
			Invalid username and password.
		</div>
		<div th:if="${param.logout}">
			You have been logged out.
		</div>
		<form th:action="@{/login}" method="post">
		<div><label>User Name: <input type="text" name="username"></label></div>
		<div><label>Password: <input type="text" name="password"></label></div>
		<div><input type="submit" value="Sign In"></div>
		</form>
	</body>
</html>

なんだこれ、すげえ