ssm中使用攔截器跟自定義註解實現前端token校驗

最近做的一個項目用的是前後端分離的開發模式,系統是要登錄後才能進行操作的,所以需要進行身份token校驗,校驗通過後才能得到所請求的資源。我一開始想到的是使用過濾器實現,但系統裏的有些controller是不用過濾的,比如一個新增用戶的頁面,有很多個下拉框,那請求下拉框數據的時候,就不用每個controller都進行校驗了。

於是就用了攔截器+自定義註解來實現,思路如下:用戶登錄成功後生成一個jwt,也就是token,然後存到Redis裏去;每次發起請求時首先判斷該controller上有沒有自定義的註解,如果有則跳過token驗證,如果沒有則不跳過,然後去Redis裏取token,取到了則驗證成功,取不到則驗證失敗

自定義註解代碼如下(ExcludeInterceptor.java):

package com.ue.core.web.interceptor;

import java.lang.annotation.*;

/**
 * 用於排除不用進行jwt驗證的自定義註解
 * @author LiJun
 * @Date 2019年09月28日
 * @Time 11:08
 */
@Target(ElementType.METHOD)//指定被修飾的Annotation可以放置的位置:方法上面
@Retention(RetentionPolicy.RUNTIME)//註解會在class字節碼文件中存在,在運行時可以通過反射獲取到
@Inherited//指定被修飾的Annotation將具有繼承性
public @interface ExcludeInterceptor {
}

攔截器相關邏輯如下(JwtInterceptor.java):

package com.ue.core.web.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.ue.core.dao.UserDao;
import com.ue.core.service.JwtService;
import com.ue.core.util.BeanUtil;
import com.ue.core.util.JedisUtil;
import com.ue.core.util.JsonRespData;
import com.ue.core.util.SpringContextHolder;
import com.ue.core.web.interceptor.ExcludeInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import redis.clients.jedis.Jedis;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;

import static com.ue.core.entity.CommonEntity.*;

/**
 * 用於處理jwt驗證的攔截器
 * @author LiJun
 * @Date 2019年09月27日
 * @Time 18:06
 */
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
    private UserDao userDao;//定義用戶的dao層
    private JwtService jwtService;
    private boolean OFF = false;//true爲關閉jwt令牌驗證功能

    /**
     * 返回 false:請求被攔截,返回
     * 返回 true :請求OK,可以繼續執行,放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        Jedis jedis = null;
        try {
            //獲取到目標方法對象
            HandlerMethod method = (HandlerMethod) o;
            //拿到方法上的註解
            ExcludeInterceptor methodAnnotation = method.getMethodAnnotation(ExcludeInterceptor.class);
            if (OFF || BeanUtil.isNotBlank(methodAnnotation)) {//如果該方法上有自定義的註解,則直接跳過這個攔截器
                return true;
            }

            jedis = JedisUtil.getJedisConn();
            String jwt = request.getHeader("jwt");//請求頭裏的jwt令牌
            String userId = request.getParameter("tokenId");//登錄者的ID

            jwtService = SpringContextHolder.getBean(JwtService.class);//從靜態變量applicationContext中取到JwtService
            userDao = SpringContextHolder.getBean(UserDao.class);//從靜態變量applicationContext中取到UserDao

            if (StringUtils.isNotBlank(jwt)){//jwt不爲空
                if (StringUtils.isBlank(jedis.get(jwt))){//如果Redis裏沒有這個jwt,說明jwt已經失效
                    returnErrorResponse(response,JsonRespData.success(REQUEST_204, "token已過期"));
                    return false;
                }
                else {//jwt沒有失效
                    Map user = userDao.getUserById(userId);
                    String isDelete = String.valueOf(user.get("is_delete"));
                    String state = String.valueOf(user.get("state"));

                    if ("N".equals(isDelete) && "2".equals(state)) {//該用戶沒有被邏輯刪除且審覈已通過
                        DecodedJWT decodedJWT = jwtService.getDecryptString(jwt);//token解密id
                        String id = decodedJWT.getClaim("id").asString();
                        if (StringUtils.isNotBlank(userId) && userId.equals(id)) {
                            jedis.expire(jwt, 1800);//驗證用戶的登錄狀態成功,token有效期重置爲30分鐘
                            return true;
                        } else {
                            returnErrorResponse(response,JsonRespData.success(REQUEST_203, "用戶登錄異常,用戶id前後不一致"));
                            return false;
                        }
                    } else {
                        log.info("帳號已被管理員禁用,token有效期不重置");
                        returnErrorResponse(response,JsonRespData.success("202", "該帳號暫時不能進行登錄"));
                        return false;
                    }
                }
            }
            else {
                log.info("入參jwt爲空");
                returnErrorResponse(response,JsonRespData.success(REQUEST_205, "token不允許爲空"));
                return false;
            }
        }catch (Exception e){
            log.info("驗證jwt出現異常:" + e);
            returnErrorResponse(response,JsonRespData.success(INTERNAL_SERVER_ERROR, "驗證jwt出現異常"));
            return false;
        }finally {
            if (jedis != null)
                jedis.close();
        }
    }

    /**
     * 發送jwt的驗證結果到客戶端
     * @author LiJun
     * @Date 2019/9/28
     * @Time 15:13
     * @param response
     * @param result
     * @return void
     */
    private void returnErrorResponse(HttpServletResponse response, JsonRespData result) throws IOException {
        OutputStream out = null;
        try{
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/json");
            out = response.getOutputStream();
            out.write(JSONObject.toJSONString(result).getBytes("utf-8"));
            out.flush();
        } finally{
            if(out != null){
                out.close();
            }
        }
    }

    /**
     * 請求controller之後,渲染視圖之前
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 請求controller之後,視圖渲染之後
     */
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

