[JAVA] Spring-Security new registration and login (JPA)

I was addicted to the Spring Security settings

As the title says. I tried to authenticate using Spring Security for the first time, but I fell in love with it. The reference URLs are here and here.

First, create a project. The environment is as follows. Java version: 1.8 FW:Springboot(gradle) DB: MySQL (access is JPA) Others: Web, lombok, Thymeleaf, etc. are included.

First, display the login screen

LoginController


package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

@RestController
@RequestMapping(value="/login")
public class LoginController {
	@GetMapping
	public ModelAndView login(ModelAndView mav) {
        return mav;
    }
}

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-springsecurity5">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Login</title>
</head>
<body>
	<div class="login-form">
		<form th:action="@{/login}" th:method="post">
			<input type="text" name="loginId" id="loginId"><br />
			<input type="password" name="password" id="password"><br />
			<button type="submit" class="login-btn">Login</button>
		</form>
	</div>

	<button onclick="location.href='./signup'" class="signup-link">sign up</button>
</body>
</html>

Spring Security configuration file-like guy

WebSecurityConfig


package com.example.demo.config;

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.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 com.example.demo.service.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    //Password encryption
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
    	return new BCryptPasswordEncoder();
    }

    //Folders and files that are not filtered
    @Override
    public void configure(WebSecurity web) throws Exception {
    	web.ignoring().antMatchers(
    			"/image/**",
    			"/css/**",
    			"/js/**"
    			);
    }

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

    	//Anyone can transition between the login screen and the new registration screen
    	http.authorizeRequests()
    	.antMatchers("/login", "/signup")
		.permitAll()
    	.anyRequest().authenticated();

    	//Login
    	http.formLogin()
    	.loginPage("/login") //Since the login page does not go through the controller, it is necessary to link it with ViewName.
    	.loginProcessingUrl("/login") //Submit URL of the form, authentication process is executed when a request is sent to this URL
    	.usernameParameter("loginId") //Explicit name attribute of request parameter
    	.passwordParameter("password")
    	.successForwardUrl("/top")
    	.failureUrl("/login?error")
    	.permitAll();

    	//Log out
    	http.logout()
    	.logoutUrl("/logout")
    	.logoutSuccessUrl("/login")
    	.invalidateHttpSession(true)
    	.deleteCookies("JSESSIONID", "SESSION", "remember-me")
    	.permitAll();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
}

Register user in DB

Create an appropriate account in the DB.

SignUpController


package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.Conversion;
import com.example.demo.form.SignUpForm;
import com.example.demo.service.SignUpService;

@RestController
@RequestMapping(value="/signup")
public class SignUpController {

	@GetMapping
	public ModelAndView signup(ModelAndView mav) {
        return mav;
    }

	@Autowired
	private SignUpService signupService;
	@Autowired
	private Conversion conversion;

	@PostMapping
	public ModelAndView signup(SignUpForm signupForm, ModelAndView mav) {
		signupService.registUser(conversion.signupCon(signupForm));
		mav.setViewName("login");
		return mav;
	}
}

SignUpForm


package com.example.demo.form;

import javax.persistence.Column;
import javax.persistence.Id;

import lombok.Data;

@Data
public class SignUpForm {

	@Id
	private Integer id;
	@Column(name = "login_id")
	private String loginId;
	private String password;
}

signup.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-springsecurity5">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>sign up</title>
</head>
<body>
	<div class="signup-form">
		<form th:action="@{/signup}" th:method="post" th:object="${SignUpForm}">
			<label class="loginId-label">Login ID</label>
			<input type="text" name="loginId" id="loginId"><br />
			<label class="password-label">password</label>
			<input type="password" name="password" id="password"><br />
			<button type="submit" class="signup-btn">Registration</button>
		</form>
	</div>
</body>
</html>

UserEntity


package com.example.demo.entity;

import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Data;

@Data
@Table(name="user")
@Entity
public class UserEntity{
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	@Column(name = "login_id")
	private String loginId;
	private String password;

	public UserEntity(Integer id, String loginId, String password) {
		this.id = id;
		this.loginId = loginId;
		this.password = password;
	}
}

