Introducing how to implement user authentication using Spring security. First, after introducing how to hash the password and register as a new member, We will show you how to authenticate users using Spring security.
This time, we have confirmed the operation in the following environment.
DB
First, create a table to store your username and password. This time, we will create a USER table with the user name (NAME), password (PASSWORD) and ID as columns. Execute the following SQL statement to create the USER table.
CREATE TABLE USER(ID INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(64) NOT NULL, PASSWORD VARCHAR(64) NOT NULL);
The important thing here is to set the PASSWORD to 60 or more characters. The password is hashed and registered in the DB, but if the password is hashed with BCryptPasswordEncoder used in this hashing, the number of characters will be 60 characters, so at least 60 characters are required for the password column this time.
First, inject Spring security dependencies to use Spring security. Add the following dependency to Pom.xml.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Injecting a Spring security dependency will cause the Spring security default login screen to appear when you launch the application.
At this stage, the following default login screen is displayed.
Now, let's display the login screen you created in the next section.
Here, create a login screen, create a Controller, and create WebSecurityConfig.java.
First, create a login screen.
Login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form th:action="/" method="POST">
<table align="center">
<tr>
<td>
User name:
</td>
<td>
<input type="text" id="user" name="user"/>
</td>
</tr>
<tr>
<td>
password:
</td>
<td>
<input type="password" id="password" name="password"/>
</td>
</tr>
<tr>
<td>
</td>
<td align="right">
<button type="submit">Login</button>
</td>
</tr>
<tr>
<td>
</td>
<td align="right">
<a href="/RegistrationForm">sign up</a>
</td>
</tr>
</table>
</form>
</body>
</html>
Next, create a Controller.
LoginController.java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
@RequestMapping("/login")
public String showLoginForm(Model model) {
//Transition to the login screen.
return "Login";
}
}
Next, configure with java config. Create a WebSecurityConfig class for configuration. This class inherits from WebSecurityConfigurerAdapter.
WebSecurityConfig.java
import org.springframework.context.annotation.Configuration;
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;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//Specify the login page.
//All access to the login page is allowed.
http.formLogin()
.loginPage("/login")
.permitAll();
http.authorizeRequests()
.anyRequest().authenticated();
}
}
In loginPage (), specify the path specified in RequestMapping of showLoginForm () method of LoginController class. By calling permitAll (), you can access the login page even if you are not authenticated. Call anyRequest (). authenticated () to authenticate all requests.
When you have completed this step, the login screen you created will be displayed as shown below.
Next, we will implement the membership registration function.
This chapter describes how to hash the password and register it in the DB. However, how to create a membership registration form and how to receive the input value of the form are the same as when the password is not hashed, so I will omit it. This section focuses on how to hash passwords. If you want to see all the source code, please see github for the source code.
This time, BCryptPasswordEncoder is used as the hashing method. Here, the PassswordEncoder class is included in two packages, org.springframework.security.crypto.password and org.springframework.security.authentiction.encoding, but the latter is deprecated, so the former I will use the one. First, add BCryptPasswordEncoder to the bean with java config.
WebSecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 {
//Specify the login page.
//All access to the login page is allowed.
http.formLogin()
.loginPage("/login")
.permitAll();
http.authorizeRequests()
.anyRequest().authenticated();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
This time, by setting the password and user name in the argument of the insertMemberInfo () method of the created mapper interface and calling it, insert the member information into the DB. Hash the password entered in the form before calling the insertMemberInfo () method.
RegisterMemberService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.webapp.certificationtest.mapper.RegisterMemberMapper;
@Service
@Transactional
public class RegisterMemberService {
@Autowired
RegisterMemberMapper registerMemberMapper;
@Autowired
PasswordEncoder passwordEncoder;
/**
*Register member information in the DB.
*/
public void registerMember(MemberRegistrationEntity entity) {
//Hash the password and insertMemberInfo()Set to the object passed to.
entity.setPassword(passwordEncoder.encode(entity.getPassword()));
registMemberMapper.insertMemberInfo(entity);
}
}
Here, we first declare PasswordEncoder with @ </ span> Autowired. Then, after calling encode () of PasswordEncoder to hash the password, insert the password into the DB.
First, add to WebSecurityConfig.java.
WebSecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 {
//Specify the login page.
//All access to the login page is allowed.
http.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticate")
.usernameParameter("userName")
.passwordParameter("password")
.defaultSuccessUrl("/")
.permitAll();
//Added when the member registration function is implemented
http.authorizeRequests()
.antMatchers("/RegistrationForm").permitAll()
.antMatchers("/Register").permitAll()
.antMatchers("/Result").permitAll()
.anyRequest().authenticated();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Specify the URL to move to the authentication process with loginProcessingUrl (). Make sure that this URL matches the value of the action attribute of the form tag of the login form. In usernameParameter (), specify the value of the name attribute of the input tag that puts the user name in html. In passwordParameter (), specify the value of the name attribute of the input tag that puts the password in html. Specify the URL of the page to go to when login is successful with defaultSuccessUrl ().
Next, change the action and input names of the Login.html form.
Login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form th:action="/authenticate" method="POST">
<table align="center">
<tr>
<td>
User name:
</td>
<td>
<input type="text" id="user" name="userName"/>
</td>
</tr>
<tr>
<td>
password:
</td>
<td>
<input type="password" id="password" name="password"/>
</td>
</tr>
<tr>
<td>
</td>
<td align="right">
<button type="submit">Login</button>
</td>
</tr>
<tr>
<td>
</td>
<td align="right">
<a href="/RegistrationForm">sign up</a>
</td>
</tr>
</table>
</form>
</body>
</html>
Next, create a class to put the username and password obtained from the USER table.
Account.java
/**
*A class that stores member information required for login.
*/
public class Account {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Next, write a mapper that gets the username and password from the USER table.
LoginMapper.java
import com.webapp.certificationtest.Account;
public interface LoginMapper {
public Account findAccount(String name);
}
LoginMapper.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.webapp.certificationtest.mapper.LoginMapper">
<select id="findAccount" resultType="com.webapp.certificationtest.Account"
parameterType="String">
SELECT
NAME,
PASSWORD
FROM
USER
WHERE
NAME = #{userName}
</select>
</mapper>
Next, create a class to store user information. It inherits from the User class.
DbUserDetails.java
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
public class DbUserDetails extends User {
//User information.
private final Account account;
public DbUserDetails(Account account,
Collection<GrantedAuthority> authorities) {
super(account.getName(), account.getPassword(),
true, true, true, true, authorities);
this.account = account;
}
public Account getAccount() {
return account;
}
}
The second argument of the constructor of the DbUserDetails class gives a list of permissions to grant to the user.
Next, create the Service class. This Service class gets the value from the User table and generates UserDetails. Authentication is performed by matching the information entered by the user in the login form with the User Details generated here.
DbUserDetailsService.java
import java.util.Collection;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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 org.springframework.transaction.annotation.Transactional;
import com.webapp.certificationtest.mapper.LoginMapper;
@Service
public class DbUserDetailsService implements UserDetailsService {
@Autowired
LoginMapper loginMapper;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String mailAddress)
throws UsernameNotFoundException {
//Get user information from DB.
Account account = Optional.ofNullable(loginMapper.findOne(mailAddress))
.orElseThrow(() -> new UsernameNotFoundException("User not found."));
return new DbUserDetails(account, getAuthorities(account));
}
/**
*Set the range of authority given to this user when authentication is successful.
* @param account User information obtained from DB.
* @return A list of privilege ranges.
*/
private Collection<GrantedAuthority> getAuthorities(Account account) {
//Set the range of authority given to the user when authentication is successful.
return AuthorityUtils.createAuthorityList("ROLE_USER");
}
}
If nothing can be retrieved from the User table, throw a UsernameNotFoundExceptionw. In the argument of UsernameNotFoundException, specify the message when the user name does not exist in the User table. In getAuthorities (), set the authority given to the user obtained from the User table. Change this method as needed. If you want to give multiple permissions, add them to the argument of createAuthorityList () separated by commas.
Next, add to WebSecurityConfig.java for authentication.
WebSecurityConfig.java
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
//Specify the login page.
//All access to the login page is allowed.
http.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticate")
.usernameParameter("userName")
.passwordParameter("password")
.defaultSuccessUrl("/")
.permitAll();
http.csrf().disable().authorizeRequests()
.antMatchers("/RegistrationForm").permitAll()
.antMatchers("/Register").permitAll()
.antMatchers("/Result").permitAll()
.anyRequest().authenticated();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
void configureAuthenticationManager(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
}
Finally, add it to LoginController.java.
LoginController.java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
/**
*Transit to the login form.
*/
@RequestMapping("/login")
public String showLoginForm(Model model) {
//Transition to the login screen.
return "Login";
}
/**
*Move to the main page.
*If the login is successful, this method will be called.
*/
@RequestMapping("/")
public String login(Model model) {
//Main page.
return "index";
}
}
Create a screen to be displayed after login.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Main page</title>
</head>
<body>
This is the main page.
</body>
</html>
Check the operation.
First is membership registration.
Enter your user name and password to register.
When the membership registration is completed, the password is hashed and inserted into the DB like this.
Next is login.
Enter the user name and password you registered earlier.
I was able to log in and moved to the main page.
The source code up to this point is listed on github, so if you want to see all of them, please see that. If you have any questions, please contact me on twitter.
Recommended Posts