Java教程

Spring 系列 (3) - 在 Spring Boot 项目里实现基于拦截器的 Login 实例

本文主要是介绍Spring 系列 (3) - 在 Spring Boot 项目里实现基于拦截器的 Login 实例,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。

Spring Boot 同样提供了拦截器功能,Spring Boot 拦截器的详细说明参考 “Spring基础知识(30)- Spring Boot (十一)” 的 “2. 拦截器(Interceptor)”。

本文将结合实例讲讲如何在 Spring Boot 项目里使用 Thymeleaf、JQuery、Bootstrap 的基础上,开发基于基于拦截器的 Login 实例。

后续的实例,默认在使用 Thymeleaf、JQuery、Bootstrap 的基础上(这里称为 Spring Boot 基础项目)开发,文档里将不再说明和强调。


1. 开发环境

    Windows版本:Windows 10 Home (20H2)   
    IntelliJ IDEA (https://www.jetbrains.com/idea/download/):Community Edition for Windows 2020.1.4
    Apache Maven (https://maven.apache.org/):3.8.1

    注:Spring 开发环境的搭建,可以参考 “ Spring基础知识(1)- Spring简介、Spring体系结构和开发环境配置 ”。


2. 创建 Spring Boot 基础项目

    项目实例名称:SpringbootExample03

    创建步骤:

        (1) 创建 Maven 项目实例 SpringbootExample03;
        (2) Spring Boot Web 配置;
        (3) 导入 Thymeleaf 依赖包;
        (4) 配置静态资源(jQuery、Bootstrap、Images);
        
    具体操作请参考 “Spring 系列 (2) - 在 Spring Boot 项目里使用 Thymeleaf、JQuery+Bootstrap 和国际化” 里的项目实例 SpringbootExample02 (它基本等同于一个基础项目)。

    SpringbootExample03 和 SpringbootExample02 相比,SpringbootExample03 不包含国际化。

    为了避免代码混淆,所以本文创建了一个新的项目实例 SpringbootExample03,本文后面代码的添加和修改都在项目实例 SpringbootExample03 上进行。


3. 拦截器(Interceptor)

    1) 定义拦截器

        创建 src/main/java/com/example/interceptor/LoginInterceptor.java 文件

 1         package com.example.interceptor;
 2 
 3         import javax.servlet.http.HttpServletRequest;
 4         import javax.servlet.http.HttpServletResponse;
 5         import org.springframework.web.servlet.HandlerInterceptor;
 6         import org.springframework.web.servlet.ModelAndView;
 7 
 8         public class LoginInterceptor implements HandlerInterceptor {
 9 
10             @Override
11             public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
12                                     Object handler) throws Exception {
13                 Object loginUser = request.getSession().getAttribute("loginUser");
14                 if (loginUser == null) {
15                     //request.setAttribute("message", "No authentication, try login");
16                     //request.getRequestDispatcher("/login").forward(request, response);
17                     response.sendRedirect("/login");
18                     return false;
19                 } else {
20                     return true;
21                 }
22             }
23 
24             @Override
25             public void postHandle(HttpServletRequest request, HttpServletResponse response,
26                                 Object handler, ModelAndView modelAndView) throws Exception {
27                 System.out.println("LoginInterceptor -> postHandle(): modelAndView = " + modelAndView);
28             }
29 
30             @Override
31             public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
32                                         Object handler, Exception ex) throws Exception {
33                 System.out.println("LoginInterceptor -> afterCompletion()");
34             }
35         }


    2) 注册拦截器和指定拦截规则


        创建 src/main/java/com/example/config/ExtendMvcConfigurer.java 文件

 1         package com.example.config;
 2 
 3         import org.springframework.context.annotation.Configuration;
 4         import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 5         import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
 6         import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 7 
 8         import com.example.interceptor.LoginInterceptor;
 9 
10         @Configuration
11         public class ExtendMvcConfigurer implements WebMvcConfigurer {
12 
13             @Override
14             public void addViewControllers(ViewControllerRegistry registry) {
15 
16                 registry.addViewController("/").setViewName("home");
17                 registry.addViewController("/login.html").setViewName("login");
18                 registry.addViewController("/home.html").setViewName("home");
19             }
20 
21             @Override
22             public void addInterceptors(InterceptorRegistry registry) {
23 
24                 registry.addInterceptor(new LoginInterceptor())
25                         .addPathPatterns("/**") // 拦截所有请求,包括静态资源文件
26                         .excludePathPatterns("/login", "/lib/**", "/css/**",
27                                             "/images/**", "/js/**"); // 放行登录页,登陆操作,静态资源
28             }
29 
30         }


