前言
在上一篇文章中,我們手寫了一個簡單的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.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>http://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> <!-- https://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" "http://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="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 "> <!--開啟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>
專案整體結構
專案執行結果
啟動專案後輸入地址進行訪問,可以看到控制檯輸出以下資訊:
訪問:http://localhost:8080/demo/testSecurity?username=hardy
因為hardy在授權列表中,故可以訪問成功。
下面訪問:http://localhost:8080/demo/testSecurity?username=wangwu
因為wangwu不在授權列表中,故訪問失敗。
總結
今天我們在Spring MVC框架基礎上實現了訪問攔截功能,這裡的核心程式碼是Security註解及Security攔截器,功能也比較簡單,但是這裡的原理與常見的登入攔截功能是相通的,有興趣的朋友可以在此基礎上實現一個真正的登入攔截功能。