Getting started with Spring Security

Keywords: Java Spring Microservices security Java framework

Getting started with Spring Security

1, Use of Spring Security

1.1 basic terms

Spring Security is a powerful and highly customizable authentication and authorization framework. It is a set of Web security standards for spring applications. Spring Security focuses on providing authentication and authorization functions for java applications.

OAuth2 is an industry standard protocol for authorization, which provides class specific authorization flow to simplify client development.

Resource owner: the end user who owns the resource and has the account and password to access the resource.

Resource server: the server that owns the protected resource. If the request contains the correct access token, it can access the resource.

Client: the client that accesses the resources will use the access token to obtain the resources of the resource server, which can be a browser, mobile device or server.

Authorization server: a server used to authorize users. If the client passes the authorization, it issues a token to access the resource server.

Four authorization modes:

  • Authorization Code mode: the client first directs the user to the authorization server, logs in and obtains the Authorization Code, then authorizes, and finally obtains the access token according to the Authorization Code.

  • Implicit (simplified mode): compared with the authorization code mode, it cancels the process of obtaining the authorization code and directly obtains the access token.

  • Resource Owner Password Credential (password mode): the client directly obtains the user name and password from the user, and then obtains the access token from the authorization server.

  • Client Credential: the client obtains the access token from the authorization server directly through the client authorization.

1.2 basic use

1.2.1 import dependency

Create a new module tools security and introduce dependencies:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!--    Safety certification    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

1.2.2 configuration file

server:
  port: 9040

spring:
  application:
    name: tools-security

1.2.3 test interface

    @RequestMapping("test")
    public String test() {
        return "test";
    }

Run the project and find the Using generated security password in some logs intercepted below. The following string is the original password (the results are different each time) and the account is user

2021-10-23 15:52:58.472  WARN [tools-security,,] 100588 --- [  restartedMain] o.s.c.s.a.z.ZipkinAutoConfiguration      : Check result of the [RestTemplateSender{http://localhost:9411/api/v2/spans}] contains an error [CheckResult{ok=false, error=org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: [Empty JSON_V2 message]}]
2021-10-23 15:52:59.049  INFO [tools-security,,] 100588 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-10-23 15:52:59.303  INFO [tools-security,,] 100588 --- [  restartedMain] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: 0c731ade-806a-4a8e-83e1-e11044fe0f48


visit http://localhost:9040/test , you will jump to the default login page and enter the account (user) password (0c731ade-806a-4a8e-83e1-e11044fe0f48) to access the / test interface.

1.3 15 filters in the filter chain

1. org.springframework.security.web.context.SecurityContextPersistenceFilter

The first filter to bear the brunt

The SecurityContextPersistenceFilter mainly uses the SecurityContextRepository to save or update a securitycontext in the session and give the securitycontext to future filters to establish the required context for subsequent filters.

2. org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter

This filter is used to integrate SecurityContext into WebAsyncManager in Spring asynchronous execution mechanism

3. org.springframework.security.web.header.HeaderWriterFilter

Add corresponding information to the requested Header, which can be controlled by using security: headers inside the http tag

4. org.springframework.security.web.csrf.CsrfFilter

CSRF is also called cross domain request forgery. Spring security will verify whether all post requests contain the token information of CSRF generated by the system. If not, an error will be reported. It can prevent CSRF attack.

5. org.springframework.security.web.authentication.logout.LogoutFilter

Match the request with the URL of / logout to exit the user and clear the authentication information.

6. org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

The authentication operation depends on this filter. The default matching URL is / login and must be a post request.

7. org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter

If no authentication page is specified in the configuration file, a default authentication page is generated by the filter

8. org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter

This filter can produce a default exit login page

9. org.springframework.security.web.authentication.www.BasicAuthenticationFilter

This filter will automatically parse the header information in the http request whose header name is Authentication and starts with Basic.

10. org.springframework.security.web.savedrequest.RequestCacheAwareFilter

A RequestCache is maintained internally through HttpSessionRequestCache to cache HttpServletRequest

11. org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter

The ServletRequest is wrapped once, so that the request has a richer API

12. org.springframework.security.web.authentication.AnpnymousAuthenticationFilter

When the authentication information in the SecurityContextHolder is empty, an anonymous user will be created and stored in the SecurityContextHolder.

In order to be compatible with unlisted access, spring security also goes through a set of authentication process, which is just an anonymous identity.

13. org.springframework.security.web.session.SessionManagementFilter

The SecurityContextRepository limits the number of replies opened by the same user

14. org.springframework.security.web.access.ExceptionTranslationFilter

The exception transfer filter is located behind the entire spring securityfilterchain and is used to convert exceptions in the entire link.

15. org.springframework.security.web.access.intercept.FilterSecurityinterceptor

Obtain the authorization information of the configured resource access, and determine whether it has permission according to the user information stored in the SecurityContextHolder

1.4 user defined authentication WebSecurityConfigurerAdapter

The previous method is to use the default account user and the password automatically generated when the application is started for access authentication. However, each system should have its own user system. User login authentication and interface authentication control need to be customized. Here, websecurity configuration needs to be configured, mainly focusing on three methods: configure(HttpSecurity http), configure(AuthenticationManagerBuilder auth) and configure(WebSecurity).

  • HttpSecurity allows network-based security to be configured at the resource level based on selection matching, and declares that any other web address needs to be successfully verified. That is to restrict the access path of the role.
  • AuthenticationManagerBuilder is used to establish authentication mechanisms by allowing authenticationproviders to be easily added. The configured authentication information is mainly used to configure user identity and role information, that is, it is used to record account, password and role information.
  • WebSecurity is used to configure settings that affect global security (configure resources, set debugging mode, and reject requests by implementing custom firewall definitions). It is generally used to configure some general things of the global, such as static resources.

Create WebSecurityConfiguration.java and inherit WebSecurityConfigurerAdapter

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

}

1.4.1 configure(HttpSecurity http)

HttpSecurity has two important methods: authorizeRequests() and requestMatchers()

authorizeRequests()

Methods of authorization management control

This method returns an expressionurlauthorizationconfigurator.expressionintercepturlregistry object.

authorizeRequests defines those URLs that need to be protected and those that do not need to be protected. It usually comes out in the first line of the configuration.

All Security permissions are controlled based on this class. For example:

http.authorizeRequests().anyRequest().authenticated() requires permission authentication for all interfaces, which is equivalent to http.authorizeRequests().antMatchers("/**").authenticated();

The http.authorizeRequests().antMatchers("/**").permitAll() configuration requires that all interfaces do not need permission authentication, which is equivalent to http.authorizeRequests().anyRequest().permitAll()

In addition, the antMatchers method in these two codes is to configure matching rules. That is, which interfaces need or do not need permission authentication.

anyRequest(): all interfaces

authenticated(): authority authentication is required

permitAll(): permission authentication is not required

Ant matchers (string): matching interface

requestMatchers()

Obtain the RequestMatcherConfigurer object and configure the routes that can be filtered; For example, requestMatchers().anyRequest() is equivalent to http.authorizeRequests().anyRequest().access("permitAll");

give an example:

package com.lmc.security.conf;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @author lmc
 * @Description: TODO
 * @Create 2021-10-23 19:55
 * @version: 1.0
 */
@Configuration

public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/403", "/404", "/500").permitAll()    // Authentication is not required when accessing 403 / 404 / 500
                .anyRequest().authenticated()   // Other requests require authentication
                .and()
                .formLogin()    // Login via form
                .and()
                .csrf().disable();  // Turn off Cross Site Request Forgery protection
    }
}