4. 视图和控制器

    1) 创建 src/main/resources/templates/common.html 文件

 1         <div th:fragment="header(var)">
 2             <meta charset="UTF-8">
 3             <title th:text="${var}">Title</title>
 4             <link rel="stylesheet" th:href="@{/lib/bootstrap-4.2.1-dist/css/bootstrap.min.css}" href="/lib/bootstrap-4.2.1-dist/css/bootstrap.min.css">
 5             <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}" src="/lib/jquery/jquery-3.6.0.min.js"></script>
 6             <script language="javascript" th:src="@{/lib/bootstrap-4.2.1-dist/js/bootstrap.min.js}" src="/lib/bootstrap-4.2.1-dist/js/bootstrap.min.js"></script>
 7         </div>
 8 
 9         <div th:fragment="content-header" class="container" id="content-header-id">
10             <nav class="navbar navbar-light bg-light">
11                 <a class="navbar-brand" href="#">
12                     <img th:src="@{/images/bootstrap-solid.svg}" src="/images/bootstrap-solid.svg" width="30" height="30" class="d-inline-block align-top" alt="">
13                     Thymeleaf Demo
14                 </a>
15 
16                 <a class="nav-link" th:href="@{/logout}" th:if="${session.loginUser} != null">Logout</a>
17             </nav>
18         </div>
19 
20         <div th:fragment="content-footer(var)" class="container" id="content-footer-id">
21             <p th:text="${var}">Content Footer</p>
22         </div>


    2) 创建 src/main/resources/templates/login.html 文件

 1         <!DOCTYPE html>
 2         <html lang="en" xmlns:th="http://www.thymeleaf.org">
 3         <head th:include="common::header(var='Login')">
 4         </head>
 5         <body>
 6         <div th:replace="common::content-header"></div>
 7 
 8         <div class="container" id="content" th:style="'min-height: 480px;'">
 9             <h4>Login</h4>
10 
11             <p>&nbsp;</p>
12             <div class="alert alert-info" role="alert" th:text="${message}" th:if ="${message} != null"></div>
13 
14             <form method="POST">
15                 <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
16                 <div class="form-group">
17                     <label for="username">Username</label>
18                     <input type="text" class="form-control" id="username" name="username" value="admin" />
19                 </div>
20                 <div class="form-group">
21                     <label for="password">Password</label>
22                     <input type="password" class="form-control" id="password" name="password" value="123456" />
23                 </div>
24                 <button type="submit" class="btn btn-primary">Submit</button>
25             </form>
26 
27         </div>
28 
29         <div th:replace="common::content-footer(var='Copyright &copy; 2020')"></div>
30 
31         <script type="text/javascript">
32                 $(document).ready(function(){
33                     console.log("jQuery is running");
34                 });
35             </script>
36         </body>
37         </html>


    3) 创建 src/main/resources/templates/home.html 文件

 1         <!DOCTYPE html>
 2         <html lang="en" xmlns:th="http://www.thymeleaf.org">
 3         <head th:include="common::header(var='Home')">
 4         </head>
 5         <body>
 6             <div th:replace="common::content-header"></div>
 7 
 8             <div class="container" id="content" th:style="'min-height: 480px;'">
 9                 <h4>Home Page</h4>
10 
11                 <p>&nbsp;</p>
12                 <div class="alert alert-info" role="alert" th:text="${message}" th:if ="${message} != null"></div>
13             </div>
14 
15             <div th:replace="common::content-footer(var='Copyright &copy; 2020')"></div>
16 
17             <script type="text/javascript">
18                 $(document).ready(function(){
19                     console.log("jQuery is running");
20                 });
21             </script>
22         </body>
23         </html>


    4) 创建 src/main/java/com/example/controller/UserController.java 文件

 1         package com.example.controller;
 2 
 3         import javax.servlet.http.HttpServletRequest;
 4         import javax.servlet.http.HttpSession;
 5 
 6         import org.springframework.ui.Model;
 7         import org.springframework.stereotype.Controller;
 8         import org.springframework.web.bind.annotation.RequestMapping;
 9 
10         @Controller
11         public class UserController {
12 
13             @RequestMapping("/login")
14             public String login(HttpServletRequest request, Model model) {
15 
16                 Object loginUser = request.getSession().getAttribute("loginUser");
17                 if (loginUser != null)
18                     return "redirect:/home";
19 
20                 if ("POST".equals(request.getMethod())) {
21                     String username = request.getParameter("username");
22                     String password = request.getParameter("password");
23 
24                     if ("admin".equals(username) &&
25                         "123456".equals(password)) {
26 
27                         request.getSession().setAttribute("loginUser", username);
28 
29                         request.getSession().setAttribute("message", "Welcome " + username);
30                         return "redirect:/home";
31                     }
32 
33                     model.addAttribute("message", "Invalid username or password");
34                 }
35 
36                 return "login";
37             }
38 
39             @RequestMapping("/logout")
40             public String login(HttpSession session) {
41                 session.removeAttribute("loginUser");
42                 return "redirect:/login";
43             }
44 
45         }


        访问:http://localhost:9090/


5. 防止 CSRF 攻击

    CSRF 全称是 Cross-site request forgery,跨站请求伪造。

    用户访问了带木马或类似脚本的网站页面后,如果电脑或手机的Web浏览器被控制,浏览器之前访问过的登录、注册、支付等网站接口就可能被非法利用。

    CSRF Token 就是用来防止网站的接口被非法利用,token 是一个随机字符串,在浏览器的页面里保存为 hidden 或 cookies 值,服务端保存在 Session(或 Redis),token 有时效性,保存在 session 的,就是 session 的 timeout 值。

    CSRF Token 原理,以登录页面为例,显示登录页面是一个 GET 操作,点击 "登录"按钮后,就是提交一个 POST 操作。

    在 GET 操作里一个随机 token1 隐藏在登录页面里 (服务器也保存着这个 token2),  POST 操作时要带上登录页面里隐藏的 token1,POST 接口会检查 token1 是不是超时了,是不是和 token2 相等,如果不满足条件,拒绝登录操作。

    Spring Boot 提供的 Spring security 包 (spring-security-web) 里包含了防止 CSRF 攻击的功能,具体配置如下。

    1) 导入 spring-security-web 依赖包

        访问 http://www.mvnrepository.com/,查询 spring-security-web

        修改 pom.xml:

 1             <project ... >
 2                 ...
 3                 <dependencies>
 4                     ...
 5 
 6                     <dependency>
 7                         <groupId>org.springframework.security</groupId>
 8                         <artifactId>spring-security-web</artifactId>
 9                     </dependency>
10 
11                     ...
12                 </dependencies>
13 
14                 ...
15             </project>


        在IDE中项目列表 -> SpringbootExample03 -> 点击鼠标右键 -> Maven -> Reload Project

    2) 配置 CsrfFilter
        
        修改 src/main/java/com/example/config/ExtendMvcConfigurer.java 文件

 1         package com.example.config;
 2 
 3         import org.springframework.context.annotation.Configuration;
 4         import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 5         import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
 6         import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 7 
 8         import org.springframework.context.annotation.Bean;
 9         import org.springframework.boot.web.servlet.FilterRegistrationBean;
10         import org.springframework.security.web.csrf.CsrfFilter;
11         import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
12 
13         import com.example.interceptor.LoginInterceptor;
14 
15         @Configuration
16         public class ExtendMvcConfigurer implements WebMvcConfigurer {
17 
18             @Bean
19             public FilterRegistrationBean csrfFilter() {
20                 FilterRegistrationBean registration = new FilterRegistrationBean();
21                 registration.setFilter(new CsrfFilter(new HttpSessionCsrfTokenRepository()));
22                 registration.addUrlPatterns("/*");
23                 return registration;
24             }
25 
26             ...
27 
28         }   


    3) 修改 src/main/resources/templates/login.html 文件,在 form 里添加一个 hidden

1         ...
2 
3         <form method="POST">
4             <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
5 
6             ...
7         </form>
8         
9         ...


        注:AJAX 处理 CSRF 时,使用 xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}") 。


    访问:http://localhost:9090/

这篇关于Spring 系列 (3) - 在 Spring Boot 项目里实现基于拦截器的 Login 实例的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!