Spring Security custom authentication page (dynamic web page solution + static web page solution) -- successful mid-term training

write in front

Last time we briefly analyzed the loading process of the spring security interceptor chain, and we still have some simple problems that have not been solved. How to customize the login page? How to get user permission information through database?
Today, I mainly solve the problem of how to configure a custom authentication page. Because the front and back ends are now separated, stateless and restful interface designs are relatively popular, so I am thinking about how to obtain the CRSF Token of spring security for static web pages. I put forward my opinion at the end of this article, but it does not seem to be a good solution. Looking forward to your valuable suggestions!

Spring Security configure custom authentication page steps

Step 1: Specify the information of the custom login page in the configuration file of spring security

<!--Static resources are not intercepted-->
    <security:http pattern="/assets/**" security="none"/>
<!--settings can be used spring of el Expression configuration Spring Security And automatically generate corresponding configuration components (filters)-->
    <security:http auto-config="true" use-expressions="true">
        <!--use spring of el expression to specify that all resource accesses for the project must have ROLE_USER or ROLE_ADMIN Role-->
        <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>

        <!--        Make this page anonymous-->
        <security:intercept-url pattern="/login.html" access="permitAll()"/>
        <security:form-login
                login-page="/login.html"
                login-processing-url="/login"
                default-target-url="/pages/index.html"
                username-parameter="username"
                password-parameter="password"
                authentication-failure-url="/failure.html"/>
        <security:logout
                logout-url="/logout"
                logout-success-url=":/login.html"/>
    </security:http>

A few notes about configuration files:

  1. login-page: configure a custom authentication page;
  2. login-processing-url: configure the url for authentication processing;
  3. default-target-url: Configure the page to be redirected after successful authentication;
  4. authentication-failure-url: Configure the page to be redirected after authentication fails;
  5. logout-url: configure the logout processing path;
  6. logout-success-url: Configure the page to jump to after successful logout;
  7. username-parameter: Configure the parameter name of the user account submitted by the front-end form to the background. The default value is username, which can be omitted;
  8. password-parameter: Configure the parameter name for submitting user credentials from the front-end form to the background. The default value is password.
  9. security="none" means that resource access under this path is not intercepted (this is different from the configuration of anonymous access)

Step 2: Write a front-end login page login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Log in</title>
</head>
<!-- import style -->
<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.2/lib/theme-chalk/index.css">
<style>
    * {margin: 0; padding: 0; box-sizing: border-box;}
    .form {position: relative;}
    .son {position: relative; top:50%;margin-top:-50px;left:50%;margin-left:-50px}
</style>
<body>
<div id="app">
<el-container>
    <el-header></el-header>
    <el-main style="margin-top: 10%">

        <div id="form" style="width: 310px;height:280px;margin: auto;background-color: #71d3bd">


            <el-form id="son" ref="form" :model="user"  style="margin: auto;padding-left: 10px;padding-right: 10px">
                <el-form-item>
                    <h3 style="text-align: center">Login please</h3>
                </el-form-item>
                <el-form-item  style="width: auto;">
                    <el-input v-model="user.username" placeholder="username"></el-input>
                </el-form-item>
                <el-form-item  style="width: auto;">
                    <el-input v-model="user.password" placeholder="password"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="onSubmit" style="width:100%;">Log in</el-button>
                </el-form-item>
            </el-form>
        </div>
    </el-main>
</el-container>
</div>


<!--axios-->
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>
<!--vue-->
<script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script>
<!-- import component library -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<script>

    new Vue({
        el: "#app",
        data: function (){
            return {
                user:{
                    username:"",
                    password:""
                }
            }
        },
        methods:{
         onSubmit(){
             axios.post("/login",this.user)
            }
        }

    })
</script>
</body>
</html>

The third step, start tomcat, access http://127.0.0.1:8081/

As we can see, we are redirected to the login page. Next we fill in the username and password and click login (defined in the previous configuration file)

We found that the results did not meet our expectations. We got a 403 (No Permission) error message.