在springmvc中配置攔截器(spring-web.xml):

<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.ue.core.web.interceptor.JwtInterceptor"></bean>
        </mvc:interceptor>
</mvc:interceptors>

登錄相關代碼如下:

UserController.java:

    /**
     * APP端登錄驗證
     * @author LiJun
     * @Date 2019/9/25
     * @Time 9:35
     * @param user
     * @return com.ue.core.util.JsonRespData
     */
    @RequestMapping(value = "login",method = RequestMethod.POST)
    @ResponseBody
    @ExcludeInterceptor
    @ApiOperation(value = "APP端登錄驗證", notes = "登錄", response = JsonRespData.class)
    public JsonRespData login(User user){
        return userService.login(user);
    }

UserService.java:

    /**
     * APP端登錄驗證
     * @author LiJun
     * @Date 2019/9/25
     * @Time 9:35
     * @param user
     * @return com.ue.core.util.JsonRespData
     */
    public JsonRespData login(User user){
        String name = user.getName();//用戶輸入的用戶名
        String password = user.getPassword();//用戶輸入的密碼
        User userInfo = userDao.getPwdByName(name);
        if (BeanUtil.isNotBlank(userInfo)) {
            Integer state = userInfo.getState();
            String isDelete = userInfo.getIsDelete();
            if (state != 2) {//如果用戶狀態不是“已通過”
                //0:禁用、1:待審覈、2:審覈已通過、3:審覈不通過
                return JsonRespData.success(REQUEST_208, "該帳號暫時不能進行登錄");
            }
            else if ("Y".equals(isDelete)){//如果用戶已被刪除
                //Y:未刪除、N:已刪除
                return JsonRespData.success(REQUEST_209, "賬號已刪除");
            }
            else {
                Jedis jedis = null;
                try {
                    String pwd = userInfo.getPassword();
                    if (pwd.equals(password)) {//登陸成功,添加token到redis管理
                        jedis = JedisUtil.getJedisConn();
                        String id = userInfo.getId().toString();
                        String accessUrl = "暫未指定";
                        String token = jwtService.getEncryptString(id, accessUrl);//生成token
                        jedis.set(token, token);//將token保存到Redis裏
                        jedis.expire(token, 3600);
                        userInfo.setToken(token);
                        return JsonRespData.success(REQUEST_SUCCESS, "登錄成功", userInfo);
                    } else {//登錄失敗
                        return JsonRespData.success(REJECT_REQUEST, "帳號或密碼錯誤");
                    }
                } catch (Exception e) {
                    log.error("登錄驗證發生異常:" + e);
                    return JsonRespData.success(INTERNAL_SERVER_ERROR, "服務器發生錯誤");
                }finally {
                    if (jedis != null)
                        jedis.close();
                }
            }
        } else {
            return JsonRespData.success(REJECT_REQUEST, "帳號未註冊");
        }
    }

自定義註解的使用:

使用自定義註解期間遇到的類型轉換異常:

java.lang.ClassCastException: org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler cannot be cast to org.springframework.web.method.HandlerMethod

這是JwtInterceptor.java中HandlerMethod method = (HandlerMethod) o強制轉換時的異常,出現原因是:所請求的資源不存在,也就是說系統中沒有所請求的controller

解決方案:在強轉前加上if (o instanceof HandlerMethod)判斷一下,如果請求的資源不存在則不進行處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章