不知道有没有同学发现,聚合服务没有加token的时候,也是可以访问的,其实很简单,因为聚合服务也是需要鉴权的,也是需要先留的,这两个东西,我们同样采用切面来做。
考虑到聚合服务,有些接口不需要做限流,有些不需要做鉴权,我们用注解来实现。
新建两个注解类,鉴权注解,限流注解
鉴权注解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 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测试
返回的就是没有token的提示
错误token测试
返回错误的token提示
正确token测试
访问成功,可以直接访问后台获取数据
限流测试:20个线程
出现了请求繁忙的提示,限流有效果了。
至此,鉴权与限流完成。
总结:鉴权还是继续普通实现,可以自行根据需要拓展,最好结合redis进行拓展,因为需要针对多个聚合接口进行鉴权。
最后,谢谢观赏,觉得好的话,点个赞,有什么问题可以留言沟通,么么哒。