SpringBoot拦截器

Interceptor拦截器

  • 1. SpringMVC执行流程
  • 2. 拦截器
    • 2.1 作用位置
    • 2.2 HandlerInterceptor接口与拦截器实现
      • 2.2.1 HandlerInterceptor拦截器方法调用时机
      • 2.2.2 通过实现HandlerInterceptor接口定义拦截器
      • 2.2.3 WebMvcConfigurer接口与拦截器注册
        • 2.2.3.1 WebMvcConfigurer接口介绍
        • 2.2.3.2 通过实现WebMvcConfigurer接口注册拦截器
        • 2.2.3.3 通过实现WebMvcConfigurer接口配置跨域访问
    • 2.3 HandlerInterceptor接口如何产生作用?
      • 2.3.1 HandlerExecutionChain执行链对象与HandlerInterceptor拦截器
      • 2.3.2 HandlerInterceptor拦截器的接口方法调用
      • 2.3.3 如何获取HandlerExecutionChain执行链对象?
      • 2.3.4 总结
  • 3. 拦截器
    • 3.1 Spring+Spring MVC拦截器实现
      • 3.1.1 案例:基于token进行接口访问拦截
      • 3.1.2 引入jwt依赖
      • 3.1.3 编写jwt工具类
      • 3.1.4 使用Jwt工具类
      • 3.1.5 定义拦截器
      • 3.1.6 配置拦截器
      • 3.1.7 前端请求头添加token信息
    • 3.1 SpringBoot 拦截器实现

    Interceptor拦截器的作用类似于Filter过滤器,例如:可以用来拦截用户请求,并作相应的拦截处理。不同之处在于:过滤器依赖Servlet容器,可以拦截用户的全部请求;拦截器是SpringMVC的,它不依赖于servlet容器,由Spring容器进行初始化,可以通过相应的配置,拦截更细粒度的一组请求。

1. SpringMVC执行流程

    拦截器是依赖于SpringMVC的,SpringMVC框架是高度可配置的,无论其视图技术(如 JSP、FreeMarker、Thymeleaf等)选择何种,SpringMVC的执行流程图大抵是相同的。

SpringBoot拦截器_第1张图片

    执行流程为:

        [1] 用户触发某个请求路径,通过浏览器发起一个HTTP Request请求,该请求会被提交至DispatcherServlet前端控制器;
        [2] 由DispatcherServlet前端控制器请求一个或者多个HandlerMapping(处理器映射器),并返回一个Handler执行链(HandlerExecutionChain);
        [3] DispatcherServlet前端控制器将HandlerMapping处理器映射器返回的执行链中包含的Handler信息发送给HandlerAdapter处理器适配器;
        [4] HandlerAdapter处理器适配器根据Handler信息找到并执行相应的Handler处理器(即:项目中的Controller层中的控制器);
        [5] Handler处理器执行完毕后,会返回给HandlerAdapter处理器适配器一个ModelAndView对象(SpringMVC的底层对象,封装了Model数据模型和View视图信息);
        [6] HandlerAdapter处理器适配器接收到ModelAndView对象后,将其返回给DispatcherServlet前端控制器;
        [7] DispatcherServlet前端控制器接收到ModelAndView对象后,回请求ViewResolver视图解析器对视图进行解析;
        [8] ViewResolver视图解析器根据View匹配到相应的视图结果,并返回给DispatcherServlet前端控制器;
        [9] DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
        [10] 视图会被响应给浏览器客户端,并进行渲染。

2. 拦截器

2.1 作用位置

SpringBoot拦截器_第2张图片

    拦截器主要用于拦截用户请求并作相应的处理,那么其拦截位置必定是在HTTP请求到达DispatcherServlet前端控制器之后、将处理结果响应给浏览器之前。
    查看DispatcherServlet前端控制器的继承结构,会看到它其实是HttpServlet的一个子类,其职责如下:
Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities.
是http请求处理器/控制器的中央调度器,例如:Web UI控制器或者HTTP远程服务的出口。用于分发web请求到已经注册过的处理器进行处理,并提供方便的映射和异常处理机制。
SpringBoot拦截器_第3张图片

