微服務網關實戰09-聚合服務鑑權及限流

不知道有沒有同學發現,聚合服務沒有加token的時候,也是可以訪問的,其實很簡單,因爲聚合服務也是需要鑑權的,也是需要先留的,這兩個東西,我們同樣採用切面來做。

微服務網關實戰09-聚合服務鑑權及限流

 

考慮到聚合服務,有些接口不需要做限流,有些不需要做鑑權,我們用註解來實現。

新建兩個註解類,鑑權註解,限流注解

鑑權註解Security

/**
 * All rights Reserved, Designed By OprCalf
 * Copyright:    Copyright(C) 2016-2020
 * Company       OprCalf Ltd.
*/

package com.platform.gateway.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**@projectName:  platform-gateway
 * @package:      com.platform.gateway.common.annotation
 * @className:    Security.java
 * @description:  鑑權註解,用於那些需要鑑權的方法
 * @author:       OprCalf
 * @date:         2019年5月22日
 */
@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Security {

}

限流RateLimit註解類:

/**
 * All rights Reserved, Designed By OprCalf
 * Copyright:    Copyright(C) 2016-2020
 * Company       OprCalf Ltd.
*/

package com.platform.gateway.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**@projectName:  platform-gateway
 * @package:      com.platform.gateway.common.annotation
 * @className:    RateLimit.java
 * @description:  限流注解,用於那些需要被限流的接口
 * @author:       OprCalf
 * @date:         2019年5月22日
 */
@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {

    // 默認每秒放入桶中的token
    int limitNum() default 50;
}

鑑權切面類Security

/**
 * All rights Reserved, Designed By OprCalf
 * Copyright:    Copyright(C) 2016-2020
 * Company       OprCalf Ltd.
 */

package com.platform.gateway.common.aspect;

import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.base.Strings;
import com.platform.gateway.common.annotation.Security;
import com.platform.gateway.common.utils.MsgUtils;

import lombok.extern.slf4j.Slf4j;

/**@projectName:  platform-gateway
 * @package:      com.platform.gateway.common.aspect
 * @className:    SecurityAspect.java
 * @description:  鑑權界面
 * @author:       OprCalf
 * @date:         2019年5月22日
 */
@Component
@Aspect
@Slf4j
public class SecurityAspect {

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private HttpServletRequest request;

    @Pointcut("@annotation(com.platform.gateway.common.annotation.Security)")
    public void serviceSecurity() {
    }

