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

なんだこれ、すげえ

vpsでjarファイルを実行する

CREATE TABLE users (
id SERIAL NOT NULL,
name varchar(255),
department varchar(255),
PRIMARY KEY(id)
);

# java -jar practice-0.0.1-SNAPSHOT.jar

. ____ _ __ _ _
/\\ / ___’_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | ‘_ | ‘_| | ‘_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
‘ |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.3)

${id}:8765/test1/index
-> 動かない 何故だ。postgresも入れたし、localだと動くんだけどな。。
# lsof -i:8765

ずっとやってんだけど解決する見込みがない。。
まあ、keep going
次はauth

centos8でpostgresをインストールする

# systemctl restart firewalld
# sudo firewall-cmd –permanent –add-port=8756/tcp
success
# systemctl restart firewalld
# ls
practice-0.0.1-SNAPSHOT.jar
# java -jar practice-0.0.1-SNAPSHOT.jar

-> postgresが入ってないのでエラーになる

# yum module list postgresql
Failed to set locale, defaulting to C.UTF-8
Last metadata expiration check: 0:27:06 ago on Sat Feb 20 12:56:42 2021.
CentOS Linux 8 – AppStream
Name Stream Profiles Summary
postgresql 9.6 client, server [d] PostgreSQL server and client module
postgresql 10 [d] client, server [d] PostgreSQL server and client module
postgresql 12 client, server [d] PostgreSQL server and client module
# yum install -y @postgresql:12/server
# /usr/bin/postgresql-setup –initdb
# systemctl start postgresql
# systemctl enable postgresql
# psql –version
psql (PostgreSQL) 12.5

なんや
vi /var/lib/pgsql/data/pg_hba.conf
psql -h localhost -U postgres
alter role root with superuser login password ”;
ALTER USER root WITH PASSWORD ‘password’;

# psql -U root test
Password for user root:
psql (12.5)
Type “help” for help.

test=#

なんか色々触ってたらできたな

[CentOS8] jdk-11をインストールする

# sudo yum update
# cat /etc/redhat-release
CentOS Linux release 8.3.2011
# java -version
openjdk version “1.8.0_275”
# yum remove java-1.8.0-openjdk
# yum install -y java-11-openjdk
# java -version
openjdk version “1.8.0_275”

あれあれ???
-> デフォルトでOpenJDK8がインストールされている為、alternativesコマンドで11に切り替える必要がある。

# alternatives –config java

There are 2 programs which provide ‘java’.

Selection Command
———————————————–
*+ 1 java-1.8.0-openjdk.x86_64 (/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.275.b01-1.el8_3.x86_64/jre/bin/java)
2 java-11-openjdk.x86_64 (/usr/lib/jvm/java-11-openjdk-11.0.9.11-3.el8_3.x86_64/bin/java)

Enter to keep the current selection[+], or type selection number: 2
# java -version
openjdk version “11.0.9.1” 2020-11-04 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.9.1+1-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.9.1+1-LTS, mixed mode, sharing)

なるほど

[SpringBoot2.4.2] jdbcTemplateの削除を実装する

index.html

<a th:href="'/test1/delete/' + ${list.id}"><button type="button" class="btn btn-danger">削除</button></a>

delete_complete.html

<h1>社員情報削除 完了</h1>
<div class="col-md-8">
<p>社員の削除が完了しました。</p>
<table class="table">
	<tr><td>名前</td><td th:text="${name}"></td></tr>
	<tr><td>所属</td><td th:text="${department}"></td></tr>
</table>
<button type="button" class="btn btn-primary" onclick="location.href='/test1/index'">一覧に戻る</button>
</div>

UserRepository.java

	public Users delete(Long id) throws DataAccessException {
        String sql1 = ""
            + "SELECT * FROM users WHERE id = ?";
        Map<String, Object> users = jdbcTemplate.queryForMap(sql1, id);
        Users user = new Users();
        user.setName((String)users.get("name"));
		user.setDepartment((String)users.get("department"));
		
		String sql2 = "DELETE FROM users WHERE id = ?";
	    jdbcTemplate.update(sql2, id);
        return user;
    }

MainController.java

	@GetMapping("delete/{userId}")
	public String deleteUser(@PathVariable("userId") long userId, Model model) {
		Users user = usersRepository.delete(userId);
        model.addAttribute("name", user.getName());
        model.addAttribute("department", user.getDepartment());
        return "test1/delete_complete";
	}

一度理解するとあとは早いな
とりあえずCRUD完
authに行きたいが、まずこれでVPSにデプロイしてみたい。

[SpringBoot2.4.2] 編集画面から編集完了画面を作る

画面遷移としては、編集->編集確認->編集完了

edit_confirm.html

<form class=""  method="get" action="/test1/editcomplete">
<input type="hidden" name="id" th:value="${id}">
<input type="hidden" name="name" th:value="${name}">
<input type="hidden" name="department" th:value="${department}">
<table class="table">
	<tr><td>名前</td><td th:text="${name}"></td></tr>
	<tr><td>所属</td><td th:text="${department}"></td></tr>