2.2 HandlerInterceptor接口与拦截器实现

2.2.1 HandlerInterceptor拦截器方法调用时机

    可以通过实现HandlerInterceptor接口、实现HandlerInterceptor接口自接口、继承HandlerInterceptor接口子类定义一个SpringMVC拦截器。
SpringBoot拦截器_第4张图片
    HandlerInterceptor接口中定义了如下三个default方法,实现子类通过重写相应的方法,在特定的执行流程发生时,执行用户请求的拦截动作,并完成相应的处理操作。

/*Intercept the execution of a handler. Called after HandlerMapping determined an
 appropriate handler object, but before HandlerAdapter invokes the handler.
 DispatcherServlet processes a handler in an execution chain, consisting of any number
 of interceptors, with the handler itself at the end. With this method, each interceptor
 can post-process an execution,getting applied in inverse order of the execution chain.
*/
|拦截时机:HandlerMapping处理器映射器匹配到对应的Handler处理器对象,但是还未被HandlerAdapter处理器适配器调用之前发生作用。
|处理执行链中包含的Handler处理器对象。
|作用方式:通过preHandle()这个方法,每一个拦截器对象可以对request代表的HTTP请求对象进行拦截处理,并响应一个自定义HTTP响应消息给浏览器客户端。
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		//空方法
		return true;
}

/*
 Intercept the execution of a handler. Called after HandlerAdapter actually
 invoked the handler, but before the DispatcherServlet renders the view.
 Can expose additional model objects to the view via the given ModelAndView.

*/
|拦截时机:在HandlerAdapter 处理器适配器调用handler处理器(即Controller控制器)之后,在DispatcherServlet前端控制器借助ViewResolver视图解析器获取到ModelAndView对象后,但是View视图还未被渲染之前。此时:最终的View视图还未被响应给浏览器客户端。
|作用方式:逆序处理执行链对应的请求对象
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {
			//空方法
}

/*
Callback after completion of request processing, that is, after rendering
the view. Will be called on any outcome of handler execution, thus allows
for proper resource cleanup.
*/
|拦截时机:在HTTP请求处理完毕,即:View视图被渲染给客户端浏览器之后被调用
|作用方式:在执行链对象内部调用,允许做一些适当的资源清理操作。
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}


2.2.2 通过实现HandlerInterceptor接口定义拦截器

    通过实现HandlerInterceptor接口中的3个default方法,可以自定义拦截器。
    在SpringBoot中,HandlerInterceptor 拦截器接口对象交由Spring容器管理,因此需要通过@Component注解进行标注。定义一个拦截器方式如下:

//注解-将拦截器交由Spring容器管理
@Component
public class SelfInterceptor implements HandlerInterceptor {
    //methods
    /**
     * 关于HandlerInterceptor接口
     *     实现此接口的子类,可以自定义执行链的处理.
     *     当前Web应用可以注册任意数量的自定义拦截器,每一个拦截器都可以用于处理一组特定的请求.
     *     这种方式实现的拦截器,无需改动处理器(控制器层)的具体实现.
     *   Workflow interface that allows for customized handler execution chains.
     *   Applications can register any number of existing or custom interceptors
     *   for certain groups of handlers, to add common preprocessing behavior
     *   without needing to modify each handler implementation.
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("接收请求路径时处理:"+request.getRequestURI());
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("响应请求路径前处理:"+request.getRequestURI());
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("响应请求路径后处理:"+request.getRequestURI());
    }
}

2.2.3 WebMvcConfigurer接口与拦截器注册

    通过实现HandlerInterceptor接口,配合@Component固然可以自定义一个拦截器,并将其交由Spring容器进行管理,但是并未告诉Spring容器何时调用这个拦截器对象、也没有明确这个拦截器对象用来拦截或者不拦截哪些HTTP请求。
    这就需要通过实现WebMvcConfigurer接口,配合@Configuration注解,对拦截器进行注册。

2.2.3.1 WebMvcConfigurer接口介绍

    实现WebMvcConfigurer接口的子类,可以基于@EnableWebMvc注解,通过重写接口中的default方法,来修改SpringMVC的默认配置。
    这些默认配置例如:
    PathMatch路径匹配、AsyncSupport-异步请求支持、
    DefaultServletHandling-处理Servlet容器未处理的所有url请求、
    addFormatters-Converter和Formatter转换器定制、
    addCorsMappings-全局跨域请求处理
    configureViewResolvers-视图解析器定制、
    getValidator-验证器、
    addInterceptors-添加拦截器等等,
    每一类配置都对应着一个default方法,因此:根据实际需求,重写对应的default方法即可

2.2.3.2 通过实现WebMvcConfigurer接口注册拦截器
package com.xwd.config;

import com.xwd.interceptor.SelfInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

/**
 * @ClassName InterceptorConfig-拦截器配置
 * @Description: com.xwd.config
 * @Auther: xiwd
 * @version: 1.0
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    //properties
    @Autowired
    private SelfInterceptor selfInterceptor;

    //methods
    /**
     * 注册拦截器
     * 重写addInterceptors()方法
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /* public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor)
        *  参数类型HandlerInterceptor-将Spring IOC管理的自定义拦截器实例放入即可
        * /** 表示拦截所有路径(多重子路径)
        * */
        InterceptorRegistration interceptorRegistration = registry.addInterceptor(selfInterceptor).addPathPatterns("/**").excludePathPatterns("/student/test");
    }
}

