[JAVA] Try to implement login function with Spring-Boot

Preface and environment

I implemented a simple login function using Spring-Boot. I've done something similar with PHP once, and when I thought I could afford it, I had a hard time, so I'll summarize it as a memorandum. If you write everything comprehensively, you will run out of power, so I will write roughly focusing on the clogged part. I'm wrong here! If you have any questions, please kindly let me know (╹◡╹)

environment

Process flow

The flow of this process is as follows. There are "hello.html" and "login.html" on the screen, and hello.html is a screen that cannot be seen without logging in. ① Authorization processing If you try to access hello.html without logging in, it will transition to login.html

② Authentication process Compare the "Username" and "Password" values entered in the form with the values stored in the DB. If there is a match, the authentication is successful. If it does not exist, it will transition to login.html and display an error message.

③ Session management If the authentication is successful, the session information is stored in the DB. Allows you to get the username from the session information as needed.

④ Discard the session When the logout button is pressed, the session information will be discarded and authentication will be required again.

The figure below summarizes these processes.

フロー図.png

If you implement these at once, your head will get flat, so this time we implemented it in 3 steps.

Below we will look at the implementation steps for each step.

Dependencies

Before that, I will describe the dependencies when creating a project with Spring-Boot. For more details, see pom.xml. dependencies.png

Step 1. Enter "user" and "password" and it will pass!

We will start by making the foundation. Here's what you'll make in this step:

Let's start with all the base configuration files. But before that ...

Config file difficult problem

It seems difficult to tinker with the config file ... So at first I thought Spring-Security was hard to get along with. However, since Spring-Security allows you to write settings in the form of Java classes, I feel that once you write it, you can get a feel for it. I understand intuitively that it is possible to set the property of the class that manages the setting by adding the value (setting for the system) because it feels like playing with the volume and image quality in the game. I think it's easy.

MvcConfig is especially easy to understand, so if you are new to Config, you may want to try it from there. When I first saw the config file, it looked completely different from the code I usually write, so I wrote it before actually looking at the code.

WebSecurityConfig.java


package login.app.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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import login.app.service.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	
	@Autowired
	private UserDetailsServiceImpl userDetailsService;
	
	//Since the password obtained from the DB to be compared with the form value is encrypted, it is also used to encrypt the form value.
	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		return bCryptPasswordEncoder;
	}
	
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                            "/images/**",
                            "/css/**",
                            "/javascript/**"
                            );
    }

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
		    .authorizeRequests()
		        .anyRequest().authenticated()
		        .and()
		    .formLogin()
		        .loginPage("/login") //Since the login page does not go through the controller, it is necessary to link it with ViewName.
		        .loginProcessingUrl("/sign_in") //Submit URL of the form, authentication process is executed when a request is sent to this URL
		        .usernameParameter("username") //Explicit name attribute of request parameter
		        .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").roles("USER");
	}

}

The basic part is carefully summarized by Official, so if you don't know what you are saying, please refer to that. I think it's better to do it.

Now let's take a look at each process.

Annotation

Read the @Configuration annotation properly as this class is a configuration file! It is for telling Spring. The @EnableWebSecurity annotation that accompanies it is for using this and that of Spring-Security.

Inheritance class

This class inherits from the "WebSecurityConfigurerAdapter" class, and it will be used by overriding some methods used in the settings. This time, we will override the configure method and set the authentication / authorization process.

configure method (WebSecurity)

Here, the settings are made so that the request when using static files (image, css, javascript) is not played.

configure method (HttpSecurity)

It is a method to describe the setting for the part related to http request in the authentication / authorization process. It seems difficult at first glance because there are a lot of dots lined up, but all I'm doing is setting individual settings, so if I'm fishing for tutorials, I'm sure it will be so. Here is a rough list of what is set.

configure method (AuthenticationManagerBuilder)

Here, inMemoryAuthentication () is used to clearly indicate that in-memory authentication is performed, and the user name and password used for authentication are set directly. I think that it is unlikely that modern applications will not use DB, so I would like to revisit the authentication process in the part that uses DB.

With or without Autowired

If you look at tutorials, you will often see the method name configureGlobal and the @Autowired annotation. Speaking of @Autowired, you often see it used for fields, but you can also attach it to methods. Regarding the config class this time, the name can be set to configure for the method without @Autowired annotation, and any name can be set for the method with @Autowired annotation. Reference


Next, let's look at the configuration file related to ViewName.

MvcConfig.java


package login.app.config;

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 {
	
	/**
	 * 「/login from the URL "login".Call html
	 */
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/login").setViewName("login");
	}

}

Normally, in Spring-Boot, there is a controller, and after picking up the request with @RequesMapping annotation and doing some processing, it returns the view name for the screen (html) and displays the screen. However, since Spring-Security teacher is doing the login process, I think that there is no problem even if you do not implement LoginController or something like that. However, since it is necessary to separate the association between the view name and the URL, we will describe it in this configuration file. Here, when a request comes with the URL "/ login", the process is described by displaying "login" with the view name (login.html). I would be very happy if you could tell me if there is a better way to describe it.


