SpringBoot回顾5-拦截器、跨域开启

    科技2025-10-20  7

    SpringBoot回顾5-拦截器、跨域CORS开启

    本篇写一下在SpringBoot中拦截器功能的实现,再配合一个登陆拦截的demo。最后再讲讲什么是CORS。

    SpringBoot中的拦截器

    在SpringBoot1.5之前,都需要重写WebMvcConfigurerAdapter来添加自定义拦截器(已经@Deprecated了)

    在SpringBoot2.0后,推荐实现WebMvcConfigurer或者继承WebMvcConfigurationSupport来实现。

    WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式代替传统的xml配置文件形式来进行对框架的个性化配置,可以自定义一些Handler、Interceptor、ViewResolver、MessageConverter等。

    新建一个SpringBoot项目,我们在config包下创建自己的MvcConfig,我们来看看接口中有哪些方法可以实现

    1、实现WebMvcConfigurer接口

    乍一看有这么多可以实现的方法,我们看看常用的有哪些

    常用的方法

    /* 拦截器配置 */ default void addInterceptors(InterceptorRegistry registry) { } /* 视图跳转控制器 */ default void addViewControllers(ViewControllerRegistry registry) { } /** *静态资源处理 **/ default void addResourceHandlers(ResourceHandlerRegistry registry) { } /* 默认静态资源处理器 */ default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { } /** * 这里配置视图解析器 **/ default void configureViewResolvers(ViewResolverRegistry registry) { } /* 配置内容裁决的一些选项*/ default void configureContentNegotiation(ContentNegotiationConfigurer configurer) { } /** 解决跨域问题 **/ default void addViewControllers(ViewControllerRegistry registry) { }

    我们知道在SpringBoot项目中的静态资源都是放在resources下,可以写一个静态资源处理的配置方法

    静态资源不拦截

    public class MyMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/***") .addResourceLocations("classpath:/static/"); } }

    这样设置的话就表示静态资源路径(/static)下的访问不会被拦截

    那请求路径拦截呢?

    注意这里也自己先写好拦截器,才能注册生效

    @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginHandlerInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/index.html","/","/user/login","/css/*","/js/**","/img/**"); }

    自定义登录拦截器

    package com.feng.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * <h3>springboot-interceptor-cors-test</h3> * <p></p> * * @author : Nicer_feng * @date : 2020-10-08 10:04 **/ public class LoginHandlerInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //登陆成功之后应该有用户的session Object loginUser = request.getSession().getAttribute("loginUser"); if(loginUser==null) {//没有登陆 request.setAttribute("msg","请先登录"); request.getRequestDispatcher("/index.html").forward(request, response); return false; }else { return true; } } }

    默认访问路径

    @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("").setViewName("index"); }

    这里表示我们只需要输入 http://localhost:8080/即可访问到index.html

    登陆拦截demo实现

    先看一下项目一览

    先编写一个controller类

    LoginController.java

    package com.feng.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.thymeleaf.util.StringUtils; import javax.servlet.http.HttpSession; import java.util.Map; @Controller public class LoginController { @RequestMapping("login") public String index() { return "login"; } @PostMapping(value="/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, Map<String,Object> map, HttpSession session){ //验证用户名和密码,输入正确,跳转到dashboard if(!StringUtils.isEmpty(username)&&"123".equals(password)){ session.setAttribute("userName",username); System.out.println("----" + username); map.put("age",30); return "redirect:/dashboard"; } else //输入错误,清空session,提示用户名密码错误 { session.invalidate(); map.put("msg","用户名密码错误"); return "login"; } } @RequestMapping("dashboard") public String goMain(Map<String,Object> map) { map.put("name","冯半仙"); map.put("age",22); map.put("sex","男"); return "dashboard"; } }

    发送post请求,代替了RequestMapping(value="/login",method=“post”)

    跳转页面这里用了 return “redirect:/dashboard”;会直接调用controller,

    return "dashboard"只会访问目录下的文件

    我们简单的写一个login页面

    login.html

    <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form th:action="@{/login}" method="post"> <p style="color:red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p> username<input type="text" name="username"> password<input type="password" name="password"> <button type="submit">login</button> </form> </body> </html>

    这里面用了thymeleaf模板,如果后台校验后密码不正确,th:text=" m s g " t h : i f = " {msg}" th:if=" msg"th:if="{not #strings.isEmpty(msg)}会存在值,则会生效。

    action配置为th:action="@{/login}" method=“post”,告诉模板跳转的post请求是/login

    很简单的前端页面,几乎没有美化,先看能不能用,等下再美化

    再写一个登陆成功返回的页面

    dashborad.html

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>注册成功</h1> 姓名:<spn th:text="${name}"></spn> 年龄:<spn th:text="${age}"></spn> 性别:<spn th:text="${sex}"></spn> </body> </html>

    此时我们是没配置拦截器的,跑起来以后发现在地址栏直接输入http://localhost:8080/dashboard也能直接访问后台地址,显然是不允许的。所以我们需要配置拦截器,当用户名和密码不正确时,页面跳回登录页面,并显示无权限访问

    添加HandlerInterceptor拦截器

    在com.feng下新建component包,取名LoginHandlerInterceptor实现HandlerInterceptor接口,并实现三个接口,全部选上

    可以看到里面有三个方法,

    preHandle 在请求处理之前进行调用(Controller方法调用之前)

    postHandle 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)

    afterCompletion 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行 (主要是用于进行资源清理工作)

    LoginHandlerInterceptor.java

    package com.feng.component; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class LoginHandlerInterceptor implements HandlerInterceptor { @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { Object user = request.getSession().getAttribute("userName"); System.out.println("afterCompletion----" + user + " ::: " + request.getRequestURL()); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { Object user = request.getSession().getAttribute("userName"); System.out.println("postHandle----" + user + " ::: " + request.getRequestURL()); } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("userName"); System.out.println("preHandle----" + user + " ::: " + request.getRequestURL()); if (user == null) { request.setAttribute("msg","无权限请先登录"); // 获取request返回页面到登录页 request.getRequestDispatcher("/index.html").forward(request, response); return false; } return true; } }

    我们的前置方法先去从session里看userName有没有值(我们前面在controller中已经把userName存入session中),如果没有值就强行返回到登录页面,并把未登录警告封装到msg中传给前端

    最后是配置自己的拦截器

    MyWebMvcConfigurer.java

    package com.feng.component; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class MyWebMvcConfigurer implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/index.html").setViewName("login"); //设置login为index下的视图解析器,输入localhost:8080/index仍然跳转login } @Override public void addInterceptors(InterceptorRegistry registry) { // "/**"过滤所有请求,后面的excl内表示放行的请求 registry.addInterceptor(new LoginHandlerInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/login","/index.html") .excludePathPatterns("/assets/**"); } }

    运行

    可以看到除了首页,我们那儿也去不了,除非登录成功

    优化前端页面

    是不是觉得界面太丑了,我们可以用Bootstrap来优化前端页面

    我们把login页面替换为如下

    login.html

    <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; 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>Title</title> <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <link href="assets/css/signin.css" rel="stylesheet"> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body class="text-center"> <form class="form-signin" th:action="@{/login}" method="post"> <img class="mb-4" src="assets/img/bootstrap-solid.svg" alt="" width="72" height="72"> <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1> <p style="color:red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p> <label class="sr-only" >Username</label> <input type="text" name="username" class="form-control" placeholder="Username" required="" autofocus=""> <label class="sr-only" >Password</label> <input type="password" name="password" class="form-control" placeholder="Password" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"/> Remember me </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit" >Sign in</button> <p class="mt-5 mb-3 text-muted">© 2019-2020</p> </form> </body> </html>

    还有一个signin.css

    html, body { height: 100%; } body { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; padding-top: 40px; padding-bottom: 40px; background-color: #f5f5f5; } .form-signin { width: 100%; max-width: 330px; padding: 15px; margin: auto; } .form-signin .checkbox { font-weight: 400; } .form-signin .form-control { position: relative; box-sizing: border-box; height: auto; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; } .form-signin input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-signin input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; }

    那个图标的话自己随便放个图就行

    效果如下

    就完事儿了,下面讲讲跨域

    跨域

    首先说明,跨域问题主要存在浏览器端,在我们后台java是不存在的问题。跨域的请求也能发出去,服务器也能收到并返回正确结果,但是结果被浏览拦截了

    之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。换句话说,浏览器安全的基石是同源策略。

    在SpringBoot中解决跨域我们主要是用Cors来解决

    什么是Cors?

    CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。它通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告诉客户端跨域的限制,如果浏览器支持CORS、并且判断Origin通过的话,就会允许XMLHttpRequest发起跨域请求。

    测试

    我们创建两个SpringBoot项目

    两个项目中都添加web和thymeleaf依赖

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

    在provider项目的配置类里端口配置为8080

    server.port=8080

    而consumer的配置为8081

    server.port=8081

    在provider项目下的resources/templates目录下创建index.html

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Cors跨域请求</title> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script> </head> <body> <input type="button" onclick="getRequest()" value="GET请求"> <input type="button" onclick="postRequest()" value="POST请求"> <script> function getRequest() { $.get('http://localhost:8081/index', function (res) { $('body').html(res); }) } function postRequest() { $.post('http://localhost:8081/index', function (res) { $('body').html(res); }) } </script> </body> </html>

    然后在consumer项目下创建一个controller层

    新建

    IndexController.java

    package com.feng.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; /** * <h3>springboot-cors-consumer</h3> * <p></p> * * @author : Nicer_feng * @date : 2020-10-08 15:36 **/ @RestController public class IndexController { @GetMapping("index") public String gindex() { return "get hello;"; } @PostMapping("index") public String pindex() { return "post hello;"; } }

    第一次运行

    两个项目都跑起来,我们点击get post请求后发现报错

    报错

    可以看到报错为

    Access to XMLHttpRequest at 'http://localhost:8081/index' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

    翻译一下大概意思是说我们从8080端口访问8081收到了CORS的限制,没有开启跨域

    开启跨域

    我们只需要在consumer项目中的controller上对应的映射路径加上@CrossOrigin(value = “http://localhost:8080”),表示允许从8080端口访问即可,加入后重启consumer项目

    第二次运行

    可以看到成功访问到8081中的项目资源

    全局配置

    还记得博客刚开始中的WebMvcConfigurer接口里有个眼熟的方法吗

    没错呀,我们也可以通过项目配置,为整个项目开启跨域,咋整?也实现这个方法,新建一个CorsConfig类

    CorsConfig.java

    package com.feng.config; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * <h3>springboot-cors-consumer</h3> * <p></p> * * @author : Nicer_feng * @date : 2020-10-08 16:03 **/ public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { //所有请求都会处理跨域 registry.addMapping("/**") // 允许谁访问 .allowedOrigins("http://localhost:8080") // 允许通过的请求数 .allowedMethods("*") // 允许的请求头 .allowedHeaders("*"); } }

    我们把刚才controller上配置的@CrossOrigin注释掉,重启服务器

    第三次运行

    收工

    Processed: 0.014, SQL: 8