SpringBoot :API接口攔截器驗證(Token驗證)並將數據存入Request中供接口調用

一、爲什麼需要攔截器?

在前後端分離的現在,項目中的所有的前端的頁面都需要通過調用後臺的Api進行獲取數據
接口的功能點不同,就會有很多種情況,比如說

  1. 涉及敏感數據(登錄,獲取個人信息,個人金額修改)相關的接口需要token驗證
  2. 獲取不敏感數據則不需要進行校驗
  3. vue等前端調用後臺api,如果沒有引入(nginx),則有可能有跨域問題

所以說需要一個攔截器去區分哪些路徑下需要token校驗,那些不需要。

二、實現思路

  1. 新增一個配置類繼承WebMvcConfigurer
  2. 在這個配置類中新增自定義邏輯的攔截器(實現HandlerInterceptor接口),同時設定哪些路徑需要調用自定義攔截器

三、具體代碼實現(示例代碼:https://github.com/zz790609619/LeetCodeRecord.git)

配置類

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;

import java.util.List;

/**
 * 攔截器(可做跨域,token驗證等)
 */

@Configuration
public class WebRequestInterceptor implements WebMvcConfigurer {
    /**
     * 自定義攔截器()
     * @param registry
     */
    public void addInterceptors(InterceptorRegistry registry) {
    	// RequestInterceptor爲具體攔截邏輯的執行類 實現了HandlerInterceptor接口
    	// addPathPatterns("/test/**")  意義是訪問路徑下/test 下所有的訪問路徑都需要被RequestInterceptor攔截
    	// excludePathPatterns 這個訪問路徑/test/exception則不在被RequestInterceptor攔截的範圍
    	// /user/** user下所有路徑都包含在內 例:/user/api 、/user/api/zz 
    	// /user/* 只有user下一層路徑包含在內 例:/user/api(包含) 、/user/api/zz(不包含) 
    	// /test/queryUser接口則是token驗證後,把token爲xx的玩家信息放入Request中,方便接口拿取
        registry.addInterceptor(new RequestInterceptor())
        		.addPathPatterns("/test/**")
        		.addPathPatterns("/test/queryUser")
                .excludePathPatterns("/test/exception");
    }
    
    /**
     * 跨域支持 比如說vue 的axios訪問
     * @param registry
     */
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600 * 24);
    }
    /**
     * 修改訪問路徑
     * @param configurer
     */
    public void configurePathMatch(PathMatchConfigurer configurer) {
        // 設置爲true後,訪問路徑後加/ 也能正常訪問  /user == /user/
        // configurer.setUseTrailingSlashMatch(true);
    }

    /**
     * 內容協商機制,主要是方便一個請求路徑返回多個數據格式
     * @param configurer
     */
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    /**
     * 處理異步請求的。只能設置兩個值,一個超時時間(毫秒,Tomcat下默認是10000毫秒,即10秒),還有一個是AsyncTaskExecutor,異步任務執行器
     * @param configurer
     */
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
//        //設置超時時間
//        configurer.setDefaultTimeout(1000000);
//        //設置異步任務執行器
//        configurer.setTaskExecutor(new AsyncTaskExecutor() {
//            @Override
//            public void execute(Runnable runnable, long l) {
//
//            }
//
//            @Override
//            public Future<?> submit(Runnable runnable) {
//                return null;
//            }
//
//            @Override
//            public <T> Future<T> submit(Callable<T> callable) {
//                return null;
//            }
//
//            @Override
//            public void execute(Runnable runnable) {
//
//            }
//        });
    }

    /**
     * 這個接口可以實現靜態文件可以像Servlet一樣被訪問。
     * @param configurer
     */
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    }

    /**
     * 增加轉化器或者格式化器。這邊不僅可以把時間轉化成你需要時區或者樣式。還可以自定義轉化器和你數據庫做交互,比如傳進來userId,經過轉化可以拿到user對象
     * @param registry
     */
    public void addFormatters(FormatterRegistry registry) {
    }

    /**
     * 添加靜態資源--過濾swagger-api (開源的在線API文檔)
     * @param registry
     */
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }



    public void addViewControllers(ViewControllerRegistry registry) {
    }

    public void configureViewResolvers(ViewResolverRegistry registry) {
    }

    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    }

    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
    }
    /**
     * 配置消息轉換器
     * @param converters
     */
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }

    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }

    @Nullable
    public Validator getValidator() {
        return null;
    }

    @Nullable
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