2.2.3.3 通过实现WebMvcConfigurer接口配置跨域访问
package com.xwd.config;

import com.xwd.interceptor.SelfInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

/**
 * @ClassName InterceptorConfig-拦截器配置
 * @Description: com.xwd.config
 * @Auther: xiwd
 * @version: 1.0
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    //methods
    /**
     * 跨域访问配置
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry){
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .maxAge(3600)
                .allowedHeaders("*");
    }
}

2.3 HandlerInterceptor接口如何产生作用?

    HandlerInterceptor接口中的3个方法如何产生作用?
    先看一下HandlerExecutionChain执行链类。

2.3.1 HandlerExecutionChain执行链对象与HandlerInterceptor拦截器

Handler execution chain, consisting of handler object and any handler interceptors.Returned by HandlerMapping’s getHandler method.
    HandlerExecutionChain执行链对象与拦截器关系如下:
    HandlerExecutionChain执行链对象是由一个Handler处理器对象和多个Interceptor拦截器对象组成的。
SpringBoot拦截器_第5张图片

2.3.2 HandlerInterceptor拦截器的接口方法调用

    此外,HandlerInterceptor拦截器的接口方法是通过HandlerExecutionChain执行链对象内部的三个成员方法所调用的,这3个成员方法分别如下:


[1] boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response)
[2] void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
[3] void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)。

    在这三个方法内部,通过遍历HandlerExecutionChain执行链对象内部持有的多个拦截器,分别对preHandle、postHandle、afterCompletion方法进行调用。

/**
	 * Apply preHandle methods of registered interceptors.
	 * @return {@code true} if the execution chain should proceed with the
	 * next interceptor or the handler itself. Else, DispatcherServlet assumes
	 * that this interceptor has already dealt with the response itself.
	 */
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor = interceptors[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}

	/**
	 * Apply postHandle methods of registered interceptors.
	 */
	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
			throws Exception {

		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = interceptors.length - 1; i >= 0; i--) {
				HandlerInterceptor interceptor = interceptors[i];
				interceptor.postHandle(request, response, this.handler, mv);
			}
		}
	}

	/**
	 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
	 * Will just invoke afterCompletion for all interceptors whose preHandle invocation
	 * has successfully completed and returned true.
	 */
	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
			throws Exception {

		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = this.interceptorIndex; i >= 0; i--) {
				HandlerInterceptor interceptor = interceptors[i];
				try {
					interceptor.afterCompletion(request, response, this.handler, ex);
				}
				catch (Throwable ex2) {
					logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
				}
			}
		}
	}

