利用Filter和攔截器,將用戶信息動態傳入Request方法

来源:https://www.cnblogs.com/seifon/archive/2018/04/25/8948025.html
-Advertisement-
Play Games

前言: 在開發當中,經常會驗證用戶登錄狀態和獲取用戶信息。如果每次都手動調用用戶信息查詢介面,會非常的繁瑣,而且代碼冗餘。為了提高開發效率,因此就有了今天這篇文章。 思路: 用戶請求我們的方法會攜帶一個Token,通過Filter過濾器將會員信息查出來並放到request請求參數中。接著在Cotro ...


前言:

  • 在開發當中,經常會驗證用戶登錄狀態和獲取用戶信息。如果每次都手動調用用戶信息查詢介面,會非常的繁瑣,而且代碼冗餘。為了提高開發效率,因此就有了今天這篇文章。

思路:

  • 用戶請求我們的方法會攜帶一個Token,通過Filter過濾器將會員信息查出來並放到request請求參數中。接著在Cotroller層的請求方法中接收一個MemberDeatails類型的參數,就能直接獲得會員信息了。

詳細步驟:

1. Gradle引入需要的Jar包:
compile "com.fasterxml.jackson.core:jackson-databind:2.8.10"
2. 定義一個Login註解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Login {

    String value() default "";

}
3. 定義一個MemberDetails.class,用於封裝用戶信息
public class MemberDetails {

    private String memberId;

    private String memberName;

    private String memberNickname;

    private String memberPhone;

    private String memberEmail;

}
5. 定義一個會員介面類
/**
 * @Author: XiongFeng
 * @Description: 會員介面
 * @Date: Created in 19:40 2018/4/10
 */
public interface MemberService {

    /** 根據TokenId獲取用戶信息 */
    MemberDto getMemberByToken(String token);
}
6. 定義一個會員介面實現類,在裡面寫上用戶信息獲取方法
@Service
public class MemberServiceImpl implements MemberService {

    @Override
    public MemberDetails getMemberDetailsByToken(String token) {
        if (StringUtils.isBlank(token)) return null;
        if (!"123".equals(token)) return null;
        MemberDetails memberDetails = new MemberDetails();
        memberDetails.setMemberId("123");
        memberDetails.setMemberName("哈哈123");
        memberDetails.setMemberEmail("[email protected]");
        memberDetails.setMemberNickname("Seifon");
        memberDetails.setMemberPhone("13100001111");
        return memberDetails;
    }
}
7. 定義一個Request請求包裝類
  • 通過繼承HttpServletRequestWrapper類,重寫它裡面的多個方法,對前端傳過來的參數進行重新封裝。因為在Filter,雖然可以通過request.getParameterMap()拿到一個含有參數的map,但是不能直接對request裡面東西進行修改操作,一旦重新修改,就會報錯。後來我發現j2ee已經給我們提供瞭解決的辦法,使用HttpServletRequestWrapper類來解決向request添加額外參數的功能。於是我對HttpServletRequest進行重新包裝,在裡面重新定義一個map,將以前的參數put進去,並將我們需要添加的參數放進去,達到我們想要的效果。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * @Author: XiongFeng
 * @Description: 對Request請求重新包裝
 * @Date: Created in 11:17 2018/4/13
 */
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
    private Map<String , String[]> params = new HashMap<String, String[]>();


    @SuppressWarnings("unchecked")
    public ParameterRequestWrapper(HttpServletRequest request) {
        // 將request交給父類,以便於調用對應方法的時候,將其輸出,其實父親類的實現方式和第一種new的方式類似
        super(request);
        //將參數表,賦予給當前的Map以便於持有request中的參數
        this.params.putAll(request.getParameterMap());
    }
    //重載一個構造方法
    public ParameterRequestWrapper(HttpServletRequest request , Map<String , Object> extendParams) {
        this(request);
        addAllParameters(extendParams);//這裡將擴展參數寫入參數表
    }

    /**
     * 覆寫獲取key的方法
     */
    @Override
    public Enumeration getParameterNames() {
        Vector names = new Vector(params.keySet());
        return names.elements();
    }

    /**
     * 覆寫獲取值value的方法
     */
    @Override
    public String getParameter(String name) {
        Object v = params.get(name);
        if (v == null) {
            return null;
        } else if (v instanceof String[]) {
            String[] strArr = (String[]) v;
            if (strArr.length > 0) {
                return strArr[0];
            } else {
                return null;
            }
        } else if (v instanceof String) {
            return (String) v;
        } else {
            return v.toString();
        }
    }

    @Override
    public String[] getParameterValues(String name) {
        Object v = params.get(name);
        if (v == null) {
            return null;
        } else if (v instanceof String[]) {
            return (String[]) v;
        } else if (v instanceof String) {
            return new String[] { (String) v };
        } else {
            return new String[] { v.toString() };
        }
    }

    public void addAllParameters(Map<String , Object>otherParams) {//增加多個參數
        for(Map.Entry<String , Object>entry : otherParams.entrySet()) {
            addParameter(entry.getKey() , entry.getValue());
        }
    }


    public void addParameter(String name , Object value) {//增加參數
        if(value != null) {
            if(value instanceof String[]) {
                params.put(name , (String[])value);
            }else if(value instanceof String) {
                params.put(name , new String[] {(String)value});
            }else {
                params.put(name , new String[] {String.valueOf(value)});
            }
        }
    }

}
8. 定義一個過濾器
  • 在這個過濾裡面,主要校驗Token是否有效以及將會員信息添加到request。首先,從Request請求頭中拿到前端傳過來的Token,並使用Token調用會員信息獲取介面,得到用戶的資料,然後將用戶信息put到ParameterMap中,這個ParameterMap是我們通過ParameterRequestWrapper重新包裝的一個map,因此可以在裡面添加會員的參數,然後將新的request傳遞出去。
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: XiongFeng
 * @Description: 會員登錄信息過濾器
 * @Date: Created in 11:17 2018/4/13
 */
