网关可校验token

pull/1/head
tanghc 5 years ago
parent 530a92a3ea
commit a08313ea81
  1. 4
      changelog.md
  2. 65
      doc/docs/_sidebar.md
  3. 54
      doc/docs/files/10089_自定义校验token.md
  4. 1
      sop-2.5.0.sql
  5. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteSearchParam.java
  6. 3
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/RouteVO.java
  7. 3
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/ConfigServiceRoute.java
  8. 2
      sop-admin/sop-admin-server/src/main/resources/public/index.html
  9. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9b31c83a.52bc6b2c.js
  10. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9b31c83a.e60adb43.js
  11. 17
      sop-admin/sop-admin-vue/src/views/service/route.vue
  12. 7
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
  13. 46
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/BaseRouteDefinition.java
  14. 19
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/BaseServiceRouteInfo.java
  15. 5
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RouteDefinition.java
  16. 27
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java
  17. 7
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java
  18. 22
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java
  19. 11
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/TokenValidator.java
  20. 5
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiAbility.java
  21. 7
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiMapping.java
  22. 13
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/OpenContext.java
  23. 2
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceApiInfo.java
  24. 1
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaBuilder.java
  25. 4
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceRouteInfoBuilder.java
  26. 10
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java
  27. 1
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingInfo.java
  28. 5
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/route/RouteDefinition.java
  29. 28
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/TokenController.java
  30. 7
      sop-gateway/src/main/java/com/gitee/sop/gateway/entity/ConfigServiceRoute.java
  31. 1
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRoutesProcessor.java
  32. 15
      sop-test/src/main/java/com/gitee/sop/test/Client.java
  33. 11
      sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java

@ -1,5 +1,9 @@
# changelog
## 2.5.0
- 网关可校验token,见`com.gitee.sop.storyweb.controller.TokenController`(需要执行`sop-2.5.0.sql`升级文件)
## 2.4.1
- 优化restful接口调用(如果正在使用此功能,必看 [doc](https://durcframework.gitee.io/sop/#/files/10100_%E6%8F%90%E4%BE%9Brestful%E6%8E%A5%E5%8F%A3?t=1571107529449))

@ -1,34 +1,35 @@
* [首页](/?t=1571107529420)
* [首页](/?t=1572076365237)
* 开发文档
* [快速体验](files/10010_快速体验.md?t=1571107529423)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1571107529447)
* [新增接口](files/10020_新增接口.md?t=1571107529447)
* [开发流程](files/10021_开发流程.md?t=1571107529447)
* [业务参数校验](files/10030_业务参数校验.md?t=1571107529447)
* [错误处理](files/10040_错误处理.md?t=1571107529447)
* [编写文档](files/10041_编写文档.md?t=1571107529447)
* [接口交互详解](files/10050_接口交互详解.md?t=1571107529448)
* [easyopen支持](files/10070_easyopen支持.md?t=1571107529448)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1571107529448)
* [ISV管理](files/10085_ISV管理.md?t=1571107529448)
* [自定义返回结果](files/10087_自定义返回结果.md?t=1571107529448)
* [自定义过滤器](files/10088_自定义过滤器.md?t=1571107529448)
* [路由授权](files/10090_路由授权.md?t=1571107529448)
* [接口限流](files/10092_接口限流.md?t=1571107529448)
* [监控日志](files/10093_监控日志.md?t=1571107529448)
* [SDK开发](files/10095_SDK开发.md?t=1571107529449)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1571107529449)
* [应用授权](files/10097_应用授权.md?t=1571107529449)
* [提供restful接口](files/10100_提供restful接口.md?t=1571107529449)
* [文件上传](files/10104_文件上传.md?t=1571107529449)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1571107529449)
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1571107529449)
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1571107529450)
* [使用eureka](files/10112_使用eureka.md?t=1571107529450)
* [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1571107529450)
* [快速体验](files/10010_快速体验.md?t=1572076365239)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1572076365257)
* [新增接口](files/10020_新增接口.md?t=1572076365257)
* [开发流程](files/10021_开发流程.md?t=1572076365257)
* [业务参数校验](files/10030_业务参数校验.md?t=1572076365258)
* [错误处理](files/10040_错误处理.md?t=1572076365258)
* [编写文档](files/10041_编写文档.md?t=1572076365258)
* [接口交互详解](files/10050_接口交互详解.md?t=1572076365258)
* [easyopen支持](files/10070_easyopen支持.md?t=1572076365258)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1572076365258)
* [ISV管理](files/10085_ISV管理.md?t=1572076365258)
* [自定义返回结果](files/10087_自定义返回结果.md?t=1572076365259)
* [自定义过滤器](files/10088_自定义过滤器.md?t=1572076365259)
* [自定义校验token](files/10089_自定义校验token.md?t=1572076365259)
* [路由授权](files/10090_路由授权.md?t=1572076365259)
* [接口限流](files/10092_接口限流.md?t=1572076365259)
* [监控日志](files/10093_监控日志.md?t=1572076365259)
* [SDK开发](files/10095_SDK开发.md?t=1572076365259)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1572076365260)
* [应用授权](files/10097_应用授权.md?t=1572076365260)
* [提供restful接口](files/10100_提供restful接口.md?t=1572076365260)
* [文件上传](files/10104_文件上传.md?t=1572076365260)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1572076365260)
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1572076365261)
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1572076365261)
* [使用eureka](files/10112_使用eureka.md?t=1572076365261)
* [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1572076365261)
* 原理分析
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1571107529450)
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1571107529450)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1571107529450)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1571107529450)
* [常见问题](files/90100_常见问题.md?t=1571107529450)
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1572076365261)
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1572076365261)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1572076365261)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1572076365262)
* [常见问题](files/90100_常见问题.md?t=1572076365262)

