猿Why上个月接到一个需求,大致要求:在一个运维管理系统(System S)中集成另一个已经成熟的日志管理系统(System T)(没有权限管理)。
拿到需求,首先想到:通过代理方式做资源(API、静态资源)访问、权限控制。在一个微服务(分布式)的环境下,网关和外层的Nginx(反向代理)其实都可以实现这个需求。
现实情况,我们没有网关服务,Nginx做用户信息粘合不好(团队中没有人擅长)。所以,经过我的考虑,直接在S系统中添加个代理,所有T系统的访问,都经S一手,这样在S系统中就可以做粒度很细的权限控制。
查询一番资料后就会发现,自己能想到的,前辈们早就干过了(感叹Java生态环境之成熟)。于是,就要介绍这个方案的核心了:ProxyServlet
ProxyServlet API
来看看ProxyServlet的说明。嗯!很符合需求,而且它就是一个Servlet,很适合作轻量级的代理。
ProxyServlet使用
核心依赖
定义配置数据结构
public class ProxyInfo {
// 服务/功能名称
String name;
// 暴露给外部的请求
String servlet_url;
// 实际目标服务
String target_url;
}
外部化配置
启动加载代理配置类
package com.young.proxytool.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.young.proxytool.proxy.ProxyInfo;
import com.young.proxytool.proxy.ProxyServletExtend;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.UrlResource;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Proxy注册
*
* @author :<a href="mailto:youngkun2016@163.com">young</a>
* @date :Created in 2020/12/19
*/
@Configuration
public class RegistryProxyServletConfig implements ServletContextInitializer {
/**
* 外部配置文件路径
*/
@Value("${proxy.data}")
private String proxyFilePath;
private ServletContext sc;
private UrlResource urlResource;
@Override
public void onStartup(ServletContext servletContext) {
try {
this.sc = servletContext;
this.urlResourceInit();
InputStream config = this.urlResource.getInputStream();
JSONArray jsonArray = JSON.parseObject(config, JSONArray.class);
for (Object object : jsonArray) {
ProxyInfo proxyInfo = JSON.toJavaObject(JSONObject.class.cast(object), ProxyInfo.class);
this.registerProxy(proxyInfo);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void urlResourceInit() throws Exception {
FileUrlResource fileUrlResource = new FileUrlResource(proxyFilePath);
if (fileUrlResource.isReadable()) {
this.urlResource = fileUrlResource;
} else {
throw new Exception("无法读取代理配置文件:" + proxyFilePath);
}
}
protected void registerProxy(ProxyInfo proxyInfo) {
ServletRegistration registrationBean = this.sc.addServlet(proxyInfo.getName(), ProxyServletExtend.class);
Map<String, String> initParameters = new LinkedHashMap<>();
initParameters.put(ProxyServletExtend.P_LOG, "true");
initParameters.put(ProxyServletExtend.P_TARGET_URI, proxyInfo.getTarget_url());
registrationBean.setInitParameters(initParameters);
registrationBean.addMapping(proxyInfo.getServlet_url());
}
}
权限托管
- 限制直接访问T的请求,不对外暴露
- S通过ProxyServlet做S系统与T系统的映射关系
- S系统Filter做权限控制