SpringBoot Security to get started quickly

Spring-boot-security: A fast implementation based on Spring Boot integration.

1. Project construction steps

1. Create maven project.

Parent project We still use the same parent project from the example above.

Create submodule spring-boot-Security POM dependency:

	
      
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>AuthDemo</artifactId>
        <groupId>com.tuling</groupId>
        <version>1.0 the SNAPSHOT</version>
    </parent>
    <groupId>com.tuling</groupId>
    <artifactId>spring-boot-security</artifactId>
    <version>0.0.1</version>
    <name>spring-boot-security</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot-version}</version>
            </plugin>
        </plugins>
    </build>
</project>

Copy the code

2. Create application.properties in the Resources directory. Spring Security can be started without any configuration

server.port=8080
spring.application.name=security-springboot

Copy the code

3. Create the startup class. Note that in the startup class, we introduced the @enableWebSecurity annotation provided by Spring Security.

package com.tuling.springbootsecurity;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@SpringBootApplication
@EnableWebSecurity
public class SpringBootSecurityApplication {
    public static void main(String[] args) { SpringApplication.run(SpringBootSecurityApplication.class, args); }}Copy the code

Create a few simple resource access interfaces

package com.tuling.springbootsecurity.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/mobile")
public class MobileController {

    @GetMapping("/query")
    public String query(a){
        return "mobile"; }}Copy the code
package com.tuling.springbootsecurity.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/salary")
public class SalaryController {

    @GetMapping("/query")
    public String query(a){
        return "salary"; }}Copy the code

At this point, we have completed the foundation of a SpringBoot project. Then we can start referencing resources that access MobileController and SalaryController, and see that accessing those resources leads to a login page that asks for login first. The login user name is user, and the password is printed in the log.

2. Re-implement the authentication and authorization logic of our last application with SpringBoot Security.

5. Inject PasswordEncoder and user source UserDetailsService