In the above configuration, I have defined three interfaces with requests of / 403, / 404 and / 500. They do not need to be authenticated and can be released directly; Then other requests need authentication; Then, the form mode should be used when logging in, and the Cross Site Request Forgery protection should be turned off at the same time.

Before configuring, I have created three pages 403 / 404 / 500

@Controller
public class PageController {

    @RequestMapping("403")
    public String accessError() {
        return "403";
    }

    @RequestMapping("404")
    public String notFound() {
        return "404";
    }

    @RequestMapping("500")
    public String serverError() {
        return "500";
    }
    

}

At this time, run the project and access the / 404 interface normally. Then, when accessing the / test interface, you will jump to the default login page of Spring Security and enter the account and password before release.

1.4.2 configure(AuthenticationManagerBuilder auth)

Login with custom user password (memory mode)

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()   // Store user information in memory
                .withUser("lmc").password("{noop}123") // The user name is lmc and the password is 123, {noop} means that the password is not encrypted
                .roles("USER");  // Give the USER lmc the USER role
    }

The default password used by spring security needs to be encrypted, that is, the password in. password(String) is already an encrypted string. If {noop} is added before it, it means an unencrypted password

.withUser("lmc").password("{noop}123")
 It can also be written as
.passwordEncoder(new BCryptPasswordEncoder()).withUser("lmc").password(new BCryptPasswordEncoder().encode("123"))

Note that before security 5.0, the default encryption method was BC, so you only need to write

.withUser("lmc").password(new BCryptPasswordEncoder().encode("123"))

Yes, but the encryption method should be defined after 5.0, so you need to use the passwordEncoder() method to define the encryption method, otherwise the following exceptions will be reported:

There is no PasswordEncoder mapped for the id "null"

1.4.3 configure(WebSecurity web)

