亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

服務(wù)網(wǎng)關(guān) Zuul 與 Redis 結(jié)合實(shí)現(xiàn) Token 權(quán)限校驗(yàn)

flyer_dev / 3772人閱讀

摘要:項(xiàng)目地址本文將分四部分介紹登錄邏輯前置過(guò)濾器校驗(yàn)邏輯工具類演示驗(yàn)證一登錄邏輯登錄成功后,將生成的存儲(chǔ)在中。鍵是用戶值是二前置過(guò)濾器繼承自,必須實(shí)現(xiàn)的四個(gè)方法。

這兩天在寫項(xiàng)目的全局權(quán)限校驗(yàn),用 Zuul 作為服務(wù)網(wǎng)關(guān),在 Zuul 的前置過(guò)濾器里做的校驗(yàn)。

權(quán)限校驗(yàn)或者身份驗(yàn)證就不得不提 Token,目前 Token 的驗(yàn)證方式有很多種,有生成 Token 后將 Token 存儲(chǔ)在 Redis 或數(shù)據(jù)庫(kù)的,也有很多用 JWT(JSON Web Token)的。

說(shuō)實(shí)話這方面我的經(jīng)驗(yàn)不多,又著急趕項(xiàng)目,所以就先用個(gè)簡(jiǎn)單的方案。

登錄成功后將 Token 返回給前端,同時(shí)將 Token 存在 Redis 里。每次請(qǐng)求接口都從 Cookie 或 Header 中取出 Token,在從 Redis 中取出存儲(chǔ)的 Token,比對(duì)是否一致。

我知道這方案不是最完美的,還有安全性問(wèn)題,容易被劫持。但目前的策略是先把項(xiàng)目功能做完,上線之后再慢慢優(yōu)化,不在一個(gè)功能點(diǎn)上扣的太細(xì),保證項(xiàng)目進(jìn)度不至于太慢。

項(xiàng)目地址:https://github.com/cachecats/...

本文將分四部分介紹

登錄邏輯

AuthFilter 前置過(guò)濾器校驗(yàn)邏輯

工具類

演示驗(yàn)證

一、登錄邏輯

登錄成功后,將生成的 Token 存儲(chǔ)在 Redis 中。用 String 類型的 key, value 格式存儲(chǔ),key是 TOKEN_userId,如果用戶的 userId 是 222222,那鍵就是 TOKEN_222222;值是生成的 Token。

只貼出登錄的 Serive 代碼

@Override
public UserInfoDTO loginByEmail(String email, String password) {

    if (StringUtils.isEmpty(email) || StringUtils.isEmpty(password)) {
        throw new UserException(ResultEnum.EMAIL_PASSWORD_EMPTY);
    }

    UserInfo user = userRepository.findUserInfoByEmail(email);
    if (user == null) {
        throw new UserException(ResultEnum.EMAIL_NOT_EXIST);
    }
    if (!user.getPassword().equals(password)) {
        throw new UserException(ResultEnum.PASSWORD_ERROR);
    }

    //生成 token 并保存在 Redis 中
    String token = KeyUtils.genUniqueKey();
    //將token存儲(chǔ)在 Redis 中。鍵是 TOKEN_用戶id, 值是token
    redisUtils.setString(String.format(RedisConsts.TOKEN_TEMPLATE, user.getId()), token, 2l, TimeUnit.HOURS);

    UserInfoDTO dto = new UserInfoDTO();
    BeanUtils.copyProperties(user, dto);
    dto.setToken(token);

    return dto;
}
二、AuthFilter 前置過(guò)濾器

AuthFilter 繼承自 ZuulFilter,必須實(shí)現(xiàn) ZuulFilter 的四個(gè)方法。

filterType(): Filter 的類型,前置過(guò)濾器返回 PRE_TYPE

filterOrder(): Filter 的順序,值越小越先執(zhí)行。這里的寫法是 PRE_DECORATION_FILTER_ORDER - 1, 也是官方建議的寫法。

shouldFilter(): 是否應(yīng)該過(guò)濾。返回 true 表示過(guò)濾,false 不過(guò)濾??梢栽谶@個(gè)方法里判斷哪些接口不需要過(guò)濾,本例排除了注冊(cè)和登錄接口,除了這兩個(gè)接口,其他的都需要過(guò)濾。