2.3.3 如何获取HandlerExecutionChain执行链对象?

    那么,HandlerExecutionChain执行链对象是如何从HandlerMapping处理器映射器返回到DispatcherServlet前端控制器的呢?
    在解释之前,先看一下DispatcherServlet前端控制器是如何分发HTTP请求的
    SpringMVC的前端控制器DispatcherServlet类中包含一个doDispatch()方法,该方法可以负责HTTP请求到Handler处理器的实际调度,所有的HTTP方法都会被此方法处理,并由HandlerAdapters 处理器适配器或者handler处理器自己决定可接收的HTTP请求。

    代码如下:当前仅需要关注如下几点,
    [1] 获取HandlerExecutionChain 执行链对象的方法,DispatcherServlet的doDispatch()方法中,会调用 另一个成员方法getHandler(processedRequest)方法,其中:processedRequest就是HttpServletRequest 对象;
    [2] 成员方法getHandler()方法内部通过一个for循环,来遍历内部持有的HandlerMapping列表,调用HandlerMapping处理器映射器的getHandler()方法获取一个HandlerExecutionChain执行链对象,并将其返回
    [3] HandlerMapping处理器映射器的的getHandler()方法作用: Return a handler and any interceptors for this request.将当前HTTP请求封装过来的HttpServletRequest对象作为参数接收,找到它对应的处理器或者拦截器对象,将其返回。

    doDispatch()方法大致的执行流程已给出注释。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		//HttpServletRequest对象
		HttpServletRequest processedRequest = request;
		//执行链对象
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;
		
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				//调用getHandler()方法,内部借助HandlerMappings处理器映射器对象获取执行链对象
				mappedHandler = getHandler(processedRequest);
				//如果执行链为空-调用noHandlerFound()
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// mappedHandler.getHandler()-获取Handler处理器对象
				// 将Handler处理器对象交由HandlerAdapter处理器适配器
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				//获取HTTP请求方式,例如:GET, POST, or PUT等
				String method = request.getMethod();
				//根据请求方式来做进一步处理
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					//为当前的request, response对——创建ServletWebRequest对象
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				
				//判断执行链对象是否需要处理下一个Handler或者Interceptor
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				//通过HandlerAdapter 处理器适配器调用handle()方法,实际调用handler处理器(controller控制器),对request请求进行处理,并由Handler处理器返回一个ModelAndView对象
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				
				//将ModelAndView对象应用到当前request对象上
				applyDefaultViewName(processedRequest, mv);
				//通过执行链调用applyPostHandle,内部调用HandlerInterceptor接口的postHandle方法
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

	/**
	 * Return the HandlerExecutionChain for this request.
	 * 

Tries all handler mappings in order. * @param request current HTTP request * @return the HandlerExecutionChain, or {@code null} if no handler could be found */ @Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }

2.3.4 总结

    DispatcherServlet前端控制器接收到HandlerMapping处理器映射器返回的一个执行链HandlerExecutionChain对象(该对象是在前端控制器的doPost()->getHandler()方法中,通过遍历HandlerMapping列表,由列表元素调用getHandler()方法返回),HandlerExecutionChain包含多个Handler处理器对象和一组拦截器interceptor对象。
DispatcherServlet在获取到执行链对象之后,就可以通过执行链中的三个成员方法:applyPreHandle、applyPostHandle、triggerAfterCompletion,对拦截器对象(即:HandlerInterceptor接口实例)被重写的接口方法发起调用,对HTTP请求进行拦截操作。而接口方法被调用的时机,在2.2中已经介绍过。

3. 拦截器

    基于以上认知,可以知道:HandlerInterceptor拦截器接口隶属于SpringMVC的一部分,无论是在基于Spring,还是Spring Boot搭建的web项目中,都可以进行配置使用。
    其使用流程如下:
    [1] 通过实现HandlerInterceptor接口定义拦截器;
    [2] 将HandlerInterceptor拦截器交由Spring容器管理;
    [3] 注册HandlerInterceptor拦截器,明确拦截器可以拦截哪一类HTTP请求。

3.1 Spring+Spring MVC拦截器实现

    以Spring+Spring MVC项目为例,进行拦截器实现。