@Component
@WebFilter(urlPatterns = "/*")
public class MemberFilter implements Filter {

    MemberService memberService = new MemberServiceImpl();

    ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;

        String tokenId = req.getHeader("X-Authorization");

        if (tokenId == null || "".equals(tokenId) || tokenId.isEmpty()) {
            chain.doFilter(request, response);
            return;
        }

        MemberDetails memberDetails = memberService.getMemberDetailsByToken(tokenId);
        if (memberDetails == null) {
            try {
                respFail(response);
                return;
            } catch (Exception e) {
                throw new ServletException();
            }
        }

        Map<String, String[]> parameterMap = new HashMap<String, String[]>(request.getParameterMap());
        parameterMap.put("memberId", new String[]{memberDetails.getMemberId()});
        parameterMap.put("memberName", new String[]{memberDetails.getMemberName()});
        parameterMap.put("memberEmail", new String[]{memberDetails.getMemberEmail()});
        parameterMap.put("memberNickname", new String[]{memberDetails.getMemberNickname()});
        parameterMap.put("memberPhone", new String[]{memberDetails.getMemberPhone()});
        chain.doFilter(new ParameterRequestWrapper((HttpServletRequest) req, parameterMap), response);
    }

    /** 返回失敗結果Json數據 */
    private void respFail(ServletResponse response) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("status", 500);
        map.put("message", "登錄失效,請登錄");
        map.put("data", null);
        String s = objectMapper.writeValueAsString(map);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(s);
    }

    @Override
    public void destroy() {

    }
}
9. 定義一個SpringMVC攔截器
package cn.seifon.paymodle.interceptor;

import cn.seifon.paymodle.annotations.Login;
import cn.seifon.paymodle.dto.MemberDetails;
import cn.seifon.paymodle.service.manager.member.MemberService;
import cn.seifon.paymodle.service.manager.member.impl.MemberServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: XiongFeng
 * @Description: 會員登錄信息攔截器
 * @Date: Created in 11:17 2018/4/13
 */
public class MemberInterceptor extends HandlerInterceptorAdapter {

    ObjectMapper objectMapper = new ObjectMapper();

    MemberService memberService = new MemberServiceImpl();

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HandlerMethod method = (HandlerMethod) handler;
        String[] memberIds = request.getParameterValues("memberId");

        MethodParameter[] methodParameters = method.getMethodParameters();
        //判斷方法類是否有MemberDetails入參
        if (methodParameters.length > 0) {
            for (MethodParameter methodParameter : methodParameters) {
                Type genericParameterType = methodParameter.getGenericParameterType();
                String typeName = genericParameterType.getTypeName();
                if (!typeName.equals(MemberDetails.class.getTypeName())) continue;
                if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用戶信息就返回失敗
                break;
            }
        }

        //判斷是否有Login註解
        Login login = method.getMethodAnnotation(Login.class);
        if (login == null) return true;

