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.
■ 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
・ 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.
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.
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);
}
...
┗ 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.
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.
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";
}
}
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.
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