自定義的攔截器

package com.example.demo.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.entity.model.ResponseDto;
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.Enumeration;

public class RequestInterceptor implements HandlerInterceptor {

        /**
         * 預處理回調方法,實現處理器的預處理(如檢查登陸),第三個參數爲響應的處理器,自定義Controller
         * 返回值:
         * true表示繼續流程(如調用下一個攔截器或處理器);
         * false表示流程中斷(如登錄檢查失敗),不會繼續調用其他的攔截器或處理器,此時我們需要通過response來產生響應;
         */
        @Override
        public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        	//將頭部信息都轉換成map
            JSONObject map = new JSONObject();
            Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String key = headerNames.nextElement();
                String value = httpServletRequest.getHeader(key);
                map.put(key, value);
            }
            map.put("token", httpServletRequest.getHeader("AUTH-TOKEN"));
            //判斷從前端傳來的頭部信息中AUTH-TOKEN的值是否與我們後臺定義的token值一致
            if("111".equals(map.get("token"))){
                //token正確 繼續下一步攔截器(如果有)
                System.out.println("token is right");
                //從Spring上下文中拿到UserMapper
                UserMapper userMapper= ApplicationContextUtil.getBean(UserMapper.class);
                //獲取該token對應的用戶信息
                User user=userMapper.getUserByToken(String.valueOf(map.get("token")));
                //將用戶信息放入Request中
                httpServletRequest.setAttribute("test",JSON.toJSONString(user));
                return true;
            }else{
                //token錯誤 返回錯誤response 
                System.out.println("token is error");
                PrintWriter writer = null;
                try {
                    ResponseDto dto=new ResponseDto();
                    dto.setErrorCode(1002);
                    dto.setMessage("RequestInterceptor");
                    httpServletResponse.setCharacterEncoding("utf-8");
                    httpServletResponse.setHeader("Content-Type","application/json");
                    writer = httpServletResponse.getWriter();
                    //將返回的錯誤提示壓入流中
                    writer.write(JSON.toJSONString(dto));
                    writer.flush();
                } catch (Exception e) {

                } finally {
                    if (null != writer) {
                        writer.close();
                    }
                    return false;
                }
            }

        }

        /**
         * 後處理回調方法,實現處理器的後處理(但在渲染視圖之前),此時我們可以通過modelAndView(模型和視圖對象)對模型數據進行處理或對視圖進行處理,modelAndView也可能爲null。
         */
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        }

        /**
         * 整個請求處理完畢回調方法,即在視圖渲染完畢時回調,如性能監控中我們可以在此記錄結束時間並輸出消耗時間,還可以進行一些資源清理,類似於try-catch-finally中的finally,但僅調用處理器執行鏈中
         */
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        }


}

接口Controller

package com.example.demo.controller;

import com.aliyun.openservices.shade.com.alibaba.fastjson.JSON;
import com.example.demo.entity.model.ResponseDto;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping(value = "/test")
public class UserNodeController {
    @RequestMapping(value = "/index")
    public String index(){
        return "/index";
    }

    /**
     * 異常報錯
     * 訪問路徑:http://127.0.0.1:8090/test/exception
     * 返回結果: {"errorCode": 1001,"message": "Exception:4","data": null }
     */
    @RequestMapping(value = "/exception")
    public String getException(){
        int[] arr = {1, 2, 3};
        System.out.println(arr[4]);
        ResponseDto dto=new ResponseDto();
        dto.setMessage("NoEnterGlobalException");
        return JSON.toJSONString(dto);
    }
    /**
     *
     * 空指針異常報錯
     * 訪問路徑:http://127.0.0.1:8090/test/nullPointException
     * 返回結果:{"errorCode": 1002,"message": "NullPointerException:null","data": null }
     */
    @RequestMapping(value = "/nullPointException")
    public ResponseDto getNullPointException(){
        Object obj = null;
        obj.toString();
        ResponseDto dto=new ResponseDto();
        dto.setMessage("NoEnterGlobalNullPointException");
        return dto;
    }
    /**
     * 獲取全局變量
     * 訪問路徑:http://127.0.0.1:8090/test/getGlobalParm
     * 返回結果:{"ww":{"ww":"helloQ"}}
     */
    @RequestMapping(value = "/getGlobalParm")
    public String getGlobalParm(Model model){
        Map<String, Object> map = model.asMap();
        return JSON.toJSONString(map);
    }

