1. 程式人生 > >Spring Security技術棧開發企業級認證與授權(五)使用Filter、Interceptor和AOP攔截REST服務

Spring Security技術棧開發企業級認證與授權(五)使用Filter、Interceptor和AOP攔截REST服務

一般情況,在訪問RESTful風格的API之前,可以對訪問行為進行攔截,並做一些邏輯處理,本文主要介紹三種攔截方式,分別是:過濾器Filter、攔截器Interceptor以及面向切面的攔截方式AOP

一、使用過濾器Filter進行攔截

使用過濾器進行攔截主要有兩種方式,第一種是將自定義的攔截器標註為SpringBean,在Spring Boot應用就可以對RESTful風格的API進行攔截。第二種方式往往應用在繼承第三方過濾器,這時候就需要將第三方攔截器使用FilterRegistrationBean物件進行註冊即可。接下來詳細介紹兩種方式。

  • 將攔截器標註為Spring
    Bean
package com.lemon.security.web.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;

/**
 * @author lemon
 * @date 2018/4/1 下午10:19
 */
@Component
public class TimeFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws
ServletException { System.out.println("time filter init."); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("time filter start."); long startTime = System.currentTimeMillis(); chain.doFilter(request, response); System.out.println("time filter 耗時: "
+ (System.currentTimeMillis() - startTime)); System.out.println("time filter finish."); } @Override public void destroy() { System.out.println("time filter destroy."); } }

啟動Spring Boot應用的時候,上面的攔截器就會起作用,當訪問每一個服務的時候,都會進入這個攔截器中。初始化方法init和銷燬方法destroy只會呼叫一次,分別是應用啟動時候呼叫init方法,應用關閉時候呼叫destroy方法。而doFilter方法則在每次都會呼叫。

  • 將攔截器作為第三方攔截器進行註冊

使用的類還是上面的同一個類,只不過這次不需要@Component註解,這時候我們需要自己寫一個配置類,將過濾器註冊到Spring容器中。推薦使用這種方式,因為這種方式我們可以自己設定需要攔截的API,否則第一種方式是攔截所有的API


package com.lemon.security.web.config;

import com.lemon.security.web.filter.TimeFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
 * @author lemon
 * @date 2018/4/1 下午10:34
 */
@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean timeFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        TimeFilter timeFilter = new TimeFilter();
        filterRegistrationBean.setFilter(timeFilter);
        List<String> urls = new ArrayList<>();
        urls.add("/*");
        filterRegistrationBean.setUrlPatterns(urls);
        return filterRegistrationBean;
    }
}

這裡我設定的仍然是攔截所有的API,可以設定為自定義的方式對API進行攔截。

二、使用攔截器Interceptor進行攔截

這裡需要定義一個攔截器類,並實現HandlerInterceptor介面,這個介面有三個方法需要實現,分別是:

boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception;
void postHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;
void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;

下面對三個方法進行一一解釋:

  • preHandle方法的第三個引數是具體的API處理方法的Method物件,我們可以將其強轉為HandlerMethod,然後就可以獲取該Method的一些屬性,比如方法名,方法所在類的類名等資訊。preHandle是當訪問API之前,都要進入這個方法,由這個方法進行一些邏輯處理,如果處理完結果返回true,那麼將繼續進入到具體的API中,否則將就地結束訪問,邏輯不會進入API方法中。

  • postHandle方法是在API方法訪問完成之後立即進入的方法,可以處理一些邏輯,比如將API中的資料封裝到ModelAndView中,如果前面的preHandle方法返回false,將不會執行該方法,如果API方法發生了異常,也將不會呼叫此方法。

  • afterCompletion方法的呼叫只要preHandle方法通過之後就會呼叫它,不論API方法是否出現了異常。如果出現了異常,將被封裝到Exception物件中。

下面,寫一個自定義的類來實現上述介面:

package com.lemon.security.web.interceptor;

import org.springframework.stereotype.Component;
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;

/**
 * @author lemon
 * @date 2018/4/1 下午10:39
 */