I'll omit hello.html because it's just a greeting, but I'll write about login.html because it's a little addictive.

login.html


<form th:action="@{/sign_in}" method="post">
	    <input type="text" name="username">user_name
	    <br/>
	    <input type="password" name="password">password
	    
	    <br/>
	    
	    <input type="submit" value="Login">
	</form>

The point to note here is that the value of the name attribute should match the one described in the configuration file, and that the destination of the form should be specified by the th: action attribute. However, if you write "/ sign_in", you will get angry that there is no such URL. This is because Spring-Security's CSRF measures are enabled by default. If you pass the plain URL to acrtion without thinking in this state, you will get angry because the token for CSRF countermeasures is not placed. There are various ways to solve this, but this time, by setting "th: action = @ {URL}" in Thymeleaf, a token will be generated and put on the URL. Reference

Also, by default, the URL of the destination is a long URL that performs authentication processing, but I think it is better to specify it in the configuration file.


If you execute it with the implementation so far, the login screen will be displayed, so if you enter "user" and "password", you will be able to transition to hello.html. Since I forgot to capture the execution result every time, the execution screen will be displayed only in step 3, but please forgive me.


Step 2. If you find a user in the DB, let it through!

We have implemented a feature that allows only the user logged in in step 1 to see the page. However, it is not possible to identify "who logged in" if everyone uses a common user name and password. Security is too ridiculous in the first place. In this step, we will add a function that only users registered in the DB can log in. The following items are required.

Since the purpose here is to create a login function, I will omit how to use Entity and EntityManager, but I would like to summarize this area as well. So let's take a look at the UserDetailsServiceImpl class.

UserDetailsServiceImpl.java


package login.app.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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import login.app.dao.LoginUserDao;
import login.app.entity.LoginUser;

@Service
public class UserDetailsServiceImpl implements UserDetailsService{
	
	//A class that implements a method to search user information from DB
	@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 of permissions
		//There are Admin, User, etc., but since we will not use them this time, only USER is temporarily set.
		//When using authority, it is necessary to create and manage the authority table and user authority table on the DB.
		List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
		GrantedAuthority authority = new SimpleGrantedAuthority("USER");
		grantList.add(authority);
		
		//RawData password cannot be passed, so encryption
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		
		//Since UserDetails is an interface, cast the user object created by the constructor of the User class.
		UserDetails userDetails = (UserDetails)new User(user.getUserName(), encoder.encode(user.getPassword()),grantList);
		
		return userDetails;
	}

}

The processing flow of the method loadUserByUsername that acquires the information for performing the authentication process is as follows.

I would like to take a look at each of them.

Authority information

In Spring-Security, it is mandatory to pass the authority (Admin, User, etc.) to the authentication information. However, since all you need is a User here, we will temporarily generate permission information from SimpleGrantedAuthority and put it in the GrantedAuthority list. If you want to set the authority, you need to create the authority table and user authority table in the DB and increase the number of entities, but this time it is OK if you can log in, so I will omit it.

encryption

In Spring-Security, passwords are basically encrypted. Since I have no knowledge about encryption, I feel that I am using famous places for the time being, but when making an actual application, I have knowledge about each method and encrypt the password at the stage of storing user information in the DB. I think it will be necessary.

UserDetails object

In Spring-Security, instead of passing user information as user Entity, a user name called UserDetails, encrypted password, and UserDetails type object created by permission information are passed to the authentication process. Since the User class is an implementation class of the UserDetails interface, it can be used as a return value as it is, but I think it is better to cast it according to the method type and then return it.


Next, since the authentication information has been changed from in-memory to DB, I would like to take a look at the changed part of the configuration file.

WebSecurityConfig.java(Authentication processing part)


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

Here, if you specify that userDetailsService is used for authentication and pass the implementation class created earlier as an argument, a class called DaoAuthenticationProvider will be called and the loadUserByUsername method implemented during authentication will be called. .. Specify the same encryption method as that of DB in passwordEncoder, and use it to compare the input value of the form with the password of DB and perform the authentication process.


If you submit the form with login.html again with the implementation so far, it will search the DB and only the users existing in the DB will be authenticated. You did it.


Step 3. See who is logged in in the session!

Finally, it manages session information and realizes the following functions.