    /**
     * 獲取預處理後的數據
     * 訪問地址及參數:http://127.0.0.1:8090/test/getPreprocessedData?helloA.errorCode=1&helloA.message=a&helloA.data=a&helloB.errorCode=2&helloB.message=b&helloB.data=b
     * 返回結果:dtoA:{"data":"a","errorCode":1,"message":"a"},dtoB:{"data":"b","errorCode":2,"message":"b"}
     */
    @RequestMapping(value = "/getPreprocessedData")
    public String getPreprocessedData(@ModelAttribute("helloA")ResponseDto dtoA,@ModelAttribute("helloB")ResponseDto dtoB){
        return "dtoA:"+JSON.toJSONString(dtoA)+",dtoB:"+JSON.toJSONString(dtoB);
    }

	@GetMapping("/queryUser")
    public void queryUser(HttpServletRequest request,@RequestParam("token") String token){
        //在攔截器中驗證token並獲取到這個token對應的信息
        System.out.println(request.getAttribute("test"));
        System.out.println(token);
    }
}

全局異常處理類

package com.example.demo.config;

import com.example.demo.entity.model.ResponseDto;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class GlobalControllerAdvice {
    /**
     * 所有異常處理
     * @param request
     * @param e
     * @return
     * @throws Exception
     */
    @ExceptionHandler(value = Exception.class)
    public @ResponseBody
    ResponseDto exceptionHandler(HttpServletRequest request,Exception e)throws Exception{
        ResponseDto dto=new ResponseDto();
        dto.setErrorCode(1001);
        dto.setMessage("Exception:"+e.getMessage());
        return dto;
    }

    /**
     * 空指針異常處理
     * @param request
     * @param e
     * @return
     * @throws NullPointerException
     */
    @ExceptionHandler(value = NullPointerException.class)
    public @ResponseBody
    ResponseDto exceptionHandler(HttpServletRequest request,NullPointerException e)throws NullPointerException{
        ResponseDto dto=new ResponseDto();
        dto.setErrorCode(1002);
        dto.setMessage("NullPointerException:"+e.getMessage());
        return dto;
    }

    /**
     * 全局變量  攔截器
     * @return
     */
    @ModelAttribute(name="ww")
    public Map<String,Object> globalData() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("ww", "helloQ");
        return map;
    }

    /**
     * 數據預處理
     * @param binder
     */
    @InitBinder("helloA")
    public void b(WebDataBinder binder) {
        binder.setFieldDefaultPrefix("helloA.");
    }
    @InitBinder("helloB")
    public void a(WebDataBinder binder) {
        binder.setFieldDefaultPrefix("helloB.");
    }

}

ApplicationContextUtil(在項目初始化的時候將Spring上下文放入ApplicationContextUtil中,方便後面定時器/攔截器等獲取實體類bean)

import org.springframework.context.ApplicationContext;

public class ApplicationContextUtil{
	private static ApplicationContext applicationContext;
	//在項目初始化的時候將SpringApplication.run(DemoApplication.class, args)set進去
	public static ApplicationContext setApplicationContext(ApplicationContext context){
		this.applicationContext=context;
	}
	public static object getBean(String beanId){
		return applicationContext.getBean(beanId);
	}
	public static <T>T getBean(Class<T> clazz){
		return applicationContext.getBean(clazz);
	}
}

四、測試結果

如配置類中設置的除了/test/exception ,其他的/test下的路徑都應該被我們自定義攔截器攔截

  1. 訪問路徑:http://127.0.0.1:8090/test/exception
    結果:在這裏插入圖片描述

  2. 訪問路徑:http://127.0.0.1:8090/test/nullPointException
    輸入正確的token的結果:
    在這裏插入圖片描述
    輸入錯誤token值的結果:
    在這裏插入圖片描述

  3. http://127.0.0.1:8090/test/queryUser?token=111 驗證用戶信息能否能在接口中獲取
    控制檯如圖:
    在這裏插入圖片描述
    數據庫如圖:
    在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章