@ -0,0 +1,54 @@
# 自定义校验token(2.5.0)
从2.5.0开始在`@ApiMapping`注解中新增了一个属性`needToken`,用来告诉网关是否校验token
```java
/**
* 是否需要appAuthToken,设置为true,网关端会校验token是否存在
*/
boolean needToken() default false;
```
使用方式:
```java
@ApiMapping(value = "story.token.get", needToken = true/* 设置true,网关会校验token是否存在 */)
public StoryResult token(StoryParam story) {
OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext();
String appAuthToken = openContext.getAppAuthToken();
StoryResult result = new StoryResult();
result.setName("appAuthToken:" + appAuthToken);
return result;
}
```
指定了needToken=true后,网关会判断客户端是否传了`app_auth_token`参数,没有传则返回错误信息。
网关默认简单校验参数值是否存在,如果要校验有效性,需要自己实现。
自己实现步骤:
- 在ZuulConfig类中重写`doAfter`方法
- 设置`ApiConfig中的tokenValidator属性`
`TokenValidator`是一个函数式接口,可以直接使用Lambda表达式,示例代码如下:
```java
public class ZuulConfig extends AlipayZuulConfiguration {
@Override
protected void doAfter() {
ApiConfig.getInstance().setTokenValidator(apiParam -> {
// 获取客户端传递过来的token
String token = apiParam.fetchAccessToken();
if (StringUtils.isBlank(token)) {
return false;
}
// TODO: 校验token有效性,可以从redis中读取
// 返回true表示这个token真实、有效
return true;
});
}
}
```

@ -0,0 +1 @@
ALTER TABLE `sop`.`config_service_route` ADD COLUMN `need_token` TINYINT NOT NULL DEFAULT 0 COMMENT '是否需要token' AFTER `permission`;

@ -26,4 +26,8 @@ public class RouteSearchParam extends PageParam {
@ApiDocField(description = "是否授权接口,1:是")
@Condition(ignoreValue = "0")
private Integer permission;
@ApiDocField(description = "是否需要token接口,1:是")
@Condition(ignoreValue = "0")
private Integer needToken;
}

@ -49,6 +49,9 @@ public class RouteVO {
/** 是否合并结果, 数据库字段:merge_result */
private Byte mergeResult;
/** 是否需要token, 数据库字段:need_token */
private Byte needToken;
/** 是否需要授权才能访问, 数据库字段:permission */
private Byte permission;

@ -61,6 +61,9 @@ public class ConfigServiceRoute {
/** 是否需要授权才能访问, 数据库字段:permission */
private Byte permission;
/** 是否需要token, 数据库字段:need_token */
private Byte needToken;
/** 数据库字段:gmt_create */
private Date gmtCreate;

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.c6dfb7ee.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],d=0,l=[];d<a.length;d++)c=a[d],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(l.length)l.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-0faf9ba2":"88458f58","chunk-25908fca":"05d6b15b","chunk-2c1f2e8f":"f092c0a0","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-4de1c2b6":"e74e3d03","chunk-6f78c9fe":"3ac83b41","chunk-73b2dcec":"05b5ee28","chunk-9b31c83a":"e60adb43","chunk-9f479afe":"0d0ed757","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-0faf9ba2":1,"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-4de1c2b6":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-0faf9ba2":"ed1510dd","chunk-25908fca":"89ab33e8","chunk-2c1f2e8f":"0314067f","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-4de1c2b6":"a37cd815","chunk-6f78c9fe":"31d6cfe0","chunk-73b2dcec":"99cf6327","chunk-9b31c83a":"3b12267b","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var l=document.getElementsByTagName("style");for(a=0;a<l.length;a++){i=l[a],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var h=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.f05d6d01.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.c6dfb7ee.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],d=0,l=[];d<a.length;d++)c=a[d],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(l.length)l.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-0faf9ba2":"88458f58","chunk-25908fca":"05d6b15b","chunk-2c1f2e8f":"f092c0a0","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-4de1c2b6":"e74e3d03","chunk-6f78c9fe":"3ac83b41","chunk-73b2dcec":"05b5ee28","chunk-9b31c83a":"52bc6b2c","chunk-9f479afe":"0d0ed757","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-0faf9ba2":1,"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-4de1c2b6":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-0faf9ba2":"ed1510dd","chunk-25908fca":"89ab33e8","chunk-2c1f2e8f":"0314067f","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-4de1c2b6":"a37cd815","chunk-6f78c9fe":"31d6cfe0","chunk-73b2dcec":"99cf6327","chunk-9b31c83a":"3b12267b","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var l=document.getElementsByTagName("style");for(a=0;a<l.length;a++){i=l[a],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var h=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.f05d6d01.js></script></body></html>

@ -66,6 +66,9 @@
<el-form-item>
<el-checkbox v-model="searchFormData.permission">授权接口</el-checkbox>
</el-form-item>
<el-form-item>
<el-checkbox v-model="searchFormData.needToken">需要token</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="onSearchTable">查询</el-button>
</el-form-item>
@ -123,6 +126,16 @@
<span v-if="scope.row.mergeResult === 0" style="color:#E6A23C"></span>
</template>
</el-table-column>
<el-table-column
prop="needToken"
label="需要token"
width="100"
>
<template slot-scope="scope">
<span v-if="scope.row.needToken === 1" style="font-weight: bold;color: #303133;"></span>
<span v-if="scope.row.needToken === 0"></span>
</template>
</el-table-column>
<el-table-column
prop="status"
label="状态"
@ -182,6 +195,9 @@
<el-form-item label="统一格式输出">
{{ routeDialogFormData.mergeResult === 1 ? '是' : '否' }}
</el-form-item>
<el-form-item label="需要token">
{{ routeDialogFormData.needToken === 1 ? '是' : '否' }}
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="routeDialogFormData.status">
<el-radio :label="1" name="status">启用</el-radio>
@ -269,6 +285,7 @@ export default {
id: '',
serviceId: '',
permission: 0,
needToken: 0,
pageIndex: 1,
pageSize: 10
},

@ -32,12 +32,14 @@ import com.gitee.sop.gatewaycommon.validate.ApiSigner;
import com.gitee.sop.gatewaycommon.validate.ApiValidator;
import com.gitee.sop.gatewaycommon.validate.Encrypter;
import com.gitee.sop.gatewaycommon.validate.Signer;
import com.gitee.sop.gatewaycommon.validate.TokenValidator;
import com.gitee.sop.gatewaycommon.validate.Validator;
import com.gitee.sop.gatewaycommon.zuul.configuration.ZuulErrorController;
import com.gitee.sop.gatewaycommon.zuul.param.ZuulParamBuilder;
import com.gitee.sop.gatewaycommon.zuul.result.ZuulResultExecutor;
import com.netflix.zuul.context.RequestContext;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import java.util.ArrayList;
@ -152,6 +154,11 @@ public class ApiConfig {
private ParameterFormatter parameterFormatter;
/**
* 校验token
*/
private TokenValidator tokenValidator = apiParam -> apiParam != null && StringUtils.isNotBlank(apiParam.fetchAccessToken());
// -------- fields ---------
/**

@ -1,46 +0,0 @@
package com.gitee.sop.gatewaycommon.bean;
import lombok.Getter;
import lombok.Setter;
/**
* @author tanghc
*/
@Getter
@Setter
public class BaseRouteDefinition {
/**
* 路由的Id
*/
private String id;
/**
* 路由规则转发的目标uri
*/
private String uri;
/**
* uri后面跟的path
*/
private String path;
/**
* 路由执行的顺序
*/
private int order = 0;
/**
* 是否忽略验证业务参数验证除外
*/
private int ignoreValidate;
/**
* 是否合并结果
*/
private int mergeResult;
/**
* 接口是否需要授权才能访问
*/
private int permission;
}

@ -1,19 +0,0 @@
package com.gitee.sop.gatewaycommon.bean;
import lombok.Data;
import java.util.Collections;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class BaseServiceRouteInfo<T extends BaseRouteDefinition> {
private String serviceId;
private List<T> routeDefinitionList = Collections.emptyList();
public String fetchServiceIdLowerCase() {
return this.serviceId.toLowerCase();
}
}

@ -70,4 +70,9 @@ public class RouteDefinition {
* 是否需要授权才能访问
*/
private int permission;
/**
* 是否需要token
*/
private int needToken;
}

@ -16,6 +16,8 @@ import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -25,13 +27,19 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Collections;
@ -41,6 +49,7 @@ import java.util.List;
/**
* @author tanghc
*/
@Slf4j
public class BaseGatewayConfiguration extends AbstractConfiguration {
public BaseGatewayConfiguration() {
@ -152,12 +161,20 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
@Bean
@ConditionalOnProperty(value = "sop.restful.enable", havingValue = "true")
RouterFunction<ServerResponse> routerFunction() {
return RouterFunctions.route(RequestPredicates.GET(restPath + "/**"), (serverRequest) -> {
String url = serverRequest.path();
int index = url.indexOf(restPath);
RequestPredicate requestPredicate = RequestPredicates.all()
.and(RequestPredicates.path(restPath + "/**"));
return RouterFunctions.route(requestPredicate, (serverRequest) -> {
String path = serverRequest.path();
int index = path.indexOf(restPath);
// 取/rest的后面部分
String path = url.substring(index + restPath.length());
String query = ParamNames.API_NAME + "=" + path + "&" + ParamNames.VERSION_NAME + "=";
String servletPath = path.substring(index + restPath.length());
String query = serverRequest.uri().getQuery();
String appendQuery = ParamNames.API_NAME + "=" + servletPath + "&" + ParamNames.VERSION_NAME + "=";
if (StringUtils.isBlank(query)) {
query = appendQuery;
} else {
query += '&' + appendQuery;
}
return ServerResponse
.temporaryRedirect(URI.create("/?" + query))
.build();

@ -21,7 +21,12 @@ public enum EnvironmentKeys {
/**
* sop.restful.enable=true开启传统web开发模式
*/
SOP_RESTFUL_ENABLE("sop.restful.enable");
SOP_RESTFUL_ENABLE("sop.restful.enable"),
/**
* sop.restful.path=/xx 指定请求前缀默认/rest
*/
SOP_RESTFUL_PATH("sop.restful.path", "/rest");
private String key;
private String defaultValue;

@ -83,6 +83,7 @@ public class ApiValidator implements Validator {
checkFormat(param);
checkUploadFile(param);
checkPermission(param);
checkToken(param);
}
/**
@ -238,7 +239,7 @@ public class ApiValidator implements Validator {
/**
* 校验访问权限
*
* @param apiParam
* @param apiParam 参数
*/
protected void checkPermission(ApiParam apiParam) {
String routeId = apiParam.fetchNameVersion();
@ -254,4 +255,23 @@ public class ApiValidator implements Validator {
}
}
/**
* 校验token
*
* @param apiParam 参数
*/
protected void checkToken(ApiParam apiParam) {
String routeId = apiParam.fetchNameVersion();
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId);
RouteDefinition routeDefinition = targetRoute.getRouteDefinition();
boolean needToken = BooleanUtils.toBoolean(routeDefinition.getNeedToken());
if (needToken) {
TokenValidator tokenValidator = ApiConfig.getInstance().getTokenValidator();
boolean rightToken = tokenValidator.validateToken(apiParam);
if (!rightToken) {
throw ErrorEnum.AOP_INVALID_APP_AUTH_TOKEN.getErrorMeta().getException();
}
}
}
}

@ -0,0 +1,11 @@
package com.gitee.sop.gatewaycommon.validate;
import com.gitee.sop.gatewaycommon.param.ApiParam;
/**
* @author tanghc
*/
@FunctionalInterface
public interface TokenValidator {
boolean validateToken(ApiParam apiParam);
}

@ -35,4 +35,9 @@ public @interface ApiAbility {
* 指定接口是否需要授权才能访问可在admin中进行修改
*/
boolean permission() default false;
/**
* 是否需要appAuthToken设置为true网关端会校验token是否存在
*/
boolean needToken() default false;
}

@ -25,7 +25,7 @@ public @interface ApiMapping {
/**
* 版本号默认版本号是""<br>
* 改默认版本号<code>ServiceContext.getSopServerConfig().setDefaultVersion("1.0");</code>
* 改默认版本号<code>ServiceConfig.getInstance().setDefaultVersion("1.0");</code>
*/
String version() default "";
@ -44,6 +44,11 @@ public @interface ApiMapping {
*/
boolean permission() default false;
/**
* 是否需要appAuthToken设置为true网关端会校验token是否存在
*/
boolean needToken() default false;
// ------------ 自定义属性 end ------------

@ -81,12 +81,23 @@ public interface OpenContext<T> extends OpenBeanFactory {
Date getTimestamp();
/**
* 返回token即access_token
* 返回token即access_token.
*
* @deprecated 废弃使用getAppAuthToken()
* @return 返回token
*/
@Deprecated
String appAuthToken();
/**
* 返回token即access_token
*
* @return 返回token
*/
default String getAppAuthToken() {
return appAuthToken();
}
/**
* 返回回调地址
*

@ -28,6 +28,8 @@ public class ServiceApiInfo {
private int mergeResult;
/** 是否需要授权才能访问 */
private int permission;
/** 是否需要token */
private int needToken;
/** 是否是原始Mapping */
private boolean originalMapping;

@ -87,6 +87,7 @@ public class ApiMetaBuilder {
apiMeta.setIgnoreValidate(BooleanUtils.toInteger(apiMappingInfo.isIgnoreValidate()));
apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult()));
apiMeta.setPermission(BooleanUtils.toInteger(apiMappingInfo.isPermission()));
apiMeta.setNeedToken(BooleanUtils.toInteger(apiMappingInfo.isNeedToken()));
return apiMeta;
} else {
if (!ServiceContext.getCurrentContext().getBoolean(ServiceContext.RESTFUL_KEY, false)) {

@ -50,8 +50,8 @@ public class ServiceRouteInfoBuilder {
List<ServiceApiInfo.ApiMeta> apis = serviceApiInfo.getApis();
List<RouteDefinition> routeDefinitionList = new ArrayList<>(apis.size());
for (ServiceApiInfo.ApiMeta apiMeta : apis) {
RouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta);
routeDefinitionList.add(gatewayRouteDefinition);
RouteDefinition routeDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta);
routeDefinitionList.add(routeDefinition);
}
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
serviceRouteInfo.setServiceId(serviceApiInfo.getServiceId());

@ -35,9 +35,10 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
method.setAccessible(true);
String name = null;
String version;
boolean ignoreValidate = false;
boolean mergeResult = true;
boolean permission = false;
boolean ignoreValidate;
boolean mergeResult;
boolean permission;
boolean needToken = false;
ApiMapping apiMapping = method.getAnnotation(ApiMapping.class);
if (apiMapping != null) {
name = apiMapping.value()[0];
@ -45,6 +46,7 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
ignoreValidate = apiMapping.ignoreValidate();
mergeResult = apiMapping.mergeResult();
permission = apiMapping.permission();
needToken = apiMapping.needToken();
} else {
ApiAbility apiAbility = this.findApiAbilityAnnotation(method);
if (apiAbility != null) {
@ -52,6 +54,7 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
ignoreValidate = apiAbility.ignoreValidate();
mergeResult = apiAbility.mergeResult();
permission = apiAbility.permission();
needToken = apiAbility.needToken();
} else {
return super.getCustomMethodCondition(method);
}
@ -75,6 +78,7 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
apiMappingInfo.setIgnoreValidate(ignoreValidate);
apiMappingInfo.setMergeResult(mergeResult);
apiMappingInfo.setPermission(permission);
apiMappingInfo.setNeedToken(needToken);
logger.info("注册接口,name:" + method + ", version:" + version);
return new ApiMappingRequestCondition(apiMappingInfo);
}

@ -12,6 +12,7 @@ public class ApiMappingInfo {
private boolean ignoreValidate;
private boolean mergeResult;
private boolean permission;
private boolean needToken;
public ApiMappingInfo(String name, String version) {
this.name = name;

@ -68,4 +68,9 @@ public class RouteDefinition {
* 是否需要授权才能访问
*/
private int permission;
/**
* 是否需要token
*/
private int needToken;
}

@ -0,0 +1,28 @@
package com.gitee.sop.storyweb.controller;
import com.gitee.sop.servercommon.annotation.ApiMapping;
import com.gitee.sop.servercommon.bean.OpenContext;
import com.gitee.sop.servercommon.bean.ServiceContext;
import com.gitee.sop.storyweb.controller.param.StoryParam;
import com.gitee.sop.storyweb.controller.result.StoryResult;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestController;
/**
* @author tanghc
*/
@RestController
@Slf4j
@Api(tags = "故事接口")
public class TokenController {
@ApiMapping(value = "story.token.get", needToken = true/* 设置true,网关会校验token是否存在 */)
public StoryResult token(StoryParam story) {
OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext();
String appAuthToken = openContext.getAppAuthToken();
StoryResult result = new StoryResult();
result.setName("appAuthToken:" + appAuthToken);
return result;
}
}

@ -1,15 +1,13 @@
package com.gitee.sop.gateway.entity;
import com.gitee.fastmybatis.core.annotation.LogicDelete;
import lombok.Data;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
@ -63,6 +61,9 @@ public class ConfigServiceRoute {
/** 是否需要授权才能访问, 数据库字段:permission */
private Byte permission;
/** 是否需要token, 数据库字段:need_token */
private Byte needToken;
/** 数据库字段:gmt_create */
private Date gmtCreate;

@ -54,6 +54,7 @@ public class DbRoutesProcessor implements RoutesProcessor {
configServiceRoute.setStatus((byte) routeDefinition.getStatus());
configServiceRoute.setPermission((byte) routeDefinition.getPermission());
configServiceRoute.setOrder(routeDefinition.getOrder());
configServiceRoute.setNeedToken((byte)routeDefinition.getNeedToken());
configServiceRoute.setServiceId(serviceId);
return configServiceRoute;
})

@ -138,6 +138,7 @@ public class Client {
private Map<String, String> header;
private boolean ignoreSign;
private boolean postJson;
private String appAuthToken;
private List<HttpTool.UploadFile> uploadFileList;
private Callback callback;
@ -229,6 +230,17 @@ public class Client {
return this;
}
/**
* 设置token
*
* @param appAuthToken 给定的token
* @return 返回RequestBuilder
*/
public RequestBuilder appAuthToken(String appAuthToken) {
this.appAuthToken = appAuthToken;
return this;
}
/**
* 添加文件
*
@ -310,6 +322,9 @@ public class Client {
if (version != null) {
params.put("version", version);
}
if (appAuthToken != null) {
params.put("app_auth_token", appAuthToken);
}
params.put("format", "json");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");

@ -287,6 +287,17 @@ public class AllInOneTest extends TestBase {
System.out.println("成功次数:" + success);
}
public void testToken() {
Client.RequestBuilder requestBuilder = new Client.RequestBuilder()
.method("story.token.get")
.version("1.0")
.bizContent(new BizContent().add("id", "1").add("name", "葫芦娃"))
.appAuthToken("asdfasdfadsf")
.httpMethod(HttpTool.HTTPMethod.GET);
client.execute(requestBuilder);
}
class BizContent extends HashMap<String, String> {
public BizContent add(String key, String value) {
this.put(key, value);

Loading…
Cancel
Save