package com.tuling.springbootsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyWebConfig implements WebMvcConfigurer {
    // The default Url root path jumps to /login, which spring Security provides
    @Override
    public void addViewControllers(ViewControllerRegistry registry)
    {
        registry.addViewController("/").setViewName("redirect:/login");
    }
    /** * Inject a PasswordEncoder. *@return* /
    @Bean
    public PasswordEncoder getPassWordEncoder(a){
        return new BCryptPasswordEncoder(10);
// return NoOpPasswordEncoder.getInstance();
    }

    /** * Inject a UserDetailsService * if not, In UserDetailsServiceAutoConfiguration will default into a contain user InMemoryUserDetailsManager * Also can be used to modify the configure (AuthenticationManagerBuilder auth) method and injection authenticationManagerBean way. *@return* /
    @Bean
    public UserDetailsService userDetailsService(a){
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(User.withUsername("admin").password(passwordEncoder().encode("admin")).authorities("mobile"."salary").build(),
                                                                                       User.withUsername("manager").password(passwordEncoder().encode("manager")).authorities("salary").build(),
                                                                                       User.withUsername("worker").password(passwordEncoder().encode("worker")).authorities("worker").build());
        return userDetailsManager;
// return new JdbcUserDetailsManager(DataSource dataSource);}}Copy the code

6. Injection verification configuration rules:

package com.tuling.springbootsecurity.config;

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;

/** * inject a custom configuration */
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    // Configure the security interception policy
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Configure the blocking policy chained
        http.csrf().disable()// Disable CSRG cross-domain check
                .authorizeRequests()
                .antMatchers("/mobile/**").hasAuthority("mobile") // Configure resource permissions
                .antMatchers("/salary/**").hasAuthority("salary")
                .antMatchers("/common/**").permitAll() // Requests under common pass directly
                .anyRequest().authenticated() // Other requests require login
                .and() // Parallel condition
                .formLogin().defaultSuccessUrl("/main.html").failureUrl("/common/loginFailed"); // you can login from the default login page and jump to main.html}}Copy the code

7. Get current user information: Spring Security provides several ways to get current user information.

package com.tuling.springbootsecurity.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.security.Principal;

@RestController
@RequestMapping("/common")
public class LoginController {

    @GetMapping("/getLoginUserByPrincipal")
    public String getLoginUserByPrincipal(Principal principal){
        return principal.getName();
    }
    @GetMapping(value = "/getLoginUserByAuthentication")
    public String currentUserName(Authentication authentication) {
        return authentication.getName();
    }
    @GetMapping(value = "/username")
    public String currentUserNameSimple(HttpServletRequest request) {
        Principal principal = request.getUserPrincipal();
        return principal.getName();
    }
    @GetMapping("/getLoginUser")
    public String getLoginUser(a){
        User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        returnuser.getUsername(); }}Copy the code

Then we ported the foreground page over.

Thus, a simple Spring Secuity project is configured. So let’s summarize this briefly.

1. We can implement password encryption by injecting a PasswordEncoder object. NoOpPasswordEncoder is an outdated encryptor that does nothing to encrypt the password. In actual projects, the most commonly used is BCryptPasswordEncoder.

2. We manage the entity data of the system by injecting a UserDetailsService. If we don’t own injection UserDetailsService, that will be the default in UserDetailsServiceAutoConfiguration infuse a UserDetailsService contains user, The password of user is printed in console logs. And besides our system used to InMemoryUserDetailsManager SpringSecurity also provides JdbcUserDetailsManager implemented for user data in the database management.

In addition, about the user data sources, can cover the configure of WebSecurityConfigurerAdapter (AuthenticationManagerBuilder auth) method, And injection authenticationManagerBean () way to intervene.

3. The permission rules in the current example are written directly out of memory, while the actual project is obviously loaded from the database. Also, while our rules are currently customized based on web request paths, Spring Security actually provides annotation-based method-level rule configuration.

3. Project testing

This will start the task for testing. Once started, you can access the login page provided by Security by defaulthttp://localhost:8080/login

You can then log in using the three users you created earlier and go to the test main page.

In the test page, logout uses the default logout address /logout provided by the Security framework. Access to services under Mobile and Salary respectively can see that permissions are controlled.

4. Understand the extension points of the SpringBoot Security project

As a result, a basic Spring-boot-Security project is quickly set up. Spring Security actually provides quite a lot of extension points, including user name and password verification rules, resource verification rules, Session management rules, and so on. We need to understand these extension points so that we can use Spring Security in real projects.

1. Main data sources

SpringSecurity manages the principal data by referring to the UserDetailsService object in the Spring container. By default, a default principal management service containing the User user is injected. We can by injecting a demo InMemoryUserDetailsManager covers the main body of the default manager object.

Most of the user information in real projects comes from databases. In SpringSecurity, JdbcUserDetailsManager is also provided to manage user information in the database. If these do not meet the actual requirements, you can implement a UserDetailsService object and inject it into the Spring container to implement custom principal data management.

2. Password resolver

Spring Security provides many password parsers, including CryptPassEncoder, Argon2PasswordEncoder, Pbkdf2PasswordEncoder, etc. See the PassEncoder interface implementation class for details. The most common one is the BCryptPasswordEncoder. It should be noted that when we select different password parsers, the background store user passwords to store the corresponding ciphertext.

3. Customize authorization and security interception policies

The conventional way is through the cover of WebSecurityConfigurerAdapter protected void the configure (HttpSecurity HTTP) method. Configure custom interception rules over HTTP. Includes access control, login page and logic, exit page and logic, etc.

Custom login: the http.loginpage () method configures the loginPage, and the http.loginprocessingurl () method customizes the login logic. Note that SpringSecurity’s login page and login logic are the same address /login. If you use a custom page, you need to separate the login logical address as well. For example: http.loginPage(“/index.html”).loginProcessingURL (“/login”).

For details about logical processing on the login page, see the default login page provided by the system. However, it is important to pay attention to the access rights of the login page. And on the login page source code, can be found in the DefaultLoginPageGeneratingFilter.

Remember Me: The login page provides the Remember Me function, which simply submits a parameter of REMEber-me to login, which can be on, yes, 1, or true, to remember the token of the currently logged in user into the cookie. Http.rememberme ().remembermeparameter (“remeber-me”), which you can use to customize the parameter name. And when YOU log out, it clears cookies that remember my function.

Interception strategy: The antMachers() method sets up path matching, with two asterisks representing multiple paths, one asterisk representing one or more characters, and a question mark representing one character. Configure the corresponding security policy.

PermitAll () is accessible to all. DenyAll () is inaccessible to all. Anonymous () is accessible only to those who have not logged in, but not to those who have logged in.

HasAuthority and hasRole Are configured to be accessed only by corresponding permissions or roles. A role is a resource that corresponds to a ROLE_ role name.

The other two configuration object, AuthenticationManagerBuilder configuration authentication strategy, WebSecurity configuration added Web request strategies.

4. About CSRF

CSRF stands for Cross-site Request Forgery. This is a kind of security attack method. In simple terms, hackers can use the information of existing clients to forge a normal client for attack. For example, if you visit website A, log in and open A TAB page to visit website B without exiting, then website B can use the sessionId saved in the browser to forge your identity to visit website A.

In our example, we simply turn off CSRF checking using the http.csrf().disable() method. Spring Security has a special checking mechanism for CSRF. His idea is to add a TOKEN value of CSRF to the background session. Then, when sending requests to the back end, requests other than GET, HEAD, TRACE, and OPTIONS, such as POST, PUT, and DELETE, will be matched with this token value.

When we open the check of CSRF and access the default login page, we can see that there is a hidden field named CSRF in the login form of the page, which is the token of CSRF. For example, we can use add in freemarker’s template language.

In the Spring Security background, there is a CsrfFilter that checks Csrf parameters. He will call HttpSessionCsrfTokenRepository generated a CsrfToken, value and will be saved to the Session.

5. Annotation level method support: On the registered class @ Configuration support open comments @ EnableGlobalMethodSecurity (prePostEnabled = true, securedEnabled = true, jsr250Enabled = True) can support methods and annotation support. The prePostEnabled attribute corresponds to @preauthorize. The securedEnabled attribute supports the @secured annotation for role-level permission control. The jsr250Enabled attribute corresponds to the @rolesAllowed annotation, which is equivalent to @secured.

Exception handling: The state of the front and back end is now separated by @ControllerAdvice to inject an ExceptionHandler class to declare the method with the @ExceptionHandler annotation and push exception information to the front end.