使用springboot和redis實現redis權限認證

一、引言

登錄權限控制是很多系統具備的功能,實現這一功能的方式有很多,其中使用token是現在用的比較多的

好處:可以防止CSRF攻擊

二、功能實現:

用戶登錄成功後,後臺生成一個token並存在redis中,同時給此用戶的token設置時限,返回一個token給調用者,同時自定義一個@AuthToken註解,被該註解標註的API請求都需要進行token效驗,效驗通過纔可以正常訪問,實現接口級的鑑權控制。同時token具有生命週期,在用戶持續一段時間不進行操作的話,token則會過期,用戶一直操作的話,則不會過期。

具體流程:

1、 用戶輸入賬號密碼,發送請求給後臺,後臺生成一個token,將用戶的用戶名或者手機號等跟token綁定存入redis中,並且設置過期時間,同時將當前時間戳也存入redis中


    @PostMapping(value = "login")
    public ResponseTemplate login(String phone, String password) {

        User user = userService.getUser(phone,password);

        JSONObject result = new JSONObject();

        if (user != null) {
            Jedis jedis = new Jedis("localhost", 6379);
            String token = tokenGenerator.generate(phone, password);
            //設置token以及過期時間
            jedis.set(token, phone);
            //設置key生存時間,當key過期時,它會被自動刪除,時間是秒
            jedis.expire(token, ConstantKit.TOKEN_EXPIRE_TIME);
            //記錄設置token的時間,以便於後續調用時分析token是否過期
            Long currentTime = System.currentTimeMillis();
            jedis.set(token + phone, currentTime.toString());
            jedis.expire(token+phone,ConstantKit.TOKEN_EXPIRE_TIME);
            //用完關閉
            jedis.close();
            result.put("token", token);
            result.put("status",1);
        } else {
            result.put("status",0);
        }
        return ResponseTemplate.builder()
                .code(200)
                .message("OK")
                .data(result)
                .build();

2、每次請求接口都會走攔截器,如果該接口標註了@AuthToken註解,則要檢查客戶端傳過來的Authorization字段,獲取 token。並且根據token是否存在redis中,如果可以獲取則說明 token 正確,反之,說明錯誤,返回鑑權失敗。

3、token可以根據用戶使用的情況來動態的調整自己過期時間。在生成 token 的同時也往 redis 裏面存入了創建 token 時的時間戳,每次請求被攔截器攔截 token 驗證成功之後,將當前時間與存在 redis 裏面的 token 生成時刻的時間戳進行比較,噹噹前時間的距離創建時間快要到達設置的redis過期時間的話,就重新設置token過期時間,將過期時間延長。如果用戶在設置的 redis 過期時間的時間長度內沒有進行任何操作(沒有發請求),則token會在redis中過期。

public class AuthorizationInterceptor implements HandlerInterceptor {

    //存放鑑權信息的Header名稱,默認是Authorization
    private String httpHeaderName = "Authorization";

    //鑑權失敗後返回的錯誤信息,默認爲401 unauthorized
    private String unauthorizedErrorMessage = "401 unauthorized";

    //鑑權失敗後返回的HTTP錯誤碼,默認爲401
    private int unauthorizedErrorCode = HttpServletResponse.SC_UNAUTHORIZED;

    /**
     * 存放登錄用戶模型Key的Request Key
     */
    public static final String REQUEST_CURRENT_KEY = "REQUEST_CURRENT_KEY";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        // 如果打上了AuthToken註解則需要驗證token
        if (method.getAnnotation(AuthToken.class) != null || handlerMethod.getBeanType().getAnnotation(AuthToken.class) != null) {

            //從請求體中取出token
            //String token = request.getParameter(httpHeaderName);
            //從請求頭中取出token
            String token = request.getHeader(httpHeaderName);

            log.info("Get token from request is {} ", token);
            String phone = "";
            Jedis jedis = new Jedis("localhost", 6379);
            if (token != null && token.length() != 0) {
                phone = jedis.get(token);
                log.info("Get username from Redis is {}", phone);
            }
            if (phone != null && !phone.trim().equals("")) {
                Long tokeBirthTime = Long.valueOf(jedis.get(token + phone));
                log.info("token Birth time is: {}", tokeBirthTime);
                Long diff = System.currentTimeMillis() - tokeBirthTime;
                log.info("token is exist : {} ms", diff);
                //重新設置Redis中的token過期時間
                if (diff > ConstantKit.TOKEN_RESET_TIME) {
                    jedis.expire(token, ConstantKit.TOKEN_EXPIRE_TIME);
                    log.info("Reset expire time success!");
                    Long newBirthTime = System.currentTimeMillis();
                    jedis.set(token + phone, newBirthTime.toString());
                    jedis.expire(token + phone,ConstantKit.TOKEN_EXPIRE_TIME);
                }

                //用完關閉
                jedis.close();
                request.setAttribute(REQUEST_CURRENT_KEY, phone);
                return true;
            } else {
                JSONObject jsonObject = new JSONObject();

                PrintWriter out = null;
                try {
                    response.setStatus(unauthorizedErrorCode);
                    response.setContentType(MediaType.APPLICATION_JSON_VALUE);

                    jsonObject.put("code", ((HttpServletResponse) response).getStatus());
                    jsonObject.put("message", HttpStatus.UNAUTHORIZED);
                    out = response.getWriter();
                    out.println(jsonObject);

                    return false;
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (null != out) {
                        out.flush();
                        out.close();
                    }
                }

            }
        }
        request.setAttribute(REQUEST_CURRENT_KEY, null);
        return true;
    }
}

登錄成功

當沒有權限時調用接口

 

代碼demo: github地址

 

 

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