Just encrypt the password received in Form and pass it to Entity. I tried to do it only with Entity, but I didn't understand it well, so please tell me who understands it.

Conversion


package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import com.example.demo.entity.UserEntity;
import com.example.demo.form.SignUpForm;

@Component
public class Conversion {
	@Autowired
	BCryptPasswordEncoder passwordEncoder;

	public UserEntity signupCon(SignUpForm form) {
		return new UserEntity(
				form.getId(),
				form.getLoginId(),
				passwordEncoder.encode(form.getPassword()));
	}
}

After that, let Service register the value of Entity with JPA.

Addictive point ①

It was my first time to use Thymeleaf or JPA in the first place, so I stumbled on various things, but for the time being, I managed to get it. Also, remove the signup page from Security.

Login function

Login authentication is finally done from here. Spring Security settings. It seems that loadUserByUsername of UserDetailsService is for performing the authentication process itself. But this argument is `` `String username```, so isn't the password determined here? I don't know how to do it.

UserDetailsServiceImpl


package com.example.demo.service;

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

import com.example.demo.entity.UserEntity;
import com.example.demo.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

	@Autowired
	private UserRepository userRepository;

	//loadUserByUsername is a method of UserDetails
	@Override
	public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {

		UserEntity loginUser = userRepository.findUser(loginId);

		if (loginUser == null) {
			throw new UsernameNotFoundException("User" + loginId + "Account does not exist");
		}
//Authority setting (dummy)
		List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
		GrantedAuthority authority = new SimpleGrantedAuthority("USER");
		grantList.add(authority);

//Since UserDetails is an interface, cast the user object created by the constructor of the User class.
		UserDetails userDetails = (UserDetails) new User(loginUser.getLoginId(), loginUser.getPassword(), grantList);

		return userDetails;
	}
}

This time I use LoginController. It seems that you can do it with MvcConfig, but is there any merit?

After that, you can log in by adding Query to the JPA Repository and searching for user with login_id! (Because there was no such thing as a String ID search in JPA. If anyone knows this, please let me know)

I thought ...

Addictive point ②

Even if I enter the ID and password and press the login button, the top screen does not appear. Is the mapping at the time of success (successful login) wrong? After checking various things with, I changed the .successForwardUrl ("/ top") part of WebSecurityConfig to .defaultSuccessUrl ("/ top") and it changed. I wasn't sure about the difference.

Remove the authority (ROLE) setting

I made it with reference to the link, but when I made the DB, I didn't use ROLE, so I don't need administrator privileges for the time being, right? I was addicted to it because I couldn't do it at all when I thought that I wouldn't have administrator privileges. After all, just comment out the dummy permission part of UserDetailsServiceImpl and return the Entity information.

UserDetailsServiceImpl


package com.example.demo.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.demo.entity.UserEntity;
import com.example.demo.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

	@Autowired
	private UserRepository userRepository;

	//loadUserByUsername is a method of UserDetails
	@Override
	public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {

		UserEntity loginUser = userRepository.findUser(loginId);

		if (loginUser == null) {
			throw new UsernameNotFoundException("User" + loginId + "Account does not exist");
		}
//Authority setting (dummy)
//		List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
//		GrantedAuthority authority = new SimpleGrantedAuthority("USER");
//		grantList.add(authority);

//Since UserDetails is an interface, cast the user object created by the constructor of the User class.
//		UserDetails userDetails = (UserDetails) new User(loginUser.getLoginId(), loginUser.getPassword(), grantList);

		return loginUser;
	}
}

I was still able to log in. Of course, there should be an administrator, so I wonder if the authority will actually be set. You can also apply an administrator authority filter for screen transitions.

Summary

Thymeleaf, JPA, Spring Security, and Spring itself weren't really understood in the first place, so it took a long time to get there. It may not be very common to start from here in the actual field, but I thought I'd just understand it.

Recommended Posts

Spring-Security new registration and login (JPA)
[Rails] Strong parameter sanitizer method required when customizing devise's "new registration", "login", and "information update" functions
Old and new speed showdown