run(): 過(guò)濾器的具體邏輯

為了方便前端,考慮到要給 pc、app、小程序等不同平臺(tái)提供服務(wù),token 設(shè)置在 cookie 和 header 任選一均可,會(huì)先從 cookie 中取,cookie 中沒(méi)有再?gòu)?header 中取。

package com.solo.coderiver.gateway.filter;

import com.google.gson.Gson;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.solo.coderiver.gateway.VO.ResultVO;
import com.solo.coderiver.gateway.consts.RedisConsts;
import com.solo.coderiver.gateway.utils.CookieUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * 權(quán)限驗(yàn)證 Filter
 * 注冊(cè)和登錄接口不過(guò)濾
 *
 * 驗(yàn)證權(quán)限需要前端在 Cookie 或 Header 中(二選一即可)設(shè)置用戶的 userId 和 token
 * 因?yàn)?token 是存在 Redis 中的,Redis 的鍵由 userId 構(gòu)成,值是 token
 * 在兩個(gè)地方都沒(méi)有找打 userId 或 token其中之一,就會(huì)返回 401 無(wú)權(quán)限,并給與文字提示
 */
@Slf4j
@Component
public class AuthFilter extends ZuulFilter {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    //排除過(guò)濾的 uri 地址
    private static final String LOGIN_URI = "/user/user/login";
    private static final String REGISTER_URI = "/user/user/register";

    //無(wú)權(quán)限時(shí)的提示語(yǔ)
    private static final String INVALID_TOKEN = "invalid token";
    private static final String INVALID_USERID = "invalid userId";

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        log.info("uri:{}", request.getRequestURI());
        //注冊(cè)和登錄接口不攔截,其他接口都要攔截校驗(yàn) token
        if (LOGIN_URI.equals(request.getRequestURI()) ||
                REGISTER_URI.equals(request.getRequestURI())) {
            return false;
        }
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        //先從 cookie 中取 token,cookie 中取失敗再?gòu)?header 中取,兩重校驗(yàn)
        //通過(guò)工具類從 Cookie 中取出 token
        Cookie tokenCookie = CookieUtils.getCookieByName(request, "token");
        if (tokenCookie == null || StringUtils.isEmpty(tokenCookie.getValue())) {
            readTokenFromHeader(requestContext, request);
        } else {
            verifyToken(requestContext, request, tokenCookie.getValue());
        }

        return null;
    }

    /**
     * 從 header 中讀取 token 并校驗(yàn)
     */
    private void readTokenFromHeader(RequestContext requestContext, HttpServletRequest request) {
        //從 header 中讀取
        String headerToken = request.getHeader("token");
        if (StringUtils.isEmpty(headerToken)) {
            setUnauthorizedResponse(requestContext, INVALID_TOKEN);
        } else {
            verifyToken(requestContext, request, headerToken);
        }
    }

    /**
     * 從Redis中校驗(yàn)token
     */
    private void verifyToken(RequestContext requestContext, HttpServletRequest request, String token) {
        //需要從cookie或header 中取出 userId 來(lái)校驗(yàn) token 的有效性,因?yàn)槊總€(gè)用戶對(duì)應(yīng)一個(gè)token,在Redis中是以 TOKEN_userId 為鍵的
        Cookie userIdCookie = CookieUtils.getCookieByName(request, "userId");
        if (userIdCookie == null || StringUtils.isEmpty(userIdCookie.getValue())) {
            //從header中取userId
            String userId = request.getHeader("userId");
            if (StringUtils.isEmpty(userId)) {
                setUnauthorizedResponse(requestContext, INVALID_USERID);
            } else {
                String redisToken = stringRedisTemplate.opsForValue().get(String.format(RedisConsts.TOKEN_TEMPLATE, userId));
                if (StringUtils.isEmpty(redisToken) || !redisToken.equals(token)) {
                    setUnauthorizedResponse(requestContext, INVALID_TOKEN);
                }
            }
        } else {
            String redisToken = stringRedisTemplate.opsForValue().get(String.format(RedisConsts.TOKEN_TEMPLATE, userIdCookie.getValue()));
            if (StringUtils.isEmpty(redisToken) || !redisToken.equals(token)) {
                setUnauthorizedResponse(requestContext, INVALID_TOKEN);
            }
        }
    }


    /**
     * 設(shè)置 401 無(wú)權(quán)限狀態(tài)
     */
    private void setUnauthorizedResponse(RequestContext requestContext, String msg) {
        requestContext.setSendZuulResponse(false);
        requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());

        ResultVO vo = new ResultVO();
        vo.setCode(401);
        vo.setMsg(msg);
        Gson gson = new Gson();
        String result = gson.toJson(vo);

        requestContext.setResponseBody(result);
    }
}
三、工具類

