基于注解的用戶權(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)則放行該請(qǐng)求,如果未登錄則提示登陸
    4. 給方法或者類打上@LoginUser注解進(jìn)行測(cè)試
  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.
 * 對(duì)請(qǐng)求標(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;

    /**
     * 在請(qǐng)求到達(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. 注冊(cè)攔截器
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. 測(cè)試(可分別將注解打在類上和方法上進(jìn)行測(cè)試)
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. 測(cè)試
    4.1 未登錄情況下調(diào)用標(biāo)記了@LoginUser的獲取當(dāng)前登陸用戶信息接口
    未登錄

    4.2 登錄
    登錄操作

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

稍微解釋一下上面登陸和獲取用戶信息的邏輯:
用戶請(qǐng)求登陸之后挨务,會(huì)為該用戶在系統(tǒng)中生成一個(gè)HttpSession击你,同時(shí)在系統(tǒng)中有一個(gè)Map來(lái)存放所有的session信息玉组,該Mapkey為一個(gè)隨機(jī)字符串,valuesession對(duì)象在系統(tǒng)中的堆地址丁侄,在登陸請(qǐng)求完成之后惯雳,系統(tǒng)會(huì)將該sesionkey值以cookie(JSESSIONID)的形式寫回瀏覽器。

設(shè)置cookie

用戶下次登陸的時(shí)候鸿摇,請(qǐng)求中會(huì)自動(dòng)帶上該cookie石景,所以我們?cè)跇?biāo)記了需要登陸的@LoginUser注解的請(qǐng)求到達(dá)處理邏輯之前進(jìn)行攔截,就是從cookie中(JSESSIONID)取出sessionkey值拙吉,如果沒(méi)有該cookie潮孽,則代表用戶沒(méi)有登陸,如果有該cookie筷黔,再在存放cookiemap中取往史,如果沒(méi)有取到,則代表用戶的session已經(jīng)過(guò)期了佛舱,需要重新登陸椎例,或者cookie是偽造的。
拿到了登陸用戶的session之后请祖,我們?nèi)?code>Map中獲取對(duì)應(yīng)的值订歪,一般是用戶的id,在通過(guò)這個(gè)用戶id肆捕,可以去數(shù)據(jù)庫(kù)查該用戶的信息刷晋,查到用戶的信息之后將用戶信息放入threadLocal中,然后就可以在任何地方get()到當(dāng)前登陸的用戶信息了慎陵,非常方便眼虱。

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

日志系統(tǒng)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蒙幻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子胆筒,更是在濱河造成了極大的恐慌邮破,老刑警劉巖诈豌,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異抒和,居然都是意外死亡矫渔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門摧莽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)庙洼,“玉大人,你說(shuō)我怎么就攤上這事镊辕∮凸唬” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵征懈,是天一觀的道長(zhǎng)石咬。 經(jīng)常有香客問(wèn)我,道長(zhǎng)卖哎,這世上最難降的妖魔是什么汽纤? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任三椿,我火速辦了婚禮庸论,結(jié)果婚禮上嫉戚,老公的妹妹穿的比我還像新娘。我一直安慰自己维贺,他們只是感情好它掂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著幸缕,像睡著了一般群发。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上发乔,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天熟妓,我揣著相機(jī)與錄音,去河邊找鬼栏尚。 笑死起愈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的译仗。 我是一名探鬼主播抬虽,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纵菌!你這毒婦竟也來(lái)了阐污?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咱圆,失蹤者是張志新(化名)和其女友劉穎笛辟,沒(méi)想到半個(gè)月后功氨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡手幢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年捷凄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片围来。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跺涤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出监透,到底是詐尸還是另有隱情桶错,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布才漆,位于F島的核電站牛曹,受9級(jí)特大地震影響佛点,放射性物質(zhì)發(fā)生泄漏醇滥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一超营、第九天 我趴在偏房一處隱蔽的房頂上張望鸳玩。 院中可真熱鬧,春花似錦演闭、人聲如沸不跟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)窝革。三九已至,卻和暖如春吕座,著一層夾襖步出監(jiān)牢的瞬間虐译,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工吴趴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漆诽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓锣枝,卻偏偏與公主長(zhǎng)得像厢拭,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撇叁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

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