why?

Let's compare the difference between the login page we wrote and the page given by spring security

<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Please sign in</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
    <link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous">
  <style>@media print {#ghostery-purple-box {display:none !important}}</style></head>
  <body>
     <div class="container">
      <form class="form-signin" method="post" action="/login">
        <h2 class="form-signin-heading">Please sign in</h2>
        <p>
          <label for="username" class="sr-only">Username</label>
          <input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus="">
        </p>
        <p>
          <label for="password" class="sr-only">Password</label>
          <input type="password" id="password" name="password" class="form-control" placeholder="Password" required="">
        </p>
<input name="_csrf" type="hidden" value="0aef3732-50d3-455a-8404-43d5ed9f2939">
        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
      </form>
</div>
</body></html>

The comparison found that the authentication page given by the system and the authentication page we wrote has the following paragraph in the form

<input name="_csrf" type="hidden" value="0aef3732-50d3-455a-8404-43d5ed9f2939">

That is to say, we are missing a parameter information, which is the content of the spring security prevention csrf attack mentioned in the previous article. Apparently it is prevented by validating the token.

Explain how to prevent csrf attacks by verifying the token:

​ To put it simply, Zhao Liu asks for a token from Wang Yuanwai (which can be an interface dedicated to issuing tokens in the system) before you go to the cashier to collect silver taels (obtain system services), and bring it with you when you receive silver taels. The token that Wang Yuanwai gave you, Mr. Li (an interceptor) who is in charge of the accounting room can only give you money after checking your token, otherwise he will not give you money. After you take the money, the token will be taken away (token expiration policy). Next time you want to take the money, you have to ask Wang Yuanwai for a new token.

Next, let's go to Mr. Li to get the silver taels, oh, no, it's to Wang Yuanwai to get the chicken feathers and arrows. . .

Step 4: Configure the csrf attack protection mechanism

Starting with Spring Security 4.0, CSRF protection is enabled by default using XML configuration. If you want to disable CSRF protection, you can see the corresponding XML configuration below.

<http>
    <!-- ... -->
    <csrf disabled="true"/>
</http>

Since the csrf token of spring security is stored in the HttpSession, the csrf token information can be easily obtained in the dynamic web page.

<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
    xmlns:c="http://java.sun.com/jsp/jstl/core"
    xmlns:form="http://www.springframework.org/tags/form" version="2.0">
    <jsp:directive.page language="java" contentType="text/html" />
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
    <!-- ... -->

    <c:url var="logoutUrl" value="/logout"/>
    <form:form action="${logoutUrl}"
        method="post">
    <input type="submit"
        value="Log out" />
    <input type="hidden"
        name="${_csrf.parameterName}"
        value="${_csrf.token}"/>
    </form:form>

    <!-- ... -->
</html>
</jsp:root>

If you are using static web pages, such as front-end and back-end separation, restful interface design and stateless session, you may consider the following design:

​ Expose the csef token, the front end obtains the token through ajax request, and brings the token when requesting the service. Of course, the token exposed here should only be exposed internally, so that after the static page gets the token, the web page is responded to the user.

​ Get token

​ Login

Part of the code for obtaining csrf token from static web pages

/**
 * @author Lai Bingfeng bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/8/21 12:29
 */
@RestController
@RequestMapping("/csrf")
public class CsrfTokenCtrl {

    @GetMapping(value = "/getToken")
    public HashMap<String, String> getToken(HttpServletRequest request ){
        /**
         * Here you can filter out some illegal requests and only allow internal static web server requests
         */
        
        CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        HashMap<String, String> map = new HashMap<>();
        map.put("csrf_header",token.getHeaderName());
        map.put("csrf",token.getToken());
        return map;
    }

}

End
In order to ensure the security of the system, it is necessary to sacrifice some design perfection. I believe there will be a better solution! These are just some of my personal opinions. Everyone is welcome to come up with different ideas.

Tags: Java spring-security

Posted by Mastermind on Thu, 12 May 2022 17:37:27 +0300