Writing in the front

Last time we briefly analyzed the loading process of the Spring Security interceptor chain, we still had some simple issues to resolve. How do I customize the login page? How do I obtain user permission information from the database? Today we will focus on how to configure a custom authentication page. Because of the separation of front and back ends, stateless and restful interface design is popular now, so I am thinking about how static web pages can obtain the CRSF Token of Spring Security. I put forward my opinion on this problem at the end of this article, but it seems not a very good solution, I am looking forward to your valuable suggestions!

Spring Security configures custom authentication page steps

Step 1: Specify the information for the custom login page in the Spring Security configuration file

<! - static resources not intercept - > < security: HTTP pattern = "/ assets / * *" security = "none" / > <! < Security: HTTP auto-config="true" use-config ="true"> <! <security:intercept- URL pattern="/**" <security:intercept- URL pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/> <! <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>Copy the code

A few notes on configuration files:

  1. Login-page: configure a customized authentication page.
  2. Login-processing-url: indicates the URL for authentication processing.
  3. Default-target-url: indicates the page to be redirected after authentication is successfully configured.
  4. Authentication-failure-url: indicates the page to which the authentication fails.
  5. Logout-url: configures the logout processing path.
  6. Logout-success-url: indicates the page that can be redirected after the logout is successfully configured.
  7. Username -parameter: configures the parameter name of the user account submitted from the front-end form to the background. The default value is username and this parameter is optional.
  8. Password-parameter: configures the parameter name for the front-end form to submit user credentials to the background. The default value is password.
  9. Security =” None “indicates that access to resources under this path is not intercepted (this differs from anonymous access configurations).

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

<! DOCTYPE HTML > < HTML lang="en"> <head> <meta charset="UTF-8"> <title> </title> </head> <! - introduction of style - > < link rel = "stylesheet" href = "https://unpkg.com/[email protected]/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: </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%;" > login < / el - button > < / el - form - item > < / el - form > < / div > < / el - the 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/[email protected]/dist/vue.js" > < / script > <! - the introduction of 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>Copy the code

Step 3, start Tomcat and accesshttp://127.0.0.1:8081/

We can see that we have been redirected to the login page. Next we fill in username and password and click login (defined in the previous configuration file)

We found that the results were not in line with our expectations. We got a 403 (no permissions) error message.

According to?

Let’s compare the login page we wrote to the one presented 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>Copy the code

By comparing the authentication page provided by the system with that written by us, we found that the following paragraph was added to the form

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

This means that we are missing a parameter that spring Security used to protect against CSRF attacks as described in the previous article. Obviously it is prevented by checking the token.

Explain how to prevent CSRF attacks by checking tokens:

Is simply a Daisy to offices you take silver handle affairs (acquisition system service) to Mr Wang before (it could be a system issue token of a special interface) for a token (token), you get silver take w give you tokens, the coffers steward, Mr Li old (a vehicle) to see your tokens to give you money, or you don’t give you the money. After you take the money, the token is taken away (token expiration strategy). Next time you want to take the money, you have to ask wang for a new token.

Next, let’s find Mr. Li to take the silver, oh, no, it’s to find Mr. Wang to take the chicken feathers…

Step 4: Configure the CSRF attack defense 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>Copy the code

Since spring Security’s CSRF token is stored in HttpSession, it is easy to obtain the CSRF token information in dynamic web pages.

<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. The 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>Copy the code

If you are using static web pages, such as front end separation, restful interface design, and stateless sessions, you may want to consider the following design:

The CSEF token is exposed, and the front end obtains the token through Ajax request and takes the token when requesting the service. Of course, the token exposed here should only be exposed internally, so that the static page gets the token before the page responds to the user.

Access token

​ 

The login

Static web page to get part of the backend code of the CSRF token

/** * @author [email protected] * @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){/** * Only 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; }}Copy the code

End

Sacrificing some design perfection in order to keep the system safe seems to me necessary. I believe there will be a better solution! The above are just some of my personal opinions. Different ideas are welcome.