3.1.1 案例:基于token进行接口访问拦截

    在前后端分离的项目中,例如前端通过Vue技术栈+Axios开发,而Axios默认是无状态的,后端无法通过HttpSession记录用户登陆状态,从而判断用户是否可以执行某些请求操作。此时,就需要通过使用一些特殊方法记录用户登陆状态,而jwt就是一种实现方式。
    jwt官方地址:点击此处。
    JWT,全称为:JSON Web Token,基于JSON对象,定义了一种轻量级的、自包含(self-contained)方式的安全信息传输方式。通过数字签名,这种JSON信息可以进行验证,从而用于实现权限控制(Authorization)或者信息交换(Information Exchange)。
    JSON Web Token由三部分组成:header-头部、payload-载荷、Signature-签名,各部分之间通过.点进行分隔,例如:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDg3NTEyNDcsImVtYWlsIjoiWHdkOTQ5MjY0QDE2My5jb20ifQ.F_zCAasNiBdbVaVg3eKo3I5NIFTzh_mErDeJOhQhP5o
    更多介绍可以查看官网介绍。
    基于token进行接口访问拦截,需要通过定义拦截器,利用jwt(JSON Web Token)技术,基于token进行用户后端接口访问权限的认证。
    对于没有携带token值的HTTP请求,默认拒绝该请求;对于携带token值的HTTP请求,接受并进行相应的处理操作。

3.1.2 引入jwt依赖

    先在pom.xml中引入jwt依赖。

  
    <dependency>
      <groupId>com.auth0groupId>
      <artifactId>java-jwtartifactId>
      <version>3.18.3version>
    dependency>

3.1.3 编写jwt工具类

    先在pom.xml中引入jwt依赖。

package com.xwd.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName JWTUtil
 * @Description: com.xwd.util
 * @Auther: xiwd
 * @version: 1.0
 */
public class JWTUtil {
    //properties
    private static final String SIGN="eyJwc3dkIjoiWQiOiJyb290In0!@ds";
    //methods
    /**
     * 根据传入信息生成Token
     * header.payload.Signature
     * @param mapinfo 携带用户信息的map集合
     */
    public static String genereteToken(Map<String,String> mapinfo){
        Calendar instance = Calendar.getInstance();
        //有效期为3小时
        instance.add(Calendar.MINUTE,60*3);
        //map用户存储claim——声明的信息
        Map<String,Object> map=new HashMap<>();
        //生成token
        JWTCreator.Builder builder = JWT.create();
        //添加payload
        mapinfo.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        //指定令牌过期时间
        builder.withExpiresAt(instance.getTime());
        //指定算法-生成token
        String token = builder.sign(Algorithm.HMAC256(SIGN));
        return token;
    }

    /**
     * 验证token的合法性
     * @param token token令牌值
     * throw 抛出异常-表示验证失败
     */
    public synchronized static void decodeToken(String token){
        JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
    }

    /**
     * 获取token信息的方法
     * @param token token令牌值
     * @return DecodedJWT对象
     * throw 抛出异常-表示验证失败
     */
    public synchronized static DecodedJWT getTokenInfo(String token){
        DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
        return decodedJWT;
    }
}

3.1.4 使用Jwt工具类

    在用户登录时,自动生成一个token值信息,将其返回给前端,保存到localStorage中,之后每次向后端发起请求时,将其添加到请求头中,以便于后端验证。

@RequestMapping(value = "/POST/login",method = RequestMethod.POST)
    @ResponseBody
    public Map<String, Object> login(@RequestParam(value = "uname")String username,
                                     @RequestParam(value = "pswd")String password){
        System.out.println("login");
        Map<String,Object> resMap=new HashMap<>();
        //判断用户名和密码是否为空
        if (StrUtil.isEmpty(username)||StrUtil.isEmpty(password)){
            resMap.put("state",0);
            resMap.put("msg","登陆失败-信息填写不完整");
            return resMap;
        }
        //查询符合条件的用户是否存在
        User user = userService.selectUser(username, password);
        if(user==null){
            resMap.put("state",-1);
            resMap.put("msg","登陆失败-账户或密码错误");
            return resMap;
        }
        //判断当前账户是否可用
        if (user.getState()!=0){
            //登录成功,创建Token值
            HashMap<String, String> map = new HashMap<>();
            map.put("email",username);
            //调用jwt工具类,生成token
            String token = JWTUtil.genereteToken(map);
            resMap.put("state",1);
            resMap.put("msg","登陆成功");
            resMap.put("token",token);
            resMap.put("user",username);
            return resMap;
        }else {
            //state==0,表示[账户禁用]
            resMap.put("state",-2);
            resMap.put("msg","登陆失败,账户不存在被禁用!");
            return resMap;
        }
    }

