zoukankan      html  css  js  c++  java
  • 手写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.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拦截器,功能也比较简单,但是这里的原理与常见的登录拦截功能是相通的,有兴趣的朋友可以在此基础上实现一个真正的登录拦截功能。

    作者:blayn
    出处:https://www.cnblogs.com/blayn/
    版权:本文版权归作者和博客园共有
    转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
  • 相关阅读:
    Linux文件查询笔记
    C语言学习和回顾
    hive的数据压缩
    进程线程那些事儿
    hive的数据存储格式
    hive的内置函数
    Hive自定义函数
    spark编译
    Impala的安装和使用
    数据库的读写分离
  • 原文地址:https://www.cnblogs.com/blayn/p/14771594.html
Copyright © 2011-2022 走看看