@Component
public class TimeInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandler");
        System.out.println(((HandlerMethod) handler).getBean().getClass().getName());
        System.out.println(((HandlerMethod) handler).getMethod().getName());
        request.setAttribute("startTime", System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandler");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
        System.out.println("TimeInterceptor耗時:" + (System.currentTimeMillis() - (Long) request.getAttribute("startTime")));
    }
}

這裡需要將其標註為SpringBean,但是僅僅標註為Bean還是不夠的,需要在配置類中進行配置。程式碼如下:

package com.lemon.security.web.config;

import com.lemon.security.web.interceptor.TimeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * @author lemon
 * @date 2018/4/1 下午10:34
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private TimeInterceptor timeInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeInterceptor);
    }
}

這個配置類需要繼承WebMvcConfigurerAdapter,並重寫新增攔截器的方法addInterceptors,將自定義攔截器新增到應用中。這時候攔截器就生效了。

三、使用AOP進行攔截

其實是有攔截器InterceptorAPI進行攔截的時候是有缺陷的,因為無法獲取前端訪問API的時候所攜帶的引數的,為什麼會這麼說?從Spring MVCDispatcherServlet的原始碼中可以發現,找到doDispatch方法,也就是請求分發的方法,有一段程式碼如下:
這裡寫圖片描述
如果我們自定的InterceptorpreHandler方法返回的是false,分發任務就會截止,不再繼續執行下面的程式碼,而下面的一行程式碼正是將前端攜帶的引數進行對映的邏輯,也就是說,preHandler方法不會接觸到前端攜帶來的引數,也就是說攔截器無法處理引數。所以這裡引進AOP進行攔截。
AOP的核心概念解釋:
描述AOP常用的一些術語有通知(Adivce)、切點(Pointcut)、連線點(Join point)、切面(Aspect)、引入(Introduction)、織入(Weaving

  • 通知(Advice

通知分為五中型別:
Before:在方法被呼叫之前呼叫
After:在方法完成後呼叫通知,無論方法是否執行成功
After-returning:在方法成功執行之後呼叫通知
After-throwing:在方法丟擲異常後呼叫通知
Around:通知了好、包含了被通知的方法,在被通知的方法呼叫之前後呼叫之後執行自定義的行為

  • 連線點(Join point

連線點是一個應用執行過程中能夠插入一個切面的點。比如:方法呼叫、方法執行、欄位設定/獲取、異常處理執行、類初始化、甚至是for迴圈中的某個點。理論上, 程式執行過程中的任何時點都可以作為作為織入點, 而所有這些執行時點都是Joint point,但 Spring AOP 目前僅支援方法執行 (method execution)。

  • 切點(Pointcut

通知(advice)定義了切面何時,那麼切點就是定義切面“何處” 描述某一類 Joint points, 比如定義了很多 Joint point, 對於 Spring AOP 來說就是匹配哪些方法的執行。

  • 切面(Aspect

切面是切點和通知的結合。通知和切點共同定義了關於切面的全部內容,它是什麼時候,在何時和何處完成功能。

  • 引入(Introduction

引用允許我們向現有的類新增新的方法或者屬性

  • 織入(Weaving

組裝方面來建立一個被通知物件。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在執行時完成。Spring和其他純Java AOP框架一樣,在執行時完成織入。

上面的概念有點生澀難懂,總結一個核心內容:切面 = 切點 + 通知
現在通過程式碼來編寫一個切面:

package com.lemon.security.web.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @author lemon
 * @date 2018/4/2 上午10:40
 */
@Aspect
@Component
public class TimeAspect {

    @Around("execution(* com.lemon.security.web.controller.UserController.*(..))")
    public Object handleTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("time aspect is start.");
        for (Object object : proceedingJoinPoint.getArgs()) {
            System.out.println(object);
        }
        long startTime = System.currentTimeMillis();
        Object obj = proceedingJoinPoint.proceed();
        System.out.println("time aspect 耗時:" + (System.currentTimeMillis() - startTime));
        System.out.println("time aspect finish.");
        return obj;
    }
}

@Around定義了環繞通知,也就是定義了何時使用切面,表示式"execution(* com.lemon.security.web.controller.UserController.*(..))"定義了再哪裡使用。ProceedingJoinPoint物件的proceed()方法表示執行被攔截的方法,它有一個Object型別的返回值,是原有方法的返回值,後期使用的時候往往需要強轉。關於切點的表示式,可以訪問Spring官方文件
對於上面三種攔截方式,他們的執行有一個基本的順序,進入的順序是Filter-->Interceptor-->Aspect-->Controller-->Aspect-->Interceptor-->Filter(不考慮異常的發生)。如下圖所示:
這裡寫圖片描述

Spring Security技術棧開發企業級認證與授權系列文章列表:

示例程式碼下載地址:

專案已經上傳到碼雲,歡迎下載,內容所在資料夾為chapter005

相關推薦

Spring Security技術開發企業級認證授權使用FilterInterceptorAOP攔截REST服務

一般情況,在訪問RESTful風格的API之前,可以對訪問行為進行攔截,並做一些邏輯處理,本文主要介紹三種攔截方式,分別是:過濾器Filter、攔截器Interceptor以及面向切面的攔截方式AOP。 一、使用過濾器Filter進行攔截 使用過

Spring Security技術開發企業級認證授權開發圖形驗證碼介面

在設計登入模組的時候,圖形驗證碼基本上都是標配,本篇部落格重點介紹開發可重用的圖形驗證碼介面,該介面支援使用者自定義配置,比如驗證碼的長度、驗證碼圖形的寬度和高度等資訊。 本文的目標是開發一個圖形驗證碼介面,該驗證碼支援使用者自定義長度,以及生成圖片後

Spring Security技術開發企業級認證授權Spring Security的基本執行原理個性化登入實現

正如你可能知道的兩個應用程式的兩個主要區域是“認證”和“授權”(或者訪問控制)。這兩個主要區域是Spring Security的兩個目標。“認證”,是建立一個他宣告的主題的過程(一個“主體”一般是指使用者,裝置或一些可以在你的應用程式中執行動作的其他系統)

Spring Security技術開發企業級認證授權使用Swagger自動生成API文件

由於Spring Boot能夠快速開發、便捷部署等特性,相信有很大一部分Spring Boot的使用者會用來構建RESTful API。而我們構建RESTful API的目的通常都是由於多終端的原因,這些終端會共用很多底層業務邏輯,因此我們會抽象出這樣一層

Spring Security技術開發企業級認證授權環境搭建

Spring Security是一個能夠為基於Spring的企業應用系統提供宣告式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Contr

Spring Security技術開發企業級認證授權十一開發簡訊驗證碼登入

簡訊登入也是一種常見的登入方式,但是簡訊登入的方式並沒有整合到Spring Security中,所以往往還需要我們自己開發簡訊登入邏輯,將其整合到Spring Security中,使用Spring Security來進行校驗。本文將介紹開發簡訊登入的方法,

Spring Security技術開發企業級認證授權

iyu 復雜 sha 日誌 開發app 一個 核心概念 並發 自動 Spring Security技術棧開發企業級認證與授權網盤地址:https://pan.baidu.com/s/1mj8u6JQ 密碼: 92rp備用地址(騰訊微雲):https://share.weiy

Spring Security技術開發企業級認證授權 Spring Security開發安全的REST服務

第1章 課程導學 介紹課程內容、課程特點,使用的主要技術棧,以及學習課程所需的前置知識 1-1 導學 第2章 開始開發 安裝開發工具,介紹專案程式碼結構並搭建,基本的依賴和引數設定,開發hello world 2-1 開發環境安裝 2-2 程式碼結構介紹 2-3

.net core 認證授權

前言 .net core web並不是一個非常新的架構,很多文章提及到認證與授權這個過程,但是一般都會提及到裡面的方法怎麼用的,而不是模擬一個怎樣的過程,所以我打算記錄自己的理解。 什麼是認證?我們大學畢業有學士證書和畢業證書,來證明你是一個學士。 什麼是授權,比如說你被認證是我的朋友後,你可以拿著這個身份,

.net core 認證授權

前言 這篇緊接著一來寫的,在第一篇中介紹了認證與授權,同時提出了這套機制其實就是模擬現實中的認證與授權。 同樣這篇介紹在這套機制下,使用者資訊管理機制?這裡就會問了,上一篇中認證和授權不是都ok了嗎,怎麼會有一個管理機制呢?當然並不一定要使用下面這套機制,但是給了我們很大的啟發。 在上一結中我們頒發證書是這樣

.net core 認證授權

前言 在寫三上是在一的基礎上寫的,所以有沒有看過二是沒得關係的,在一中介紹了認證與授權,但是沒有去介紹拿到證書後怎樣去驗證授權。 概念性東西:在這套機制中,把這個許可權認證呢,稱作為policy。這個policy是怎麼樣的過程呢? 就像我前面說的,證書也認證了,policy做的是檢查你的證書中是否符合我心中的

Angular SPA基於Ocelot API閘道器IdentityServer4的身份認證授權

在上一講中,我們已經完成了一個完整的案例,在這個案例中,我們可以通過Angular單頁面應用(SPA)進行登入,然後通過後端的Ocelot API閘道器整合IdentityServer4完成身份認證。在本講中,我們會討論在當前這種架構的應用程式中,如何完成使用者授權。 回顧 《Angular SPA基於Oc

開發之HTML快速入門

ack enter 提示 其他 red tle 顯示圖片 val password 一、HTML 是什麽? HTML 指的是超文本標記語言 (Hyper Text Markup Language) HTML 不是一種編程語言,而是一種標記語言 (markup lan

2016-06-26 發布 支付系統開發的實踐思考

接口 簡單的 單向 new 成了 異步通知 平臺 應收 技術分享 通常我們在開發手機 app 或網站時都會涉及到支付相關的業務場景,用戶只需要簡單的點擊下按鈕並輸入密碼,就完成了整個支付過程。那麽今天我們就來簡單聊一下一個完整的支

資料結構演算法-揹包佇列

  前言:許多基礎資料型別都和物件的集合有關。具體來說,資料型別的值就是一組物件的集合,所有操作都是關於新增、刪除或是訪問集合中的物件。而且有很多高階資料結構都是以這樣的結構為基石創造出來的,在本文中,我們將瞭解學習三種這樣的資料型別,分別是揹包(Bag)、棧(Stack)和佇列(Queue) 一、學習感悟

Android開發--圖形影象動畫--AnimationListener簡介

                     就像Button控制元件有監聽器一樣,動畫效果也有監聽器,只需要實現AnimationListener就可以實現對動畫效果的監聽,其中需要過載三個函式,就是下面的這幾個函式:private class MyListenr implements AnimationLis

Android開發中佈局元件—— padding margin 的區別

在 Android開發中我們會設定某個檢視相對於別的檢視的距離,這時我們就要用到 margin 和 padding ,但是有時候很容易把這兩個屬性弄混淆,那我們就看看他們的區別。 外邊距(margin): 屬於佈局引數,決定兩個元件之間的距離。作用於多個元件之間。 內邊距(

Android開發中佈局元件—— 螢幕尺寸單位dp,px,sp的探究

在Android開發中,常用的尺寸單位有 dp , px , sp 。當然還有其他的單位如 pt , mm 等,不過這些都是不常用,所以我們重點來探究一下 dp , px , sp 這三個常用的單位。 px 英文 pixel 的縮寫,即畫素。無論螢幕密度為多少,一個畫素單位對應

Android開發Linux開發的區別聯絡入門

1、Android的目錄路徑等不明,Linux程式碼如何移植進Android相應目錄不知; 2、目前Linux核心程式碼放置在\\192.168.1.190\share\android\kernel_imx; 3、安卓下編譯Linux核心的方法(PC機使用ssh除錯): #

python全開發中級班全程筆記第二模塊第四章模塊常用模塊

span 自己 文件目錄 port all mode 功能 最大的 維護 python全棧開發筆記第二模塊 第四章 :常用模塊 一、定義:   在程序開發過程中,隨著代碼越寫越多,在一個文件裏,代碼就會越來越長,越來越不容易維護。   為了編寫更好維護的