MD5 工具類

package com.solo.coderiver.user.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 生成 MD5 的工具類
 */
public class MD5Utils {

    public static String getMd5(String plainText) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());
            byte b[] = md.digest();

            int i;

            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++) {
                i = b[offset];
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            //32位加密
            return buf.toString();
            // 16位的加密
            //return buf.toString().substring(8, 24);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 加密解密算法 執(zhí)行一次加密,兩次解密
     */
    public static String convertMD5(String inStr){

        char[] a = inStr.toCharArray();
        for (int i = 0; i < a.length; i++){
            a[i] = (char) (a[i] ^ "t");
        }
        String s = new String(a);
        return s;

    }
}

生成 key 的工具類

package com.solo.coderiver.user.utils;

import java.util.Random;

public class KeyUtils {

    /**
     * 產(chǎn)生獨(dú)一無(wú)二的key
     */
    public static synchronized String genUniqueKey(){
        Random random = new Random();
        int number = random.nextInt(900000) + 100000;
        String key = System.currentTimeMillis() + String.valueOf(number);
        return MD5Utils.getMd5(key);
    }
}
四、演示驗(yàn)證

在 8084 端口啟動(dòng) api_gateway 項(xiàng)目,同時(shí)啟動(dòng) user 項(xiàng)目。

用 postman 通過(guò)網(wǎng)關(guān)訪問(wèn)登錄接口,因?yàn)檫^(guò)濾器對(duì)登錄和注冊(cè)接口排除了,所以不會(huì)校驗(yàn)這兩個(gè)接口的 token。

可以看到,訪問(wèn)地址 http://localhost:8084/user/user/login 登錄成功并返回了用戶信息和 token。

此時(shí)應(yīng)該把 token 存入 Redis 中了,用戶的 id 是 111111 ,所以鍵是 TOKEN_111111,值是剛生成的 token 值

再來(lái)隨便請(qǐng)求一個(gè)其他的接口,應(yīng)該走過(guò)濾器。

header 中不傳 token 和 userId,返回 401

只傳 token 不傳 userId,返回401并提示 invalid userId

token 和 userId 都傳,但 token 不對(duì),返回401,并提示 invalid token

同時(shí)傳正確的 token 和 userId,請(qǐng)求成功

以上就是簡(jiǎn)單的 Token 校驗(yàn),如果有更好的方案歡迎在評(píng)論區(qū)交流

代碼出自開(kāi)源項(xiàng)目 CodeRiver,致力于打造全平臺(tái)型全棧精品開(kāi)源項(xiàng)目。

coderiver 中文名 河碼,是一個(gè)為程序員和設(shè)計(jì)師提供項(xiàng)目協(xié)作的平臺(tái)。無(wú)論你是前端、后端、移動(dòng)端開(kāi)發(fā)人員,或是設(shè)計(jì)師、產(chǎn)品經(jīng)理,都可以在平臺(tái)上發(fā)布項(xiàng)目,與志同道合的小伙伴一起協(xié)作完成項(xiàng)目。

coderiver河碼 類似程序員客棧,但主要目的是方便各細(xì)分領(lǐng)域人才之間技術(shù)交流,共同成長(zhǎng),多人協(xié)作完成項(xiàng)目。暫不涉及金錢交易。

計(jì)劃做成包含 pc端(Vue、React)、移動(dòng)H5(Vue、React)、ReactNative混合開(kāi)發(fā)、Android原生、微信小程序、java后端的全平臺(tái)型全棧項(xiàng)目,歡迎關(guān)注。

項(xiàng)目地址:https://github.com/cachecats/...

