手写Spring MVC框架(二) 实现访问拦截功能
- 2021 年 5 月 20 日
- 筆記
- Spring MVC
前言
在上一篇文章中,我们手写了一个简单的mvc框架,今天我们要实现的功能点是:在Spring MVC框架基础上实现访问拦截功能。
先梳理一下需要实现的功能点:
- 搭建好Spring MVC基本框架;
- 定义注解@Security(有value属性,接收String数组),该注解用于添加在Controller类或者Handler方法上,表明哪些用户拥有访问该Handler方法的权限(注解配置用户名);
- 访问Handler时,用户名直接以参数名username紧跟在请求的url后面即可,比如//localhost:8080/demo/testSecurity?username=zhangsan;
- 程序要进行验证,有访问权限则放行,没有访问权限在页面上输出。
实现过程
闲话少说,直接来看代码。
0、项目依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="//maven.apache.org/POM/4.0.0" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hardy.edu</groupId> <artifactId>springmvc-demo</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>springmvc-demo Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>//www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <!--引入spring webmvc的依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.12.RELEASE</version> </dependency> <!-- //mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20140107</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
1、注解开发
Security注解:
package com.hardy.edu.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 使用@Target注解,使该注解作用在方法上 @Target(ElementType.METHOD) // 使用@Retention定义该注解在运行时有效 @Retention(RetentionPolicy.RUNTIME) public @interface Security { String[] value() default {}; }
2、拦截器开发
拦截器SecurityInterceptor:
package com.hardy.edu.interceptor; import com.hardy.edu.annotation.Security; import org.json.JSONObject; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class SecurityInterceptor implements HandlerInterceptor { /** * 重写preHandle方法 * 该方法会在handler方法业务逻辑执行之前执行 * 往往在这里完成权限校验工作 * @param request * @param response * @param handler * @return 返回值boolean代表是否放行,true代表放行,false代表中止 * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("SecurityInterceptor preHandle......"); // 从url中获取username的值 String username = request.getParameter("username"); HandlerMethod method = (HandlerMethod) handler; // 获取testSecurity方法上的@Security注解 Security annotation = method.getMethod().getAnnotation(Security.class); // 获取Security注解中所标记的username列表,只有这些username有权限成功访问 String[] value = annotation.value(); // 判断url中输入的username值是否在Security注解中所标记的username列表中 boolean isHavePermissionName = false; if(value != null){ for (int i = 0; i < value.length; i++) { if(username.equals(value[i])){ isHavePermissionName = true; break; } } } // isHavePermissionName为false, 则没有权限访问 if(!isHavePermissionName){ JSONObject jsonObject = new JSONObject(); jsonObject.append("error", "没有访问权限"); System.out.println("该用户没有访问权限!"); // 设置响应编码类型 response.setCharacterEncoding("UTF-8"); // 设置相应内容类型 response.setContentType("application/json;charset=utf-8"); PrintWriter out = null; try{ // 向浏览器输出error信息 out = response.getWriter(); out.append(jsonObject.toString()); }catch(IOException e){ e.printStackTrace(); }finally { if(out!=null){ out.close(); } } } return true; } /** * 会在handler方法业务逻辑执行之后尚未跳转页面时执行 * @param request * @param response * @param handler * @param modelAndView 封装了视图和数据,此时尚未跳转页面呢,你可以在这里针对返回的数据和视图信息进行修改 * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("SecurityInterceptor postHandle......"); } /** * 页面已经跳转渲染完毕之后执行 * @param request * @param response * @param handler * @param ex 可以在这里捕获异常 * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("SecurityInterceptor afterCompletion......"); } }
3、自定义类型转换器
日期转换器DateConverter:
package com.hardy.edu.converter; import org.springframework.core.convert.converter.Converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * @Author: HardyYao * @Date: 2021/5/11 * 自定义类型转换器 * S:source,源类型 * T:target:目标类型 */ public class DateConverter implements Converter<String, Date> { @Override public Date convert(String source) { // 完成字符串向日期的转换 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { Date parse = simpleDateFormat.parse(source); return parse; } catch (ParseException e) { e.printStackTrace(); } return null; } }
4、编写控制器
控制器DemoController:
package com.hardy.edu.controller; import com.hardy.edu.annotation.Security; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.text.SimpleDateFormat; import java.util.Date; /** * @Author: HardyYao * @Date: 2021/5/11 */ @Controller @RequestMapping("/demo") public class DemoController { @Security(value = {"hardy","zhangsan","lisi"}) @RequestMapping("/testSecurity") public ModelAndView testSecurity(HttpServletRequest request, HttpServletResponse response,HttpSession session) { String username = request.getParameter("username"); ModelAndView modelAndView = new ModelAndView(); Date date = new Date(); // 实现日期向字符串的转换 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); modelAndView.addObject("date", simpleDateFormat.format(date)); modelAndView.addObject("username",username); modelAndView.setViewName("success"); return modelAndView; } }
5、编写配置文件
web.xml:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "//java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <!--springmvc提供的针对post请求的编码过滤器--> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <!--配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换--> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 方式一:带后缀,比如*.action *.do *.aaa 该种方式比较精确、方便,在以前和现在企业中都有很大的使用比例 方式二:/ 不会拦截 .jsp,但是会拦截.html等静态资源(静态资源:除了servlet和jsp之外的js、css、png等) 为什么配置为/ 会拦截静态资源??? 因为tomcat容器中有一个web.xml(父),你的项目中也有一个web.xml(子),是一个继承关系 父web.xml中有一个DefaultServlet, url-pattern 是一个 / 此时我们自己的web.xml中也配置了一个 / ,覆写了父web.xml的配置 为什么不拦截.jsp呢? 因为父web.xml中有一个JspServlet,这个servlet拦截.jsp文件,而我们并没有覆写这个配置, 所以springmvc此时不拦截jsp,jsp的处理交给了tomcat 如何解决/拦截静态资源这件事? 方式三:/* 拦截所有,包括.jsp --> <!--拦截匹配规则的url请求,进入springmvc框架处理--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
springmvc.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="//www.springframework.org/schema/beans" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xmlns:context="//www.springframework.org/schema/context" xmlns:mvc="//www.springframework.org/schema/mvc" xsi:schemaLocation=" //www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd //www.springframework.org/schema/context //www.springframework.org/schema/context/spring-context.xsd //www.springframework.org/schema/mvc //www.springframework.org/schema/mvc/spring-mvc.xsd "> <!--开启controller扫描--> <context:component-scan base-package="com.hardy.edu.controller"/> <!--配置springmvc的视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 自动注册最合适的处理器映射器,处理器适配器(调用handler方法) --> <mvc:annotation-driven conversion-service="conversionServiceBean"/> <!--注册自定义类型转换器--> <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.hardy.edu.converter.DateConverter"></bean> </set> </property> </bean> <!--静态资源配置,方案一--> <!-- 原理:添加该标签配置之后,会在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler对象 这个对象如同一个检查人员,对进入DispatcherServlet的url请求进行过滤筛查,如果发现是一个静态资源请求 那么会把请求转由web应用服务器(tomcat)默认的DefaultServlet来处理,如果不是静态资源请求,那么继续由 SpringMVC框架处理 --> <!--<mvc:default-servlet-handler/>--> <!--静态资源配置,方案二,SpringMVC框架自己处理静态资源 mapping:约定的静态资源的url规则 location:指定的静态资源的存放位置 --> <mvc:resources location="classpath:/" mapping="/resources/**"/> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.hardy.edu.interceptor.SecurityInterceptor"></bean> </mvc:interceptor> </mvc:interceptors> </beans>
6、编写jsp页面
error.jsp:
<%@ page language="java" isELIgnored="false" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Insert title here</title> </head> <body> 异常信息: ${msg} </body> </html>
success.jsp:
<%@ page language="java" isELIgnored="false" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Insert title here</title> </head> <body> ${username}: 跳转成功!服务器时间:${date} </body> </html>
项目整体结构
项目运行结果
启动项目后输入地址进行访问,可以看到控制台输出以下信息:
访问://localhost:8080/demo/testSecurity?username=hardy
因为hardy在授权列表中,故可以访问成功。
下面访问://localhost:8080/demo/testSecurity?username=wangwu
因为wangwu不在授权列表中,故访问失败。
总结
今天我们在Spring MVC框架基础上实现了访问拦截功能,这里的核心代码是Security注解及Security拦截器,功能也比较简单,但是这里的原理与常见的登录拦截功能是相通的,有兴趣的朋友可以在此基础上实现一个真正的登录拦截功能。