手写Spring MVC框架(二) 实现访问拦截功能
前言
在上一篇文章中,我们手写了一个简单的mvc框架,今天我们要实现的功能点是:在Spring MVC框架基础上实现访问拦截功能。
先梳理一下需要实现的功能点:
- 搭建好Spring MVC基本框架;
- 定义注解@Security(有value属性,接收String数组),该注解用于添加在Controller类或者Handler方法上,表明哪些用户拥有访问该Handler方法的权限(注解配置用户名);
- 访问Handler时,用户名直接以参数名username紧跟在请求的url后面即可,比如http://localhost:8080/demo/testSecurity?username=zhangsan;
- 程序要进行验证,有访问权限则放行,没有访问权限在页面上输出。
实现过程
闲话少说,直接来看代码。
0、项目依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <groupId>com.hardy.edugroupId> <artifactId>springmvc-demoartifactId> <version>1.0-SNAPSHOTversion> <packaging>warpackaging> <name>springmvc-demo Maven Webappname> <url>http://www.example.comurl> <properties> <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> <maven.compiler.source>11maven.compiler.source> <maven.compiler.target>11maven.compiler.target> properties> <dependencies> <dependency> <groupId>org.springframeworkgroupId> <artifactId>spring-webmvcartifactId> <version>5.1.12.RELEASEversion> dependency> <dependency> <groupId>javax.servletgroupId> <artifactId>javax.servlet-apiartifactId> <version>3.1.0version> <scope>providedscope> dependency> <dependency> <groupId>org.jsongroupId> <artifactId>jsonartifactId> <version>20140107version> dependency> dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.mavengroupId> <artifactId>tomcat7-maven-pluginartifactId> <version>2.2version> <configuration> <port>8080port> <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{ @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"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Applicationdisplay-name>
<filter>
<filter-name>encodingfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
filter>
<filter>
<filter-name>hiddenHttpMethodFilterfilter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
<filter-name>encodingfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<filter-mapping>
<filter-name>hiddenHttpMethodFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
springmvc.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd "> <context:component-scan base-package="com.hardy.edu.controller"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> bean> <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> <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 heretitle> 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 heretitle> head> <body> ${username}: 跳转成功!服务器时间:${date} body> html>
项目整体结构
项目运行结果
启动项目后输入地址进行访问,可以看到控制台输出以下信息:
访问:http://localhost:8080/demo/testSecurity?username=hardy
因为hardy在授权列表中,故可以访问成功。
下面访问:http://localhost:8080/demo/testSecurity?username=wangwu
因为wangwu不在授权列表中,故访问失败。
总结
今天我们在Spring MVC框架基础上实现了访问拦截功能,这里的核心代码是Security注解及Security拦截器,功能也比较简单,但是这里的原理与常见的登录拦截功能是相通的,有兴趣的朋友可以在此基础上实现一个真正的登录拦截功能。