[JAVA] Login function with Spring Security

All the articles were briefly introduced, but when I actually moved my hands, there were many things I did unexpectedly. .. .. I thought that it would be helpful for spring beginners like me. I would be grateful if you could point out.

The road to login processing

What is Spring Security -DB authentication, LDAP authentication, CAS authentication, JAAS authentication, X509 authentication, and Basic authentication are supported as the authentication methods provided by Security. This time, DB authentication is performed.

■ Expected results スクリーンショット 2019-03-20 19.52.28.jpg

スクリーンショット 2019-03-20 19.52.34.jpg

things to do

・ 1. Addition of build.gradle contents ・ 2. Authentication settings in Security Config ・ 3. Implementation of UserDetailsService interface (acquisition of authentication information (create realm)) ・ 4. Create LoginUser class ・ 5. Creation of Controller ・ 6. HTML creation

There are six main steps. I have posted the demo to [github] earlier, so please refer to it if you like. ■■■ Login information ■■■ Request destination: http: // localhost: 8080 / loginForm email:[email protected] password:password ■■■■■■■■■■■■■ *** I referred to the sample project of "Site Supreme Principle Spring Boot2 Thorough Utilization **".

■ Environmental information jdk:11 Database: postgresql (docker so no installation required) ┗ About docker of this demo [here] IDE:IntelliJ Build system: gradle

Let's explain the source code.

1. Add build.gradle content

build.gradle




plugins {
	id 'org.springframework.boot' version '2.1.3.RELEASE'
	id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'mysql:mysql-connector-java'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	compile "org.springframework.boot:spring-boot-starter-validation"
	runtimeOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor "org.seasar.doma.boot:doma-spring-boot-starter:1.1.1"
	
	compile("org.seasar.doma.boot:doma-spring-boot-starter:1.1.1") {
		exclude group: "org.springframework.boot"
	}
	compile 'org.apache.commons:commons-lang3'
	//spring security
	compile 'org.springframework.boot:spring-boot-starter-security'
	// thymeleaf(roll/Controlling thymeleaf templates by permissions)
	compile "org.thymeleaf.extras:thymeleaf-extras-springsecurity5"
	compile "org.modelmapper:modelmapper:0.7.5"

}
apply plugin: 'idea'
idea.module.inheritOutputDirs = true
processResources.destinationDir = compileJava.destinationDir
compileJava.dependsOn processResources


Only the parts related to spring security are commented. This is just an addition.

2. Authentication settings in Security Config