您的鼓勵(lì)是我前行最大的動(dòng)力,歡迎點(diǎn)贊,歡迎送小星星? ~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/72270.html

相關(guān)文章

  • 你想要的全平臺(tái)全棧開(kāi)源項(xiàng)目-Vue、React、小程序、安卓、ReactNative、java后端

    摘要:無(wú)論你是前端后端移動(dòng)端開(kāi)發(fā)人員,或是設(shè)計(jì)師產(chǎn)品經(jīng)理,都可以在平臺(tái)上發(fā)布項(xiàng)目,與志同道合的小伙伴一起協(xié)作完成項(xiàng)目。 全平臺(tái)全棧開(kāi)源項(xiàng)目 coderiver 今天終于開(kāi)始前后端聯(lián)調(diào)了~ 首先感謝大家的支持,coderiver 在 GitHub 上開(kāi)源兩周,獲得了 54 個(gè) Star,9 個(gè) Fork,5 個(gè) Watch。 這些鼓勵(lì)和認(rèn)可也更加堅(jiān)定了我繼續(xù)寫下去的決心~ 再次感謝各位大佬! ...

    Maxiye 評(píng)論0 收藏0
  • 深入理解Spring Cloud服務(wù)構(gòu)建【二】 - 2.2 Spring Cloud

    摘要:負(fù)載均衡組件是一個(gè)負(fù)載均衡組件,它通常和配合使用。和配合,很容易做到負(fù)載均衡,將請(qǐng)求根據(jù)負(fù)載均衡策略分配到不同的服務(wù)實(shí)例中。和配合,在消費(fèi)服務(wù)時(shí)能夠做到負(fù)載均衡。在默認(rèn)的情況下,和相結(jié)合,能夠做到負(fù)載均衡智能路由。 2.2.1 簡(jiǎn)介 Spring Cloud 是基于 Spring Boot 的。 Spring Boot 是由 Pivotal 團(tuán)隊(duì)提供的全新 Web 框架, 它主要的特點(diǎn)...

    Rocko 評(píng)論0 收藏0
  • Zuul:構(gòu)建高可用網(wǎng)關(guān)之多維度限流

    摘要:對(duì)請(qǐng)求的目標(biāo)進(jìn)行限流例如某個(gè)每分鐘只允許調(diào)用多少次對(duì)客戶端的訪問(wèn)進(jìn)行限流例如某個(gè)每分鐘只允許請(qǐng)求多少次對(duì)某些特定用戶或者用戶組進(jìn)行限流例如非用戶限制每分鐘只允許調(diào)用次某個(gè)等多維度混合的限流。 對(duì)請(qǐng)求的目標(biāo)URL進(jìn)行限流(例如:某個(gè)URL每分鐘只允許調(diào)用多少次) 對(duì)客戶端的訪問(wèn)IP進(jìn)行限流(例如:某個(gè)IP每分鐘只允許請(qǐng)求多少次) 對(duì)某些特定用戶或者用戶組進(jìn)行限流(例如:非VIP用戶限制...

    wenshi11019 評(píng)論0 收藏0
  • API網(wǎng)關(guān)如何實(shí)現(xiàn)對(duì)服務(wù)下線實(shí)時(shí)感知

    摘要:上篇文章緩存機(jī)制介紹了的緩存機(jī)制,相信大家對(duì)有了進(jìn)一步的了解,本文將詳細(xì)介紹網(wǎng)關(guān)如何實(shí)現(xiàn)服務(wù)下線的實(shí)時(shí)感知。目前網(wǎng)關(guān)實(shí)現(xiàn)的是對(duì)網(wǎng)關(guān)下游服務(wù)的實(shí)時(shí)感知,而且需滿足以下條件生產(chǎn)者需部署在容器管理平臺(tái)生產(chǎn)者做正常的下線升級(jí)或者縮容操作。 上篇文章《Eureka 緩存機(jī)制》介紹了Eureka的緩存機(jī)制,相信大家對(duì)Eureka 有了進(jìn)一步的了解,本文將詳細(xì)介紹API網(wǎng)關(guān)如何實(shí)現(xiàn)服務(wù)下線的實(shí)時(shí)感知...

    codeKK 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<