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.
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());
}
}
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.
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 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 ...
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.
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.
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.