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

前言:

  • 在開發當中,常常會驗證用戶登陸狀態和獲取用戶信息。若是每次都手動調用用戶信息查詢接口,會很是的繁瑣,並且代碼冗餘。爲了提升開發效率,所以就有了今天這篇文章。

思路:

  • 用戶請求咱們的方法會攜帶一個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("seifon@seifon.cn");
        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)});
            }
        }
    }

    /** 簡單封裝,請根據需求改進 */
    public void addObject(Object obj) {
        Class<?> clazz = obj.getClass();
        Method[] methods = clazz.getMethods();
        try {
            for (Method method : methods) {
                if (!method.getName().startsWith("get")) {
                    continue;
                }
                Object invoke = method.invoke(obj);
                if (invoke == null || "".equals(invoke)) {
                    continue;
                }

                String filedName = method.getName().replace("get", "");
                filedName = WordUtils.uncapitalize(filedName);

                if (invoke instanceof Collection) {
                    Collection collections = (Collection) invoke;
                    if (collections != null && collections.size() > 0) {
                        String[] strings = (String[]) collections.toArray();
                        addParameter(filedName, strings);
                        return;
                    }
                }

                addParameter(filedName, invoke);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}
複製代碼
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) this.respFail(response);

        ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper(req);
        requestWrapper.addObject(memberDetails);
        chain.doFilter(requestWrapper, response);
    }

	/** 返回失敗結果Json數據 */
    private void respFail(ServletResponse response) throws IOException {
        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攔截器
  • 在這個攔截器裏面,主要驗證Controller方法中是否須要MemberDetails和是否標了@Login註解。首先,從HandlerMethod中獲取全部入參,看有沒有須要MemberDetails參數,若是有,就從HttpServletRequest中拿memberId,若是不存在說明沒有登陸,存在就經過。而後HandlerMethod獲取@Login註解,判斷是否存在,若是存在,就看有沒有memberId,沒有就不經過。
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 IOException {

        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 IOException {
        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;
    }
	
}
複製代碼

運行結果:html

img

img

遇到的坑:

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

  • 一開始我沒想到用過濾器,所以我就嘗試在攔截器裏,直接經過ParameterRequestWrapper對request包裝,後來發現無論我怎麼弄都不成功。當時很是絕望,後來想了想會不會是攔截器不支持從新包裝request,因而我就經過filter去作,沒想到成功了。這時,我想既然用到了filter,那乾脆直接在filter裏面獲取@Login註解和獲取方法參數得了,後來發現filter裏面拿不到方法的信息,哭。後來想到一個辦法,能夠經過先filter,後攔截器。因而就成功了!java

後記:

  • 這篇文章只是記錄了個人一點小小經驗,若是有什麼不對的地方或者有更好的方法,請你們在評論裏留言指正!

參考文章:www.importnew.com/19023.htmlweb

原文連接:www.seifon.cn/2018/04/21/…spring

相關文章
相關標籤/搜索