SecurityConfig.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.builders.WebSecurity;
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;
import javax.sql.DataSource;
import static com.example.demo.common.WebConst.*;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dataSource;

    @Autowired
    UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     *Do not authenticate static files
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/favicon.ico", "/css/**", "/js/**", "/images/**", "/fonts/**", "/shutdown" /* for Demo */);
    }

    /**
     *Settings that use a unique authentication realm that implements the UserDetailsService interface
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/loginFrom").permitAll()//Login form allowed
                .antMatchers("/user/**").permitAll()
                .antMatchers("/new").permitAll()//For test(user registration)* Erase when finished
                .antMatchers("/index").permitAll()//For test(Transition screen after user registration) * Erase when finished
                .antMatchers("/user/create").permitAll()//Function for test * Erase when finished
                .anyRequest().authenticated();//Access is not permitted in all other cases without authentication
        http.formLogin()
                .loginProcessingUrl("/login")//URL to log in
                .loginPage("/loginFrom")//Login screen URL
                .failureUrl("/login?error")//URL when authentication fails
                .successForwardUrl("/success")//URL when authentication is successful
                .usernameParameter("email")//User parameter name
                .passwordParameter("password");//Password parameter name
        http.logout()
                .logoutUrl("/logout**")//URL at logout (not implemented this time)
                .logoutSuccessUrl("/login");//URL when logout is successful
    }


}


-Indicate that it is a configuration class with @ Configuration (because it defines a bean? Ambiguous around here) -Import Configuration class provided by Spring Security with @EnableWebSecurity -The Bean definition of PasswordEncoder. -Inheriting WebSecurityConfigurerAdapter and overriding theconfigure ()method. In it, set the ** authentication process URL ** and parameters. -When you send a request to the ** authentication process URL **, the process passes to the inner class called AuthenticationConfiguration and it seems that the actual authentication process is performed. (Excerpt from AuthenticationConfiguration class below)

AuthenticationConfiguration.java



@Configuration
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
...abridgement...
@Override
		public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
			T userDetailsService) throws Exception {
			return super.userDetailsService(userDetailsService)
				.passwordEncoder(this.defaultPasswordEncoder);
		}

...

3. Implementation of UserDetailsService interface (acquisition of authentication information (create realm))

What is a realm?

Concept of realm According to this

In the Web system, the range to which the same authentication policy is applied is called a realm, and the name that identifies each realm is called a realm name.

Since there is a description, I think that it is enough to recognize the processing for each user (not limited to) that is passed for authentication.

To briefly describe what the realm created this time is doing in Japanese, ** [Search the database using the user name as a key, return the information of the user if it exists, and throw an exception if it does not exist. ] ** is.

UserDaoRealm.java



package com.example.demo.security;

import com.example.demo.common.security.BaseRealm;
import com.example.demo.domain.dao.UserDao;
import com.example.demo.domain.dao.UserRoleDao;
import com.example.demo.domain.dto.User;
import com.example.demo.domain.dto.UserRole;
import lombok.extern.slf4j.Slf4j;
import org.seasar.doma.jdbc.NoResultException;
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.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

@Component
@Slf4j
public class UserDaoRealm extends BaseRealm {

    @Autowired
    UserDao userDao;

    @Autowired
    UserRoleDao userRoleDao;

    @Override
    protected UserDetails getLoginUser(String email) {
        User user = null;

        List<GrantedAuthority> authorityList = null;

        try{
            user = new User();
            user.setEmail(email);

            //Get user and save to session
            user = userDao.select(user)
                    .orElseThrow(() -> new UsernameNotFoundException("no user found. [id=]" + email + "]"));

            //Get the person in charge authority
            List<UserRole> userRoles = userRoleDao.selectByUserId(user.getId(), toList());

            //Put a prefix on the role key and put it together
            Set<String> roleKeys = userRoles.stream().map(UserRole::getRole_key).collect(toSet());

            //Collect permission keys
            Set<String> permissionKeys = userRoles.stream().map(UserRole::getPermissionKey).collect(toSet());

            //Pass both roles and permissions as Granted Authority
            Set<String> authorities = new HashSet<>();
            authorities.addAll(roleKeys);
            authorities.addAll(permissionKeys);
            authorityList = AuthorityUtils.createAuthorityList(authorities.toArray(new String[0]));
        }catch (Exception e){
            //0 Do nothing if an exception is thrown
            //Otherwise, wrap it in an authentication error exception
            if(!(e instanceof NoResultException)){
                throw new UsernameNotFoundException("could not select user,", e);
            }
        }
        return new LoginUser(user, authorityList);
    }
}


-The abstract class BaseRealm is created by implementing ʻUserDetailsService. It inherits from BaseRealm and creates ʻUserDaoRealm. -Doma is used for the process of acquiring information from userDao. For details, [here] -Implement LoginUser with NO.4.

・ 4. Create LoginUser class

LoginUser.java



package com.example.demo.security;


import com.example.demo.domain.dto.User;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;


@Data
public class LoginUser extends org.springframework.security.core.userdetails.User {
    /**
     *constructor
     *
     * @param user
     * @param authorities
     */
    public LoginUser(User user, Collection<? extends GrantedAuthority> authorities){
        super(String.valueOf(user.getEmail()), user.getPassword(), authorities);
    }

}