    @Around("serviceSecurity()")
    public Object around(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        try {
            final Object obj = null;
            // 獲取攔截的方法名
            final Signature sig = joinPoint.getSignature();
            // 獲取攔截的方法名
            final MethodSignature msig = (MethodSignature) sig;
            // 返回被織入增加處理目標對象
            final Object target = joinPoint.getTarget();
            // 爲了獲取註解信息
            final Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
            // 獲取註解信息
            final Security annotation = currentMethod.getAnnotation(Security.class);
            // 當不爲空的時候,此時我們需要對此方法進行鑑權
            if (annotation != null) {
                final String token = request.getHeader("access_token");
                if (Strings.isNullOrEmpty(token)) {
                    // 拒絕了請求(服務降級)
                    log.info("拒絕了請求:" + request.getRequestURI());
                    response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                    response.setContentType("application/json;charset=UTF-8");
                    response.getOutputStream()
                            .write(MsgUtils.buildFailureMsg("當前請求沒有token").toString().getBytes("utf-8"));
                } else {
                    final String tokenValue = "nc3yb4x9n24nty23nu034bry9cy359-x23n4-x";
                    if (!tokenValue.equals(token)) {
                        response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                        response.setContentType("application/json;charset=UTF-8");
                        response.getOutputStream()
                                .write(MsgUtils.buildFailureMsg("當前token不正確").toString().getBytes("utf-8"));
                    }
                }
            }
            return obj;
        }
        catch (final Throwable throwable) {
            log.info("聚合鑑權發生錯誤:" + throwable.fillInStackTrace());
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.setContentType("application/json;charset=UTF-8");
            try {
                response.getOutputStream()
                        .write(MsgUtils.buildFailureMsg(throwable.getMessage()).toString().getBytes("utf-8"));
            }
            catch (final Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
}

限流RateLimitAspect切面類

/**
 * All rights Reserved, Designed By OprCalf
 * Copyright:    Copyright(C) 2016-2020
 * Company       OprCalf Ltd.
 */

package com.platform.gateway.common.aspect;

import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.util.concurrent.RateLimiter;
import com.platform.gateway.common.annotation.RateLimit;
import com.platform.gateway.common.utils.MsgUtils;

import lombok.extern.slf4j.Slf4j;

/**@projectName:  platform-gateway
 * @package:      com.platform.gateway.common.aspect
 * @className:    RateLimitAspect.java
 * @description:  限流切面
 * @author:       OprCalf
 * @date:         2019年5月22日
 */
@Component
@Aspect
@Slf4j
public class RateLimitAspect {

    // 用來存放不同接口的RateLimiter(key爲接口名稱,value爲RateLimiter)
    private ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>();

    private RateLimiter rateLimiter;

    @Autowired
    private HttpServletResponse resp;

    @Autowired
    private HttpServletRequest req;

    @Pointcut("@annotation(com.platform.gateway.common.annotation.RateLimit)")
    public void serviceLimit() {
    }

    @Around("serviceLimit()")
    public Object around(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        Object obj = null;
        // 獲取攔截的方法名
        final Signature sig = joinPoint.getSignature();
        // 獲取攔截的方法名
        final MethodSignature msig = (MethodSignature) sig;
        // 返回被織入增加處理目標對象
        final Object target = joinPoint.getTarget();
        // 爲了獲取註解信息
        final Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        // 獲取註解信息
        final RateLimit annotation = currentMethod.getAnnotation(RateLimit.class);
        // 獲取註解每秒加入桶中的token
        final int limitNum = annotation.limitNum();
        // 註解所在方法名區分不同的限流策略
        final String functionName = msig.getName();
        // 獲取rateLimiter
        if (map.containsKey(functionName)) {
            rateLimiter = map.get(functionName);
        } else {
            map.put(functionName, RateLimiter.create(limitNum));
            rateLimiter = map.get(functionName);
        }
        try {
            if (rateLimiter.tryAcquire()) {
                // 執行方法
                obj = joinPoint.proceed();
            } else {
                // 拒絕了請求(服務降級)
                log.info("拒絕了請求:" + req.getRequestURI());
                resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                resp.setContentType("application/json;charset=UTF-8");
                resp.getOutputStream()
                        .write(MsgUtils.buildFailureMsg("請求繁忙,請稍後再試...").toString().getBytes("utf-8"));
            }
        }
        catch (final Throwable throwable) {
            log.info("聚合限流發生錯誤:" + throwable.fillInStackTrace());
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            resp.setContentType("application/json;charset=UTF-8");
            try {
                resp.getOutputStream()
                        .write(MsgUtils.buildFailureMsg(throwable.getMessage()).toString().getBytes("utf-8"));
            }
            catch (final Exception e) {
                e.printStackTrace();
            }
        }
        return obj;
    }

}

給聚合接口新增註解

 @ApiOperation(value = "聚合服務測試接口")
    @GetMapping(value = "getTest", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @RateLimit(limitNum = 5)
    @Security
    public JSONObject getTest() {
        return getRequest.getNullUserForJson("http://ATOMIC-DATAS/", "/web/demo/user/test");
    }

這樣子就完成了,接下來我們來測試一下

沒有token測試

微服務網關實戰09-聚合服務鑑權及限流

 

返回的就是沒有token的提示

錯誤token測試

微服務網關實戰09-聚合服務鑑權及限流

 

返回錯誤的token提示

正確token測試

微服務網關實戰09-聚合服務鑑權及限流

 

訪問成功,可以直接訪問後臺獲取數據

限流測試:20個線程

微服務網關實戰09-聚合服務鑑權及限流

 

出現了請求繁忙的提示,限流有效果了。

至此,鑑權與限流完成。

總結:鑑權還是繼續普通實現,可以自行根據需要拓展,最好結合redis進行拓展,因爲需要針對多個聚合接口進行鑑權。

最後,謝謝觀賞,覺得好的話,點個贊,有什麼問題可以留言溝通,麼麼噠。

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