基於JWT與Redis的權限控制


最近處理的一個需求,讓在管理平台上做一個權限控制,原本打算使用shiro完成,基於項目架構最后選擇使用攔截器 配合jwt以及redis完成;

JWT:

  jwt呢,這里簡單說一下,項目里主要用到的就是token傳遞驗證身份,這里的話,不多介紹jwt使用方法,列幾個網址可以了解一下;

  https://www.cnblogs.com/cjsblog/p/9277677.html

       https://www.cnblogs.com/lyzg/p/6028341.html

 

Demo目錄:(沒法傳圖片,手寫一下)

  controller -> LoginController     filter -> FilterConfiguration,LoginFilter      util ->jwt,redis    jwt->JwtUtil  redis->redisUtil,redisConfig      util ->CommonUtil,Constants,StringUtil

  

LoginController   (登錄接口)

@RequestMapping(value = "/fly/login")   
 public Map<String,Object> login(@RequestBody Map<String,Object> params) {
//根據params判斷用戶是否存在(可以利用第三方) //代碼省略 String userName = params.get("userName").toString(); //定義返回map Map<String,Object> returnMap = new HashMap<>(); //定義請求路徑(這一塊會有很多邏輯操作,這里簡化成簡單的從數據庫查詢結果,具體使用自行封裝) List<String> urls = new ArrayList<>(); urls = service.gainUrlFromDB(userName); //(這里做法是將數據庫里對應到用戶的權限全部查出來)      //使用JWT生成token 放入返回Map內(這一塊是驗證身份信息以及token過期時間用到的) Map<String, Object> map = Maps.newHashMap(); map.put("username", userName); returnMap.put("token", JwtUtil.createToken(map)); //將用戶數據,權限放入redis(過期時間2小時) Map<String,Object> userRedisMsg = new HashMap<>(); userRedisMsg.put("url",urls); userRedisMsg.put("timeout",System.currentTimeMillis() + 2*60*60*1000); redisUtil.set(userName, userRedisMsg,2*60*60*1000L, TimeUnit.MILLISECONDS);
     //保存用戶名信息
     request.getSession().setAttribute("userName", userName);

    return returnMap;
}

JwtUtil

public class JwtUtil {
    private static final byte[] SECRET = Constants.TOKEN_SECRET.getBytes();

    private static final JWSHeader HEADER = new JWSHeader(
            JWSAlgorithm.HS256, JOSEObjectType.JWT, null, null, null, null,
            null, null, null, null, null, null, null);

    /**
     * 生成token,該方法只在用戶登錄成功后調用
     * @param payload Map集合,可以存儲用戶id,token生成時間,token過期時間等自定義字段
     * @return token字符串,若失敗則返回null
     */
    public static String createToken(Map<String, Object> payload) {
        String tokenString = null;
        // 創建一個JWS Object(第二部分)
        JWSObject jwsObject = new JWSObject(HEADER, new Payload(new JSONObject(payload)));
        try {
            // 將jwsObject進行HMAC簽名,相當於加密(第三部分)
            jwsObject.sign(new MACSigner(SECRET));
            tokenString = jwsObject.serialize();
        } catch (JOSEException e) {
            log.error("簽名失敗: {}", e.getMessage());
            e.printStackTrace();
        }
        return tokenString;
    }