It just inherits from ʻorg.springframework.security.core.userdetails.User` and defines the constructor.

・ 5. Creation of Controller

After that, you can map the following URL with the controller. ・ Login form -Login process (to check the input and then pass it to spring security) ・ Successful login

LoginController.java



package com.example.demo.web.controller;

import com.example.demo.common.controller.AbstractHtmlController;
import com.example.demo.common.controller.BaseController;
import com.example.demo.web.form.LoginForm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import static com.example.demo.common.WebConst.*;

@Controller
public class LoginController extends AbstractHtmlController {


    @Override
    public String getFunctionName() {
        return "A_LOGIN";
    }

    @ModelAttribute
    LoginForm loginForm(){return new LoginForm();}
    /**
     *Login screen display
     * @Display login screen when return get method
     */
    @GetMapping("/loginForm")
    public String loginFrom(){
        return "login/login";
    }

    /**
     *Check the input
     *
     * @param form
     * @param br
     * @return
     */
    @PostMapping("/login")
    public String index(@Validated @ModelAttribute LoginForm form, BindingResult br) {
        //If there is an input check error, return to the original screen
        if (br.hasErrors()) {
            return "login/login";
        }
        //20190309 If the input check is passed, forward to the authentication process set in SecurityConfig.
        //20190309 Must be Post method so you need to use forward
        return "forward:" + LOGIN_PROCESSING_URL;
    }

    /**
     *Login successful
     */
    @PostMapping("/success")
    public String loginsuccess(@ModelAttribute LoginForm loginForm, Model model,RedirectAttributes redirectAttributes){
        model.addAttribute("msg","loginSuccess");
        return "/login/success";
    }


}


6. Creating HTML

login.html



<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Springboot</title>
    <meta charset="utf-8" />
</head>
<body>
<h1 th:text="Login"></h1>
<div th:if="${param.error}" class="alert alert-danger">
The user name or password is incorrect.
</div>
<form th:action="@{'/login'}" action="../user/index.html" th:object = "${loginForm}" method="post">
    <div class="form-group" th:classappend="${#fields.hasErrors('email')}? 'has-error'">
        <label for="email" class="control-label">email</label>
        <input id="email" type="text" class="form-control" th:field="*{email}" name="email">
        <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="error-massages">error!</span>
    </div>
    <div class="form-group" th:classappend="${#fields.hasErrors('password')}? 'has-error'">
        <label for="password" class="control-label">password</label>
        <input id="password" type="password" class="form-control" th:field="*{password}" name="password">
        <span th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="error-massages">error!</span>
    </div>
    <input type="submit" class="btn btn-default" value="Login"/>
</form>
</body>
</html>

At th: if =" $ {param.error} ", the error parameter is received and a character string is output. When you press the login button with th: action =" @ {'/ login'} ", the login process is passed to spring security.

At the end

This time, I focused on important and spring security login. Next time, I would like to try ** switching the screen to be displayed depending on the authority **. thank you for listening.

Recommended Posts

Login function with Spring Security
Implemented authentication function with Spring Security ②
Implemented authentication function with Spring Security ③
Login function implementation by Spring Security (securityConfig)
Try to implement login function with Spring Boot
[Introduction to Spring Boot] Authentication function with Spring Security
SNS login with Spring Boot
Login function implementation with rails
Login function
Create login / logout function with Spring Security according to Spring official guide [For beginners]
Authentication / authorization with Spring Security & Thymeleaf
Login with HttpServletRequest # login in Spring Security of Servlet 3.x environment
I implemented an OAuth client with Spring Boot / Security (LINE login)
DB authentication with Spring Security & hashing with BCrypt
Part 1: Try using OAuth 2.0 Login supported by Spring Security 5 with Spring Boot
Try to implement login function with Spring-Boot
Make a login function with Rails anyway
With Spring boot, password is hashed and member registration & Spring security is used to implement login function.
Use Spring Security JSP tags with FreeMarker
Implement paging function with Spring Boot + Thymeleaf
How Spring Security works with Hello World
Achieve BASIC authentication with Spring Boot + Spring Security
Hash passwords with Spring Boot + Spring Security (with salt, with stretching)
Try LDAP authentication with Spring Security (Spring Boot) + OpenLDAP
Add your own authentication items with Spring Security
Create Spring Cloud Config Server with security with Spring Boot 2.0
About Spring Security authentication
Spring with Kotorin --- 5. Actuator
Self-made Validation with Spring
With Kotorin ―― 7. Scoping Function
Spring with Kotorin ―― 1. SPRING INITIALIZR
Serverless Function with Micronaut
Download with Spring Boot
Spring with Kotorin --3. Omitting curly braces from the function
Spring Security usage memo: Cooperation with Spring MVC and Boot
Spring Boot with Spring Security Filter settings and addictive points
Implement a simple Rest API with Spring Security with Spring Boot 2.0
Handle passwords hashed with BCryptPasswordEncoder in Spring Security in Perl
Create a simple demo site with Spring Security with Spring Boot 2.1
Try to work with Keycloak using Spring Security SAML (Spring 5)
Call your own method with PreAuthorize in Spring Security
Hello World with Spring Boot
Java Config with Spring MVC
Introduced graph function with rails
Implement GraphQL with Spring Boot
Spring Security usage memo CSRF
Spring with Kotorin --8 Repository layer
Get started with Spring boot
Hello World with Spring Boot!
Spring with Kotorin --6 Asynchronous processing
Run LIFF with Spring Boot
A new employee tried to create an authentication / authorization function from scratch with Spring Security
Spring Security Usage memo Method security
Spring Security usage memo Remember-Me
File upload with Spring Boot
Spring Boot starting with copy
Spring with Kotorin ―― 7. Service layer
[Ruby on Rails] Implement login function by add_token_to_users with API
Using Mapper with Java (Spring)
Spring Boot starting with Docker
[Spring Security] Spring Security on GAE (SE)