3.1.5 定义拦截器

    通过实现HandlerInterceptor 接口,自定义拦截器。

package com.xwd.interceptor;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xwd.util.JWTUtil;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExecutionChain;
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.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName JWTInterceptor
 * @Description: com.xwd.interceptor
 * @Auther: xiwd
 * @version: 1.0
 */
public class JWTInterceptor implements HandlerInterceptor {
    //methods
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取HttpServletRequest请求头中携带的token信息
        String token = request.getHeader("token");
        Map<String,Object> infoMap = new HashMap<>();
        try{
            //token验证
            DecodedJWT tokenInfo = JWTUtil.getTokenInfo(token);
            //添加返回信息
            infoMap.put("state",true);
            infoMap.put("msg","验证成功");
            return true;
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            infoMap.put("msg","无效签名");
            infoMap.put("state",false);
        }catch (TokenExpiredException e){
            infoMap.put("msg","token已过期");
            infoMap.put("state",false);
        }catch (AlgorithmMismatchException e){
            infoMap.put("msg","算法不一致");
            infoMap.put("state",false);
        }catch (Exception e){
            infoMap.put("msg","无效签名");
            infoMap.put("state",false);
        }
        //将Map转为JSON返回给前端
        ObjectMapper objectMapper = new ObjectMapper();
        String mapInfoJson = objectMapper.writeValueAsString(infoMap);
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        PrintWriter writer = response.getWriter();
        writer.write(mapInfoJson);
        return false;
    }

}

3.1.6 配置拦截器

    在springMVC-context.xml配置文件中配置拦截器。

 
    <mvc:interceptors>
        
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/user/POST/register"/>
            <mvc:exclude-mapping path="/user/POST/login"/>
            <mvc:exclude-mapping path="/sender/GET/email"/>
            <mvc:exclude-mapping path="/**/*.js"/>
            <mvc:exclude-mapping path="/upload/**"/>
            <bean class="com.xwd.interceptor.JWTInterceptor">bean>
        mvc:interceptor>
    mvc:interceptors>

3.1.7 前端请求头添加token信息

    如果为Vue+Axios,可以通过Axios配置拦截器,为每次的HTTP请求头加上本地存储的token信息,交由后端的HandlerInterceptor拦截器实现子类JWTInterceptor 进行token校验。
    Axios拦截器介绍:官网地址,主要是在每一个请求或响应被 then 或 catch 处理前拦截它们。 示例如下:

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });

    自定义前端拦截器,可以在main.js文件中添加如下代码:

import axios from 'axios'

// Vue.prototype.$axios = axios
axios.defaults.baseURL = 'http://localhost:8010/stasys_v3/'
//axios-添加请求拦截器interpetor
axios.interceptors.request.use(
    function(config) {
        //在发送请求之前,从localStorage获取token
        var token = localStorage.getItem('token')
        //判断token是否为空
        if (token && token != '') {
        	//不为空则添加token信息到请求头
            config.headers['token'] = token
        }
        return config
    },
    function(err) {
        //错误处理
        return Promise.reject(err)
    }
)
//挂载axios
Vue.prototype.$axios = axios

    也可以基于token为router路由配置导航守卫,在页面跳转时进行安全验证,这个看自己的需求。

3.1 SpringBoot 拦截器实现

    参见2.2.3部分的举例即为SpringBoot中拦截器的简单实现。

你可能感兴趣的:(Java)