        if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用戶信息就返回失敗
        return true;
    }


    /** 返回失敗結果Json數據 */
    private boolean respFail(HttpServletResponse response) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("status", 500);
        map.put("message", "登錄失效,請登錄");
        map.put("data", null);
        String s = objectMapper.writeValueAsString(map);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(s);
        return false;
    }

}
10. 將攔截器註冊到WebMvcConfigurer中
@Configuration
public class MyWebAppConfigurer
        extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns 用於添加攔截規則
        registry.addInterceptor(new MemberInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }

}
11. 定義會員Controller
  • 經過一個過濾器和一個攔截器,request請求終於來到了我們Controller層。這時候,我們只需要在方法裡面寫入MemberDetails memberDetails 就OK了,不用做任何操作,我們就可以獲取會員信息了,是不是炒雞方便!另外還可以在方法上標@Login註解
@RestController
public class MemberController {


    @RequestMapping("/token")
    @Login
    public Map<String, Object> getUser(MemberDetails memberDetails) {
        //User user = userManager.selectByPrimaryKey(id);
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("status", 200);
        map.put("message", "請求成功");
        map.put("data", memberDetails);
        return map;
    }
    
}

運行結果:

遇到的坑:

  • 當時我嘗試過把會員參數放到session域中的Attribute,也嘗試過在Model里setAttribute。後來發現這是行不通的,在filter中直接使用request.setAttribute()是無效的。放在Modle也是可行,但是Controller裡面的方法需要加@ModelAttribute("...")才能得到用戶信息,很不方便。唯有通過request.getParameterMap() put()進去,才是最方便的。

  • 一開始我沒想到用過濾器,因此我就嘗試在攔截器里,直接通過ParameterRequestWrapper對request包裝,後來發現不管我怎麼弄都不成功。當時非常絕望,後來想了想會不會是攔截器不支持重新包裝request,於是我就通過filter去做,沒想到成功了。這時,我想既然用到了filter,那乾脆直接在filter裡面獲取@Login註解和獲取方法參數得了,後來發現filter裡面拿不到方法的信息,哭。後來想到一個辦法,可以通過先filter,後攔截器。於是就成功了!

後記:

  • 這篇文章只是記錄了我的一點小小經驗,如果有什麼不對的地方或者有更好的方法,請大家在評論里留言指正!

參考文章:http://www.importnew.com/19023.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 題目: You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes co ...
  • 簡單的建立一個後臺項目 新建servlet: 內容如下: web.xml 前端代碼: 運行前後臺項目,出現異常情況 在查看ajax的發送情況 從這裡可以看到結果是正確返回的;並且後臺也是正常執行了。 因此得出結論: 跨域是瀏覽器在aja返回結果的時候進行了攔截,先執行,後判斷,不是後臺不允許跨域; ...
  • 到這一步,我的收集系統就已經完成很大一部分工作,我們重新看一下我們之前畫的圖: 我們已經完成前面的部分,剩下是要完成後半部分,將kafka中的數據扔到ElasticSearch,並且最終通過kibana展現出來 ElasticSearch 官網地址這裡介紹了非常詳細的安裝方法:https://www ...
  • 用pip安裝了一個模塊,在pycharm中不能調用,然後發現shell和pycharm中的sys.path不一樣。 納尼?還能不一樣? (我的python裝在C盤,pycharm裝在D盤,pycharm寫的文件在E盤保存。 @暈@) 很明顯左邊的pycharm的sys.path中少了三個重要的路徑。 ...
  • [Java] 設計模式:代碼形狀 lambda表達式的一個應用 Code Shape 模式 這裡介紹一個模式:Code Shape。沒聽過,不要緊,我剛剛纔起的名字。 作用 在應用程式的開發中,我們一般會使用多層架構。 在這種情況下,每一層的方法往往會呈現相同的代碼結構。這裡稱之為 層的代碼形狀 。 ...
  • 搭建Struts2開發環境 1. 下載Struts2開發包 "http://struts.apache.org" 2. 開發包目錄結構 3. 搭建開發環境 3.1 拷貝必要jar包到classpath中 3.2 建立Struts2的配置文件 at the top of classpath(在最頂層的 ...
  • 前言 當我們知道了struts2爆出來的安全問題之後,漸漸的,struts2已經比較少人在使用了,雖然在筆者的介紹中依舊會介紹struts2,但是使用確實會比較少了。那麼今天我們要學習的,就是一個類似於struts2的前端框架,那就是springmvc。 一、springmvc簡介 1.1什麼是sp ...
  • 示例1 輸出 示例2 實現類重寫default方法 輸出 示例3 輸出 示例4 輸出 報錯 示例5 輸出 示例6 輸出 報錯 示例7 輸出 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...