There are various methods of session management, but this time I will put it on MySQL. However, all you have to do is prepare a [session table generation query](https://github.com/spring-projects/spring-session/tree/master/spring-session-jdbc/src/main/ Just run resources / org / springframework / session / jdbc) and mess around with application.properties and dependencies. Spring-Security will take care of the contents.


Session management may not be able to be written here, such as using Redis with NoSQL or what to do with the session ID, so this time I will only write a rough usage method for creating a login function, and detailed contents I would like to look at another one.

Creation / setting of session storage table

The creation itself is OK if you execute the CREATE statement for each SQL you are using at the above link. You can see how the session behaves in the app by sequentially SELECTing the contents of this table. I need to know that area properly, so I'd like to organize it a little more before writing.

Next is the setting to use the table, but this is OK just by adding a little to application.properties and pom.xml. Thank you.

application.properties


spring.session.store-type=jdbc

pom.xml(Session usage part)


       <dependency>
		<groupId>org.springframework.session</groupId>
		<artifactId>spring-session-jdbc</artifactId>
	    </dependency>

I don't think pom.xml is in the list of dependencies at the project generation stage, so you need to add it by hand. I didn't understand this and it was quite packed.

Reading session information

"Hello, user name san!" In hello.html in order to see something like, who you are logged now, you must have information that. This will be implemented by the init method of HelloController that transitions when login is successful.

HelloController.java(Method called at screen transition)


@RequestMapping("/hello")
	private String init(Model model) {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		//Get login user information from Principal
		String userName = auth.getName();
		model.addAttribute("userName", userName);
		return "hello";
		
	}

The point here is the SecurityContextHolder. This stores the information of the current logged-in user, but it is actually an HttpSession. In other words, the user information contained in the session information defined on Spring-Security is used as a key to refer to the DB that stores the session information, and the login status of the corresponding user is set. It's complicated. I will organize this area properly and summarize it in another article (o_o) If it is the original application, we will pick up the user ID area from the DB from the acquired user name and read the information of each user further ..., but this time we just put the user name on the request scope. I will keep it in. By transitioning to hello.html in this state, the name of the logged-in user can be displayed on the screen.

Log out

At the time of logout, there are various things such as discarding session information, but if you send a request to "/ logout" of Spring-Security teacher, it will do it. You can implement the logout process yourself, but it seems that CSRF will do all this well, so I think it's safe to leave it to us. However, I think it is necessary to keep in mind what is required in the process and how the DB information of the session changes. Organize that area (abbreviated below)

There is one caveat, it seems that when you transition to "/ logout" on hello.html, you must always transition with the "POST" method. It seems that it is for doing this and that of CSRF, but since my understanding is fluffy, I would like to do my best to study security.

Execution result

With this, we have implemented a set of functions. Finally, I would like to put the execution result of a series of processes.

login.png Login screen invalid.png Login failure success.png Login successful logout.png Log out

The source code for this series of implementations is listed on GitHub. There is also a sql file in the sql folder to do this and that with a single command, so if you don't mind.

Recommended Posts

Try to implement login function with Spring-Boot
Try to implement login function with Spring Boot
How to implement TextInputLayout with validation function
[Swift] How to implement the LINE login function
Try to implement TCP / IP + NIO with JAVA
Java to play with Function
Login function with Spring Security
Login function implementation with rails
Implement search function with form_with
Try to implement tagging function using rails and js
Let's implement a function to limit the number of access to the API with SpringBoot + Redis
[Ruby on Rails] Implement login function by add_token_to_users with API
Implement login function simply with name and password in Rails (3)
Implement text link with Springboot + Thymeleaf
Try to implement Yubaba in Ruby
Implement simple login function in Rails
Try to imitate marshmallows with MiniMagick
[Java] Try to implement using generics
Try to implement Yubaba in Java
With Spring boot, password is hashed and member registration & Spring security is used to implement login function.
[Swift] How to implement the Twitter login function using Firebase UI ①
Login function
[Swift] How to implement the Twitter login function using Firebase UI ②
Try to implement n-ary addition in Java
[Swift] Easy to implement modal with PanModal
[Swift] How to implement the countdown function
Make a login function with Rails anyway
Try to implement UIAlertController by separating files
Implement paging function with Spring Boot + Thymeleaf
I tried to implement ModanShogi with Kinx
How to implement search function with rails (multiple columns are also supported)
"Teacher, I want to implement a login function in Spring" ① Hello World
Implement Sign in with Twitter in spring-boot, security, social
I tried to implement a function equivalent to Felica Lite with HCE-F of Android
I tried to manage login information with JMX
Try to link Ruby and Java with Dapr
[swift5] How to implement the Twitter share function
How to implement the breadcrumb function using gretel
[Android] Implement a function to display passwords quickly
[For beginners] How to implement the delete function
Flow to implement image posting function using ActiveStorage
Create login / logout function with Spring Security according to Spring official guide [For beginners]
Try to get redmine API key with ruby
[Swift] How to implement the fade-in / out function
[Rails] How to easily implement numbers with pull-down
Implement search function with Rakuten Books (DVD) API
Try to automate migration with Spring Boot Flyway
How to get resource files out with spring-boot
Try to display hello world with spring + gradle
Try to summarize the common layout with rails
[Introduction to Spring Boot] Authentication function with Spring Security
How to redirect after user login with Spring-security
Continued ・ Flow to implement image posting function using ActiveStorage
I tried what I wanted to try with Stream softly.
How to implement UICollectionView in Swift with code only
How to implement login request processing (Rails / for beginners)
I tried to implement file upload with Spring MVC
How to implement guest login in 5 minutes in rails portfolio
I tried to implement TCP / IP + BIO with JAVA
[Implementation procedure] Implement image upload function with Active Storage
How to make LINE messaging function made with Ruby