</table>
<button type="button" class="btn btn-primary" onclick="location.href='/test1/index'">戻る</button>
<button type="submit" class="btn btn-primary">編集完了</button>
</form>

MainController.java

@GetMapping("editconfirm")
	public String editconfirm(
			@RequestParam(name = "id") Integer id,
			@RequestParam(name = "name") String name,
			@RequestParam(name = "department") String department,
			Model model) {
			model.addAttribute("id", id);
			model.addAttribute("name", name);
			model.addAttribute("department", department);
			return "test1/edit_confirm";
	}

ここまでは何も考えずにいける
updateする為にnameとdepartment以外にidも加える

UsersRepository.java
L エンティティの値をupdate

public Users update(Users users) throws DataAccessException {
        // SQL文を作成
        String sql = ""
            + "UPDATE users SET name = ?, department = ?"
            + " WHERE"  + " id = ?";
        jdbcTemplate.update(sql, users.getName(),users.getDepartment(),users.getId());
        return users;
    }

MainController.java

@GetMapping("editcomplete")
	public String editcomplete(
			@RequestParam(name = "id") Integer id,
			@RequestParam(name = "name") String name,
			@RequestParam(name = "department") String department,
			Model model) {
		    Users users = new Users();
		    users.setId(id);
		    users.setName(name);
		    users.setDepartment(department);
		    usersRepository.update(users);
		    
			model.addAttribute("name", name);
			model.addAttribute("department", department);
			return "test1/edit_complete";
	}

updateされました。

よしゃああああああああああああああああああああああああ
SpringBootもCRUまできた。残りはDやな。

[SpringBoot2.4.2] URLのパスを取得してjdbcTemplateでedit画面を作成する

まずtemplates に edit.html を作ります。

<div class="form-group">
	    <label class="control-label col-md-2">名前</label>
	    <div class="col-md-4">
	        <input type="text" class="form-control" name="name" th:value="${name}">
	    </div>
	</div>
	<div class="form-group">
        <label class="control-label col-md-2">所属部署</label>
        <div class="col-md-4">
            <input type="text" class="form-control" name="department" th:value="${department}">
        </div>
    </div>

続いて、indexからeditへのリンク。これは、/edit/${userId}とします。

<td th:text="${list.id}"></td><td th:text="${list.name}">狩野 良平</td><td th:text="${list.department}">営業部</td><td><a th:href="'/edit/' + ${list.id}"><button type="button" class="btn btn-secondary">編集</button></a></td><td><a th:href="'/delete/' + ${list.id}"><button type="button" class="btn btn-danger" onclick="location.href='/delete_complete.html'">削除</button></a></td>

UsersRepository.java
L jdbcTemplate.queryForMapで取得する

public Users selectOne(Long id) throws DataAccessException {
        // SQL文を作成
        String sql = ""
            + "SELECT"
                + " *"
            + " FROM"
                + " users"
            + " WHERE"
                + " id = ?";
        Map<String, Object> users = jdbcTemplate.queryForMap(sql, id);

        // Userオブジェクトに格納する。
        Users user = new Users();
        user.setName((String)users.get("name"));
		user.setDepartment((String)users.get("department"));
        return user;
    }

MainController.java

	@GetMapping("edit/{userId}")
	public String editForm(@PathVariable("userId") long userId, Model model) {
		Users user = usersRepository.selectOne(userId);
        model.addAttribute("name", user.getName());
        model.addAttribute("department", user.getDepartment());
        return "test1/edit";
	}

まじかよ、これ作るの3時間半くらいかかったんだけど。。。

[Java11.0.2] listとは

listとは要素が順位づけられたコレクションのこと
Javaではlistを扱う用にリストインターフェースが定義される
配列の場合、要素数を指定するが、listは要素数を指定しない

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

public class Main {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<Integer>();
		list.add(1);
		list.add(2);
		for(Integer l : list){
			System.out.println(l);
		}
	}
}

$ java test.java
1
2

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

public class Main {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<Integer>();
		list.add(4);
		list.add(5);
		list.add(1);
		list.add(2);
		for(Integer l : list){
			System.out.println(l);
		}

		Collections.sort(list);

		for(Integer l : list){
			System.out.println(l);
		}
	}
}

collectionsでソートもできる
なるほど、ListとMapの違いはわかった。

[Java11.0.2] mapとは

mapとは「キー」と「値」をペアにして複数データを格納できるもの
mapインスタンスを使うには、HashMapクラスを使う

Map<key型, value型> object = new HashMap<>();
import java.util.HashMap;
import java.util.Map;

public class Main {
	public static void main(String[] args) throws Exception {
		Map<Integer, String> map = new HashMap<>();

		map.put(1, "tanaka");
		map.put(3, "suzuki");
		map.put(5, "takahashi");

		System.out.println(map.get(1));
		System.out.println(map.get(3));
		System.out.println(map.get(5));
	}
}

$ java -version
openjdk version “11.0.2” 2019-01-15 LTS
$ java test.java
tanaka
suzuki
takahashi

keySet()でkeyの値を取り出せる

		for(Integer key: map.keySet()){
			System.out.println(key);
		}

なるほどー