Configure static resources and ignore authentication

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/assets/**")
                .antMatchers("/403", "/404", "/500")
                .antMatchers("/login.html");
    }

In fact, the content of configure(WebSecurity web) can be basically configured in configure(HttpSecurity http). For example, the above configuration is configured in configure(HttpSecurity http) as follows:

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/403", "/404", "/500").permitAll()    // Authentication is not required when accessing 403 / 404 / 500
                .antMatchers("/assets/**").permitAll()
            	.antMatchers("/login.html").permitAll();
    }

However, we generally configure common things such as login pages, error pages and static resources in configure(WebSecurity web). configure(HttpSecurity http) prefers network-based security, that is, the control of role permissions, which is clear-cut and creates better observability.

1.4.4 user defined form login

We often don't use the default login page of security. We need to create and develop our own custom login page, which can be operated through HttpSecurity.

Customize the login page first:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login Page</title>

    <!-- Import style -->
    <link rel="stylesheet" href="/assets/element-ui/index.css">
</head>
<style>
    .el-header {
        background-color: #1B73E8;
        color: white;
        text-align: left;
        line-height: 60px;
    }
    .el-footer {
        background-color: #F5F5F5;
        color: black;
        text-align: center;
        line-height: 60px;
        font-size: 12px;
    }
    .el-main {
        background-color: white;
        color: #333;
        text-align: center;
        line-height: 160px;
    }

    body > .el-container {
        margin-bottom: 40px;
    }

    .el-row {
        margin-bottom: 20px;
    }
    .el-col {
        border-radius: 4px;
    }
    .bg-purple-dark {
        background: #99a9bf;
    }
    .bg-purple {
        background: #d3dce6;
    }
    .bg-purple-light {
        /*background: #e5e9f2;*/
    }
    .grid-content {
        border-radius: 4px;
        min-height: 36px;
        height: 800px;
    }
    .row-bg {
        padding: 10px 0;
        background-color: #f9fafc;
    }

    .el-card {
        padding: 80px;
        height: 638px;
    }
</style>
<body>
    <div id="app">
        <el-container>
            <el-header>lmc-tools</el-header>
            <el-main>
                <el-row :gutter="20">
                    <el-col :span="7"><div class="grid-content bg-purple"></div></el-col>
                    <el-col :span="10">
                        <div class="grid-content bg-purple-light">
                            <el-card class="box-card">
                                <el-form ref="form" action="/login" method="post" :model="form" :rules="rules" label-width="100px" name="loginform" class="login-form">
                                    <el-form-item label="user name" prop="username">
                                        <el-input v-model="form.username" name="username" clearable></el-input>
                                    </el-form-item>
                                    <el-form-item label="password" prop="password">
                                        <el-input v-model="form.password" name="password" show-password type="password" clearable></el-input>
                                    </el-form-item>
                                    <el-form-item>
                                        <el-button type="primary" @click="submitForm('form')">Sign in</el-button>
                                    </el-form-item>
                                </el-form>
                            </el-card>
                        </div>
                    </el-col>
                    <el-col :span="7"><div class="grid-content bg-purple"></div></el-col>
                </el-row>
            </el-main>
            <el-footer>Copyright © 2021 lmc</el-footer>
        </el-container>
    </div>
</body>
<!-- introduce vue -->
<script src="/assets/vue/vue.js"></script>
<!-- introduce Element-UI Component library -->
<script src="/assets/element-ui/index.js"></script>
<script>
    new Vue({
        el: "#app",
        data: {
            form: {
                username: '',
                password: ''
            },
            rules: {
                username: [
                    {required: true, message: 'User name cannot be empty', trigger: 'blur'}
                ],
                password: [
                    {required: true, message: 'Password cannot be empty', trigger: 'blur'}
                ]
            }
        },
        methods: {
            // Event triggered by login button
            submitForm: function(formName) {
                // Verify that the input box is empty
                this.$refs[formName].validate((valid) => {
                  if (valid) {
                      document.loginform.submit()
                  }
                })
            }
        }
    })

</script>
</html>

Define the interface to access the login page:

    /**
     * Login page
     * @return
     */
    @RequestMapping("login.html")
    public String login() {
        return "login";
    }

Configuration class modification:

package com.lmc.security.conf;

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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @author lmc
 * @Description: TODO
 * @Create 2021-10-23 19:55
 * @version: 1.0
 */
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/assets/**")
                .antMatchers("/403", "/404", "/500")
                .antMatchers("/login.html");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()   // Store user information in memory
                .withUser("lmc").password("{noop}123") // The user name is lmc and the password is 123, {noop} means that the password is not encrypted
                .roles("USER");  // Give the USER lmc the USER role
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()   // Other requests require authentication
                .and()
                .formLogin()    // Login via form
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .failureUrl("/500")
                .and()
                .csrf().disable();  // Turn off Cross Site Request Forgery protection
    }
}

At this point, access http://localhost:9040/test Will jump to http://localhost:9040/login.html Page, if the account password is entered incorrectly, it will jump to the failure page http://localhost:9040/500 , custom login completed.

Posted by tunari on Sat, 30 Oct 2021 00:00:24 -0700