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

なんだこれ、すげえ