基于注解的用戶權(quán)限攔截Spring HandlerInterceptor

Spring Boot (v2.0.5.RELEASE)

  • 程序中有些資源(接口)是需要用戶登錄才能夠使用的,或者是具有某種角色的用戶(比如普通登錄用戶,或者系統(tǒng)管理員等)才能使用,本篇文章先為大家講解如何控制使用某接口要求用戶必須登錄。
  • 實(shí)現(xiàn)的思路是
    1. 首先定義注解@LoginUser,該注解用于標(biāo)注哪些接口需要進(jìn)行攔截
    2. 定義攔截器,攔截標(biāo)注了@LoginUser注解的接口
    3. 攔截之后判斷該用戶目前是不是處于登陸狀態(tài),如果是登陸狀態(tài)則放行該請求,如果未登錄則提示登陸
    4. 給方法或者類打上@LoginUser注解進(jìn)行測試
  1. 定義標(biāo)注注解@LoginUser
package com.futao.springmvcdemo.annotation;

import com.futao.springmvcdemo.model.enums.Role;

import java.lang.annotation.*;

/**
 * @author futao
 * Created on 2018/9/19-14:39.
 * 登陸用戶,用戶角色
 */
@Target(value = {
        ElementType.METHOD,
        ElementType.TYPE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginUser {
    /**
     * 要求的用戶角色
     *
     * @return
     */
    Role role() default Role.Normal;
}

2。 定義攔截器LoginUserInterceptor

package com.futao.springmvcdemo.annotation.impl;

import com.alibaba.fastjson.JSON;
import com.futao.springmvcdemo.annotation.LoginUser;
import com.futao.springmvcdemo.model.entity.constvar.ErrorMessage;
import com.futao.springmvcdemo.model.system.RestResult;
import com.futao.springmvcdemo.model.system.SystemConfig;
import com.futao.springmvcdemo.utils.ThreadLocalUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * @author futao
 * Created on 2018/9/19-14:44.
 * 對請求標(biāo)記了LoginUser的方法進(jìn)行攔截
 */
@Component
public class LoginUserInterceptor extends HandlerInterceptorAdapter {

    private static final Logger logger = LoggerFactory.getLogger(LoginUserInterceptor.class);

    @Resource
    private ThreadLocalUtils<String> threadLocalUtils;

    /**
     * 在請求到達(dá)Controller之前進(jìn)行攔截并處理
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            //注解在方法上
            LoginUser loginUserAnnotation = ((HandlerMethod) handler).getMethodAnnotation(LoginUser.class);
            //注解在類上
            LoginUser classLoginUserAnnotation = ((HandlerMethod) handler).getMethod().getDeclaringClass().getAnnotation(LoginUser.class);
            if (ObjectUtils.anyNotNull(loginUserAnnotation, classLoginUserAnnotation)) {
                HttpSession session = request.getSession(false);
                //session不為空
                if (ObjectUtils.allNotNull(session)) {
                    String loginUser = (String) session.getAttribute(SystemConfig.LOGIN_USER_SESSION_KEY);
                    if (ObjectUtils.allNotNull(loginUser)) {
                        System.out.println("當(dāng)前登陸用戶為:" + loginUser);
                        //將當(dāng)前用戶的信息存入threadLocal中
                        threadLocalUtils.set(loginUser);
                    } else {
                        System.out.println("用戶不存在");
                        return false;
                    }
                } else {//session為空,用戶未登錄
                    RestResult restResult = new RestResult(false, "-1", ErrorMessage.NOT_LOGIN, ErrorMessage.NOT_LOGIN.substring(6));
                    response.getWriter().append(JSON.toJSONString(restResult));
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //釋放threadLocal資源
        threadLocalUtils.remove();
    }
}
  1. 注冊攔截器
package com.futao.springmvcdemo.annotation;

import com.futao.springmvcdemo.annotation.impl.LoginUserInterceptor;
import com.futao.springmvcdemo.annotation.impl.RequestLogInterceptor;
import com.futao.springmvcdemo.annotation.impl.SignInterceptor;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

/**
 * @author futao
 * Created on 2018/9/18-15:15.
 */
@SpringBootConfiguration
public class WebMvcConfiguration implements WebMvcConfigurer {
    @Resource
    private SignInterceptor signInterceptor;
    @Resource
    private LoginUserInterceptor loginUserInterceptor;
    @Resource
    private RequestLogInterceptor requestLogInterceptor;

    /**
     * addInterceptor()的順序需要嚴(yán)格按照程序的執(zhí)行的順序
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(requestLogInterceptor).addPathPatterns("/**");
        registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**");
        //  "/**"和"/*"是有區(qū)別的
        registry.addInterceptor(signInterceptor).addPathPatterns("/**");
    }
}
  1. 測試(可分別將注解打在類上和方法上進(jìn)行測試)
package com.futao.springmvcdemo.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.futao.springmvcdemo.annotation.LoginUser;
import com.futao.springmvcdemo.model.entity.User;
import com.futao.springmvcdemo.model.system.SystemConfig;
import com.futao.springmvcdemo.service.UserService;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.UUID;

/**
 * @author futao
 * Created on 2018/9/19-15:05.
 */
@RequestMapping(path = "User", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@RestController
public class UserController {

    @Resource
    private UserService userService;

    /**
     * 獲取當(dāng)前的登陸的用戶信息,其實(shí)是從threadLocal中獲取
     *
     * @return
     */
    @LoginUser
    @GetMapping(path = "my")
    public JSONObject my() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("當(dāng)前的登陸的用戶是:", userService.currentUser());
        return jsonObject;
    }

    /**
     * 模擬登陸接口
     *
     * @param mobile
     * @param request
     * @return
     */
    @PostMapping(path = "login")
    public JSONObject login(
            @RequestParam("mobile") String mobile,
            HttpServletRequest request
    ) {
        HttpSession session = request.getSession();
        session.setAttribute(SystemConfig.LOGIN_USER_SESSION_KEY, String.valueOf(UUID.randomUUID()));
        session.setMaxInactiveInterval(SystemConfig.SESSION_INVALIDATE_SECOND);
        return new JSONObject();
    }
}
  1. 測試
    4.1 未登錄情況下調(diào)用標(biāo)記了@LoginUser的獲取當(dāng)前登陸用戶信息接口
    未登錄

    4.2 登錄
    登錄操作

    4.3 登錄之后調(diào)用調(diào)用標(biāo)記了@LoginUser的獲取當(dāng)前登陸用戶信息接口
    登陸之后

稍微解釋一下上面登陸和獲取用戶信息的邏輯:
用戶請求登陸之后,會為該用戶在系統(tǒng)中生成一個HttpSession,同時在系統(tǒng)中有一個Map來存放所有的session信息,該Mapkey為一個隨機(jī)字符串,valuesession對象在系統(tǒng)中的堆地址,在登陸請求完成之后,系統(tǒng)會將該sesionkey值以cookie(JSESSIONID)的形式寫回瀏覽器。

設(shè)置cookie

用戶下次登陸的時候,請求中會自動帶上該cookie,所以我們在標(biāo)記了需要登陸的@LoginUser注解的請求到達(dá)處理邏輯之前進(jìn)行攔截,就是從cookie中(JSESSIONID)取出sessionkey值,如果沒有該cookie,則代表用戶沒有登陸,如果有該cookie,再在存放cookiemap中取,如果沒有取到,則代表用戶的session已經(jīng)過期了,需要重新登陸,或者cookie是偽造的。
拿到了登陸用戶的session之后,我們?nèi)?code>Map中獲取對應(yīng)的值,一般是用戶的id,在通過這個用戶id,可以去數(shù)據(jù)庫查該用戶的信息,查到用戶的信息之后將用戶信息放入threadLocal中,然后就可以在任何地方get()到當(dāng)前登陸的用戶信息了,非常方便。

使用上面的基于注解的攔截器可以實(shí)現(xiàn)很多功能,比如動態(tài)的第三方接口驗(yàn)簽,和系統(tǒng)日志記錄(不需要注解)等

日志系統(tǒng)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,057評論 25 709
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 14,000評論 2 59
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評論 19 139
  • 在 iOS 11 系統(tǒng)上訪問JS API定位業(yè)務(wù)失敗怎么解決? 蘋果新發(fā)的 iOS 11 操作系統(tǒng)的一大...
    Ruby_min閱讀 8,175評論 5 0

友情鏈接更多精彩內(nèi)容