    /**
     * 校驗token是否合法,返回Map集合,集合中主要包含
     * code狀態碼
     * data鑒權成功后從token中提取的數據
     * 該方法在過濾器中調用,每次請求API時都校驗
     * @param token token
     * @return Map<String, Object>
     */
    public static Map<String, Object> checkToken(String token,String url) {
        Map<String, Object> resultMap = Maps.newHashMap();
        try {
            JWSObject jwsObject = JWSObject.parse(token);
            // palload就是JWT構成的第二部分不過這里自定義的是私有聲明(標准中注冊的聲明, 公共的聲明)
            Payload payload = jwsObject.getPayload();
            JWSVerifier verifier = new MACVerifier(SECRET);
       //token校驗
if(jwsObject.verify(verifier)) { JSONObject jsonObject = payload.toJSONObject(); // token檢驗成功(此時沒有檢驗是否過期) resultMap.put("username", jsonObject.get("username")); RedisUtil redisUtil = (RedisUtil) SpringContextUtil.getBean("redisUtil"); long time = System.currentTimeMillis(); HashMap<String, Object> userMap = (HashMap)redisUtil.get(resultMap.get("username")); if(time > (long)userMap.get("timeout")){ resultMap.put("msg", "token過期"); }else if(!((Set)userMap.get("url")).contains(url)) { resultMap.put("msg", "用戶權限不足"); }else { //這里就是權限通過,需要重置redis過期時間 resultMap.put("msg", "具有權限"); Map<String,Object> mp = new HashMap<>(); mp.put("url", userMap.get("url")); mp.put("timeout", System.currentTimeMillis() + 2*60*60*1000); redisUtil.set( resultMap.get("username"), mp, 2*60*60*1000, TimeUnit.MILLISECONDS); } resultMap.put("data", jsonObject); } else { // 檢驗失敗 resultMap.put("msg","驗證失敗"); } } catch (Exception e) { e.printStackTrace(); // token格式不合法導致的異常 resultMap.clear(); resultMap.put("msg", "token格式不合法"); } return resultMap; } }

FilterConfiguration(攔截器)

@Configuration
public class FilterConfiguration {


    @Bean
    public FilterRegistrationBean loginFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setName("loginFilter");
        registration.setFilter(new LoginFilter());
        registration.addUrlPatterns("/study/*");
        registration.setOrder(Integer.MAX_VALUE);
        return registration;
    }
}

LoginFilter(自定義攔截器)     (代碼里的很多文字內容  例如msg字段對應的應該做成一個常量類來封裝,此處寫法只為易懂)

public class LoginFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            String path = request.getRequestURI();
            String reqUrl = path.substring(path.indexOf("/", 0));
            //過濾部分請求
            if (!StringUtil.matches(reqUrl, "/study/fly/login")) {
                String token = request.getHeader("Authorization");
                Map<String, Object> resultMap = JwtUtil.validToken(token, path);

                String msg = CommonUtil.toString(resultMap.get("msg"));
                switch (msg) {
                    case "具有權限":
                        // 取出payload中數據,放到request作用域中
                        request.setAttribute("jwt", resultMap.get("data"));
                        break;
                    case "用戶權限不足":
                        request.setAttribute("msg", "您的權限不足");
                        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "權限不足");
                        return;
                    case "token格式不合法":
                    case "token過期":
                        request.setAttribute("msg", "您的token不合法或者過期了,請重新登陸");
                        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "token不合法或者過期了");
                        return;
                    default:
                        break;
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            e.printStackTrace();
        }
        filterChain.doFilter(request, response);
    }
}

 

 

redis的代碼就不貼出來了,寫demo只簡單封裝了下,防止誤導;可以自行度娘一下,需要注意序列化;

數據庫表,需要一個 菜單權限表(table_menu)角色信息表(table_role)   用戶角色關聯表(table_user_role)    角色權限關聯表(table_role_menu)

當權限細致到接口級別(按鈕級)菜單權限表需要將菜單 Id 與  請求url關聯起來,也就是說,table_menu表中需要一個請求url的字段

這里對這部分代碼解釋一下:

首先呢,在用戶登陸的時候,進行一個用戶登錄校驗,這一塊可以自己寫一套驗證,也可以借助第三方完成;然后,根據用戶id去數據庫查出當前用戶所具有的角色權限(超級管理員,普通用戶),然后再根據角色權限去 菜單權限表取出對應的url,將這些url以及redis過期時間一並打包放到redis里,每次用戶發送接口請求,自定義攔截器會攔截請求並 依次做

token 校驗、token過期校驗、權限校驗、redis過期時間更新操作;當完成校驗,攔截器就會根據response是否拋出異常信息來判斷是否具有調用接口的權限;

 

 

    

 

          

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM