关于某项目需要引入授权控制的方案
关于某项目需要引入授权控制的方案
1、业务背景
DLearn 机器学习产品,现有后端、MC 代理两个项目,其中后端项目集成了公司框架 sousa-framework:1.4.0,属于微服务架构,且 Web 接口遵循公司的规范(Controller 必须定义为某种规则、接口路径也必须定义为某种规则)。而 MC 代理基于的是 SpringBoot 原生规则,并未引入公司框架,接口也都是常规编写方式且已部署到了多个项目现场使用。
2、需求目标
接领导要求,为了控制对外提供的接口调用,需加入 license 校验机制(PS:即在项目中配置一个 license.properties 文件,其中包含了应用名称、有效期、服务器IP地址、通过非对称加密生成的 key 等信息,是已有功能,只是未对 IP 进行严格检查),需要限制服务器的IP,即:一台服务器一个 license.properties 文件,如果服务启动时检测到当前服务器的 IP 与文件中的不一致,则拦截接口并提示未授权。要求很简单:
(1)maven 引入 sousa-framework 包,领导对该包的代码进行了更新,会严格校验 IP;注意,引入此包就意味着整个项目需要接入微服务、接口的定义和调用需要按照公司的模式。
(2)项目中的 license.properties 文件更新到最新内容。
3、需求分析
当前后端项目本就引入了公司框架,处理简单,只需要替换授权文件即可。但 MC 若引入 sousa-framework 包,将会涉及到重大调整,这绝不可行。遂反编译代码发现其实 license 的校验只是用到了框架中的一个工具类,至于接口调用时的校验完全可以写一个过滤器、拦截器等等自行处理哇。
4、解决方案
(1)项目中引入框架的部分 jar 包
<dependency>
<groupId>ys.manufacture.vplat</groupId>
<artifactId>framework-boot-vplat</artifactId>
<version>1.8.0</version>
</dependency>(2)参考框架中的工具类,自定义实现一个同名工具类
public class CfgTool {
private static final Map<String, GBKProperties> GBKProCache = new HashMap<>();
/**
* 校验 license,若无效或过期则抛出异常
*/
public static void checkLicense() {
if (!licheck()) {
throw new BusinessException("LICENSE_EXPIRED", "The license is invalid or has expired");
}
}
/**
* 校验 license(文件加载失败时抛出异常)
*/
public static boolean licheck() {
return License.checkLicenseFile(getGBKProperties("license.properties"));
}
/**
* 以GBK的编码格式读取配置文件
*
* @param proName 配置文件名称
* @return 配置内容
*/
public static GBKProperties getGBKProperties(String proName) {
// ...省略...
}
}(3)自定义拦截器
public class LicenseCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!CfgTool.licheck()) {
throw new BusinessException("LICENSE_EXPIRED", "系统license无效或已过期,请联系管理员");
}
return true;
}
}(4)配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LicenseCheckInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("一些要忽略的接口路径");
}
}5、问题延伸
经过以上实践,又发现了两个痛点:
- 第一:由于本地环境和开发测试环境的
license内容不同,且一提交代码开发环境将会自动部署更新,导致适用于本地的license内容不能提交,但一直会出现在Git版本里面,这很难受。 - 第二:对于要部署到项目现场(生产环境)时,打完包后发现代码一直读取的是
jar包中的license配置,导致无法在外部变更license文件
遂再次进行优化,通过以下方式的改造,解决了以上问题:
(1)读取配置文件的代码逻辑稍作修改,优先读取配置的路径:
public static GBKProperties getGBKProperties(String proName) {
InputStream in = null;
try {
GBKProperties pro = GBKProCache.get(proName);
if (pro == null) {
// 优先从参数中读取,没有的话再读取classpath的资源目录下
String paramValue = System.getProperty(proName);
if (StrUtil.isNotBlank(paramValue)) {
in = FileUtil.getInputStream(paramValue);
} else {
in = CfgTool.class.getClassLoader().getResourceAsStream(proName);
}
pro = new GBKProperties();
pro.load(in);
GBKProCache.put(proName, pro);
}
return pro;
} catch (Exception e) {
throw new BusinessException("FILE_LOAD_ERROR", String.format("文件[%s]加载异常", proName), e.getMessage());
} finally {
IoUtil.close(in);
}
}(2)不管是本地还是打包后,都可以在启动命令上加上以下配置,这样就可以自定义配置文件路径了:
-Dlicense.properties=/a/b/c/license.properties另外,临时应急:修改jar包中的配置文件然后重新打包的命令如下:
# 先新建一个临时目录,将当前jar包解压到该临时目录
jar xvf old.jar
# 修改完成后重新打包
jar cvfM0 new.jar *
