Golang web filter 轻量级实现

图片 3

golang web 通过http
handle模块进行restful接口与请求处理绑定;既然用了restful每个公司或项目都会制定自己的设计原则和约束条件。在日常开发中通常会根据uri匹配规则在请求进入controller之前进行一些特殊处理(权限、功能验证…);在Java
web编程中 servlet filter帮助我们实现了这样的功能,golang web通过扩展http
handle即可实现

场景

小程序请求的所有接口参数必须加密,后台返回数据也需要加密,并且增加Token验证

挺久没写博客了,因为博主开始了今年另一段美好的实习经历,学习加做项目,时间已排满;很感谢今年这两段经历,让我接触了golang和python,学习不同语言,可以跳出之前学习c/c++思维的限制,学习golang和python的优秀特性以及了解在不同的场景,适用不同的语言;而之前学习linux和c/c++,也使我很快就上手golang和python;

/safe/safe/user

一、小程序端功能编写

1.下载一份Js版的aesUtil.js源码。【注:文章末尾会贴出所有的相关类文件】2.下载一份Js版的md5.js源码。3.在pulic.js中进行加解密操作代码如下,其中秘钥和秘钥偏移量要与后台的一致。

var CryptoJS = require('aesUtil.js'); //引用AES源码jsvar md5 = requirevar key = CryptoJS.enc.Utf8.parse("76CAA1C88F7F8D1D"); //十六位十六进制数作为秘钥var iv = CryptoJS.enc.Utf8.parse('91129048100F0494'); //十六位十六进制数作为秘钥偏移量//解密方法function Decrypt { var encryptedHexStr = CryptoJS.enc.Hex.parse; var srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr); var decrypt = CryptoJS.AES.decrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8); return decryptedStr.toString();}//加密方法function Encrypt { var srcs = CryptoJS.enc.Utf8.parse; var encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.ciphertext.toString().toUpperCase();}//暴露接口module.exports.Decrypt = Decrypt;module.exports.Encrypt = Encrypt;

4.在网络请求帮助类中进行参数的加密和返回数据的解密操作。

var aes = require('../utils/public.js')var md5 = require("../utils/md5.js")... /** * 网络请求 */function request(method, loading, url, params, success, fail) { var url = BASE_URL + url; //请求参数转为JSON字符串 var jsonStr = JSON.stringify; console.log(url + ' params=> ' + jsonStr) //根据特定规则生成Token var token = productionToken; //加密请求参数 var aesData = aes.Encrypt console.log('请求=>明文参数:' + jsonStr) console.log('请求=>加密参数:' + aesData) ... wx.request({ url: url, method: method, header: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', 'Token': token }, data: { aesData: aesData }, // data: params, success: function { //判断请求结果是否成功 if (res.statusCode == 200 && res.data != '' && res.data != null) { //解密返回数据 console.log('返回=>加密数据:' + res.data); var result = aes.Decrypt; console.log('返回=>明文数据:'+result); success(JSON.parse } else { fail() } }, fail: function { fail}

其中生成Token的规则,【生成Token的规则可根据具体的业务逻辑自己定义,我这里使用的规则是根据请求参数的字母排序取其value并加上当前时间戳再进行MD5加密】

/** * 生成Token */function productionToken { var obj = util.objKeySort; var value = ''; for (var item in obj) { value += obj[item]; } //加上当前时间戳 value += util.getTokenDate(new Date //去除所有空格 value = value.replace(/s+/g, "") //进行UTF-8编码 value = encodeURI; //进行MD5码加密 value = md5.hex_md5 return value;}//util的排序函数function objKeySort { //先用Object内置类的keys方法获取要排序对象的属性名,再利用Array原型上的sort方法对获取的属性名进行排序,newkey是一个数组 var newkey = Object.keys.sort(); //创建一个新的对象,用于存放排好序的键值对 var newObj = {}; //遍历newkey数组 for (var i = 0; i < newkey.length; i++) { //向新创建的对象中按照排好的顺序依次增加键值对 newObj[newkey[i]] = obj[newkey[i]]; } //返回排好序的新对象 return newObj; }

我学习的习惯,除了学习如何使用,还喜欢研究源码,学习运行机制,这样用起来才会得心应手或者说,使用这些语言或框架,就和平时吃饭睡觉一样,非常自然;因为最近有接触到bottle和flask
web框架,所以想看下这两个的源码,但是这两个框架是基于python自带的http,因此就有了这篇文章;

假定:以/safe开头的请求对客户端请求参数进行解密以/safe/user开头的请求不仅要对客户端请求参数进行解密还要验证当前请求用户是否在线

二、服务端功能编写

由于初学SpringMVC,使用的方式不一定是最优最好的,如有不妥善之处,请各位看官多多指教

思路:

  • 通过过滤器拦截请求参数,通过自定义参数包装器对参数进行解密。
  • 在拦截器获取请求的Token并生成服务器端Token进行验证。
  • 对返回参数通过JSON转换器进行加密处理。

    图片 1思路图

1.重写HttpServletRequestWrapper,在自定义的HttpServletRequestWrapper
中对参数进行处理

/** * Describe:请求参数包装器 主要作用的过滤参数并解密 * Created by 吴蜀黍 on 2018-08-07 09:37 **/@Slf4jpublic class ParameterRequestWrapper extends HttpServletRequestWrapper { private Map<String, String[]> params = new HashMap<>(); @SuppressWarnings("unchecked") public ParameterRequestWrapper(HttpServletRequest request) { // 将request交给父类,以便于调用对应方法的时候,将其输出,其实父亲类的实现方式和第一种new的方式类似 super; //将参数表,赋予给当前的Map以便于持有request中的参数 this.params.putAll(request.getParameterMap; this.modifyParameterValues(); } //重载一个构造方法 public ParameterRequestWrapper(HttpServletRequest request, Map<String, Object> extendParams) { this; addAllParameters(extendParams);//这里将扩展参数写入参数表 } private void modifyParameterValues() {//将parameter的值去除空格后重写回去 //获取加密数据 String aesParameter = getParameter(Constants.NetWork.AES_DATA); log.debug("[modifyParameterValues]==========>加密数据:{}", aesParameter); //解密 String decryptParameter = null; try { decryptParameter = AesUtils.decrypt(aesParameter, Constants.AES.AES_KEY); log.debug("[modifyParameterValues]==========> 解密数据:{}", decryptParameter); Map<String, Object> map = JSON.parseObject(decryptParameter); Set<String> set = map.keySet(); for (String key : set) { params.put(key, new String[]{String.valueOf(map.get; } aesFlag; } catch (CommonBusinessException e) { aesFlag; log.error("[modifyParameterValues]", e); log.debug("[modifyParameterValues]==========>", e); } } /** * 解密成功标志 */ private void aesFlag(boolean flag) { params.put(Constants.NetWork.AES_SUCCESS, new String[]{String.valueOf; } @Override public Map<String, String[]> getParameterMap() {// return super.getParameterMap(); return params; } @Override public Enumeration<String> getParameterNames() { return new Vector<>(params.keySet.elements(); } @Override public String getParameter(String name) {//重写getParameter,代表参数从当前类中的map获取 String[] values = params.get; if (values == null || values.length == 0) { return null; } return values[0]; } public String[] getParameterValues(String name) {//同上 return params.get; } public void addAllParameters(Map<String, Object> otherParams) {//增加多个参数 for (Map.Entry<String, Object> entry : otherParams.entrySet { addParameter(entry.getKey(), entry.getValue; } } public void addParameter(String name, Object value) {//增加参数 if (value != null) { if (value instanceof String[]) { params.put(name,  value); } else if (value instanceof String) { params.put(name, new String[]{ value}); } else { params.put(name, new String[]{String.valueOf; } } }}

新建过滤器,在拦截器中调用自定义的参数包装器

/** * Describe:请求参数过滤器 * Created by 吴蜀黍 on 2018-08-07 10:02 **/@Slf4jpublic class ParameterFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //使用自定义的参数包装器对参数进行处理 ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper((HttpServletRequest) servletRequest); filterChain.doFilter(requestWrapper, servletResponse); } @Override public void destroy() { }}

web.xml中对过滤器进行配置

 <!--过滤器--> <filter> <filter-name>parameterFilter</filter-name> <filter-class>com.xxx.xxx.config.filter.ParameterFilter</filter-class> </filter> <filter-mapping> <filter-name>parameterFilter</filter-name> <!-- 过滤所有以.json结尾的资源--> <url-pattern>*.json</url-pattern> </filter-mapping>

AES加解密操作

 /** * Describe:AES 加密 * Created by 吴蜀黍 on 2018-08-03 17:47 **/public class AesUtils { private static final String CHARSET_NAME = "UTF-8"; private static final String AES_NAME = "AES"; private static final String ALGORITHM = "AES/CBC/PKCS7Padding"; private static final String IV = Constants.AES.AES_IV; static { Security.addProvider(new BouncyCastleProvider; } /** * 加密 */ public static String encrypt(@NotNull String content, @NotNull String key) throws CommonBusinessException { try { Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(CHARSET_NAME), AES_NAME); AlgorithmParameterSpec paramSpec = new IvParameterSpec(IV.getBytes; cipher.init(Cipher.ENCRYPT_MODE, keySpec, paramSpec); return ParseSystemUtil.parseByte2HexStr(cipher.doFinal(content.getBytes(CHARSET_NAME))); } catch (Exception ex) { throw new CommonBusinessException; } } /** * 解密 */ public static String decrypt(@NotNull String content, @NotNull String key) throws CommonBusinessException { try { Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(CHARSET_NAME), AES_NAME); AlgorithmParameterSpec paramSpec = new IvParameterSpec(IV.getBytes; cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec); return new String(cipher.doFinal(Objects.requireNonNull(ParseSystemUtil.parseHexStr2Byte), CHARSET_NAME); } catch (Exception ex) { throw new CommonBusinessException; } }}

2.新建拦截器,验证Token以及解密的判断

 ... @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception { //如果不是映射到方法直接通过 if (!(handler instanceof HandlerMethod)) { return true; } //判断参数包装器中对请求参数的解密是否成功 boolean aesSuccess = Boolean.parseBoolean(httpServletRequest.getParameter(Constants.NetWork.AES_SUCCESS)); if (!aesSuccess) { this.sendMsg(Constants.NetWork.CODE_DECRYPTION_FAILURE, Constants.NetWork.MEG_AES_FAIL, httpServletResponse); return false; } //获取客户端上传Token String token = httpServletRequest.getHeader(Constants.NetWork.TOKEN_HEAD_KEY); if (StringUtils.isNullOrEmpty { sendMsg(Constants.NetWork.CODE_TOKEN_INVALID, Constants.NetWork.MSG_TOKEN_EMPTY, httpServletResponse); return false; } //验证Token的有效性 if (!TokenUtils.verificationToken(token, httpServletRequest.getParameterMap { sendMsg(Constants.NetWork.CODE_TOKEN_INVALID, Constants.NetWork.MSG_TOKEN_INVALID, httpServletResponse); return false; } return true; } /** * 验证失败 发送消息 */ private void sendMsg(String msgCode, String msg, HttpServletResponse httpServletResponse) throws IOException { httpServletResponse.setContentType("application/json; charset=utf-8"); PrintWriter writer = httpServletResponse.getWriter(); String jsonString = JSON.toJSONString(StandardResult.create(msgCode, msg)); try { //对验证失败的返回信息进行加密 jsonString = AesUtils.encrypt(jsonString, Constants.AES.AES_KEY); } catch (CommonBusinessException e) { e.printStackTrace(); jsonString = null; log.error("[sendMsg]", e); } writer.print(jsonString); writer.close(); httpServletResponse.flushBuffer(); } ...

在spring中对拦截器注册

... <mvc:interceptors> <!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 --> <mvc:interceptor> <!-- 拦截所有请求 --> <mvc:mapping path="/**"/> <!-- 需排除拦截的地址 --> <!--<mvc:exclude-mapping path="/"/>--> <bean /> </mvc:interceptor> </mvc:interceptors>...

Token的验证

/** * Describe:Token帮助类 * Created by 吴蜀黍 on 2018-08-04 14:48 **/@Slf4jpublic class TokenUtils { /** * 验证Token * * @param token 客户端上传Token * @param mapTypes 请求参数集合 * @return boolean */ public static boolean verificationToken(String token, Map mapTypes) { try { return StringUtils.saleEquals(token, getToken); } catch (UnsupportedEncodingException e) { log.error("[verificationToken]", e); return false; } } /** * 通过客户端请求参数产生Token */ private static String getToken(Map mapTypes) throws UnsupportedEncodingException { List<String> mapKes = new ArrayList<>(); for (Object obj : mapTypes.keySet { String value = String.valueOf; //去除参数中的加密相关key if (StringUtils.saleEquals(value, Constants.NetWork.AES_SUCCESS) || StringUtils.saleEquals(value, Constants.NetWork.AES_DATA)) { break; } mapKes.add; } //排序key Collections.sort; StringBuilder sb = new StringBuilder(); for (String key : mapKes) { String value = ( mapTypes.get[0]; sb.append; } //加上时间戳,去除所有空格 进行MD5加密 String string = sb.append(DateUtils.getDateStr(DateUtils.FORMAT_YYYYMMDDHH)).toString().replace; return MD5.getMD5(URLEncoder.encode(string, "UTF-8")); }}

3.对返回数据进行加密处理,新建JSON转换器继承自阿里的FastJsonHttpMessageConverter

/** * Describe:Json转换器 将返回数据加密 * Created by 吴蜀黍 on 2018-08-07 13:57 **/@Slf4jpublic class JsonMessageConverter extends FastJsonHttpMessageConverter { @Override protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { OutputStream out = outputMessage.getBody(); try { String jsonString = JSON.toJSONString; log.debug("[writeInternal]======>返回明文数据:{}" + jsonString); //对返回数据进行AES加密 jsonString = AesUtils.encrypt(jsonString, Constants.AES.AES_KEY); log.debug("[writeInternal]======>返回加密数据:{}" + jsonString); out.write(jsonString.getBytes; } catch (CommonBusinessException e) { e.printStackTrace(); log.error("[writeInternal]======>", e); } out.close(); }}

spring中对JSON转换器进行配置

...<mvc:message-converters> <!--<bean >--> <bean > <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/json</value> <value>application/xml;charset=UTF-8</value> </list> </property> <property name="features"> <list> <!-- 默认的意思就是不配置这个属性,配置了就不是默认了 --> <!-- 是否输出值为null的字段 ,默认是false--> <value>WriteMapNullValue</value> <value>WriteNullNumberAsZero</value> <value>WriteNullListAsEmpty</value> <value>WriteNullStringAsEmpty</value> <value>WriteNullBooleanAsFalse</value> <value>WriteDateUseDateFormat</value> </list> </property> </bean> </mvc:message-converters>...

python http简单例子

定义FilterHandle、WebHandle类型

三、测试

1.控制器

/** * Describe:加解密测试 * Created by 吴蜀黍 on 2018-08-08 11:13 **/@Slf4j@Controller@RequestMapping(value = "/test")public class TestController { @RequestMapping(value = "/test.json") @ResponseBody private StandardResult test(Test test) { log.debug("[TestController]======> 接口参数:{}", test.toString; return StandardResult.createSuccessObj; }}

2.测试结果

图片 2客户端图片 3服务端

在后台自动加解密模块中,原本是打算都在JSON转换器中处理,通过readInternal()解密,再通过writeInternal()加密,奈何调试的过程中总会出现一些未知错误,如有相关大神,请帮忙指点迷津!通过过滤器来处理参数有些大材小用的意思,如果哪位有更好的方案和处理方式欢迎留言,感激不尽!!!

python
http框架主要有server和handler组成,server主要是用于建立网络模型,例如利用epoll监听socket;handler用于处理各个就绪的socket;先来看下python
http简单的使用:

type FilterHandle func(rw http.ResponseWriter,r *http.Request) errortype WebHandle func(rw http.ResponseWriter,r *http.Request) error
四、客户端JS下载

客户端JS下载,密码:qxql

import sys
from http.server import HTTPServer,SimpleHTTPRequestHandler

ServerClass = HTTPServer
HandlerClass = SimpleHTTPRequestHandler

if__name__ =='__main__':
 port = int(sys.argv[2])
 server_address = (sys.argv[1],port)
 httpd = ServerClass(server_address,HandlerClass)

sa=httpd.socket.getsockname()
print("Serving HTTP on",sa[0],"port",sa[1],"...")

try:
 httpd.serve_forever()
 except KeyboardInterrupt:
print("nKeyboard interrupt received, exiting.")
 httpd.server_close()
 sys.exit(0)

FilterHandle 拦截器处理函数WebHandle 客户端请求处理函数

运行上述例子,可以得到如下:

http handle模块与拦截器处理函数绑定
在拦截器处理函数中通过闭包回调WebHandle

python3 myhttp.py 127.0.0.1 9999

注册拦截器处理函数func Register(uriRule string,fh FilterHandle)

此时如果在当前文件夹新建一个index.html文件,就可以通过
访问了index.html页面了。

uri 路径匹配规则

这个例子的server类用的是HTTPServer,handler类是SimpleHTTPRequestHandler,因此当HTTPServer监听到有request到来时,就把这个request丢给SimpleHTTPRequestHandler类求处理;ok,了解这些之后,我们开始分别分析下server和handler.

★★★★★ 拦截器注册在http server 启动前

http之server

/** * Created with IntelliJ IDEA. * Description: * User: yangzhao * Date: 2018-08-02 * Time: 11:01 */package web_filterimport ( "net/http" "strings")type FilterHandle func(rw http.ResponseWriter,r *http.Request) error//拦截uri映射处理var filterMapping = make(map[string]FilterHandle,0)//保证有序urivar uriArray = make([]string,0)/** uriRule 路径匹配规则 fh 拦截器处理函数 */func Register(uriRule string,fh FilterHandle) { uriRule = uriRule[:len-2] filterMapping[uriRule]=fh uriArray = append(uriArray,uriRule)}type WebHandle func(rw http.ResponseWriter,r *http.Request) errorfunc Handle(webHandle WebHandle) func(rw http.ResponseWriter,r *http.Request) { return func(rw http.ResponseWriter,r *http.Request){ var uri=r.RequestURI uri+="/" for _,v:=range uriArray{ if strings.Contains { e := filterMapping[v] if e != nil { rw.Write([]byte)) return } } } err := webHandle if err != nil { rw.Write([]byte(err.Error } }}

/** * Created with IntelliJ IDEA. * Description: * User: yangzhao * Date: 2018-08-01 * Time: 16:16 */package testimport ( "net/http" "log" "errors" "testing" "common-go/web/filter" "fmt")type HttpServer struct { http.Server }func (server *HttpServer) StartServer() { log.Println("web server start "+server.Addr) err := server.ListenAndServe() if err != nil { log.Panic }}func (server *HttpServer)ServeHTTP(wr http.ResponseWriter, r *http.Request)() { fmt.Println}func TestWebFilter(t *testing.T) { web_filter.Register("/safe/**", func(rw http.ResponseWriter, r *http.Request)error { return errors.New //return nil }) web_filter.Register("/safe/user/**", func(rw http.ResponseWriter, r *http.Request)error { return errors.New //return nil }) http.HandleFunc("/safe", web_filter.Handle(func(wr http.ResponseWriter,req *http.Request) error{ wr.Write([]byte(req.RequestURI)) return nil })) http.HandleFunc("/safe/user/test", web_filter.Handle(func(wr http.ResponseWriter,req *http.Request) error{ wr.Write([]byte(req.RequestURI)) return nil })) http.HandleFunc("/safe/user", web_filter.Handle(func(wr http.ResponseWriter,req *http.Request) error{ wr.Write([]byte(req.RequestURI)) return nil })) server := &HttpServer{} server.Addr=":8080" server.StartServer()}

http模块的设计充分利用了面向对象的继承多态,因为之前有看了会tfs文件系统的代码,所以再看python
http时,没那么大的压力;先给出server的继承关系

以上属于原创文章,转载请注明作者@怪咖

 +------------------+
+------------+| tcpserver基类 |
| BaseServer +-------->| 开启事件循环监听 |
+-----+------+ | 处理客户端请求 |
 | +------------------+
 v +-----------------+
+------------+| httpserver基类 |
| TCPServer +-------->+设置监听socket |
+-----+------+ | 开启监听 |
 | +-----------------+
 v
+------------+
| HTTPServer | 
+------------+

QQ:208275451

继承关系如上图所示,其中BaseServer和TCPServer在文件socketserver.py,HTTPServer在http/server.py;我们先看下来BaseServer;

BaseServer

因为BaseServer是所有server的基类,因此BaseServer尽可能抽象出所有server的共性,例如开启事件监听循环,这就是每个server的共性,因此这也是BaseServer主要做的使;我们来看下BaseServer主要代码部分

defserve_forever(self, poll_interval=0.5):
 self.__is_shut_down.clear()
try:
with_ServerSelector()asselector:
 selector.register(self, selectors.EVENT_READ)

whilenotself.__shutdown_request:
 ready = selector.select(poll_interval)
ifready:
 self._handle_request_noblock()

 self.service_actions()
finally:
 self.__shutdown_request = False
 self.__is_shut_down.set()

代码中的selector其实就是封装了select,poll,epoll等的io多路复用,然后将服务自身监听的socket注册到io多路复用,开启事件监听,当有客户端连接时,此时会调用self._handle_request_noblock()来处理请求;接下来看下这个处理函数做了啥;

def_handle_request_noblock(self):
try:
 request, client_address = self.get_request()
exceptOSError:
return
ifself.verify_request(request, client_address):
try:
 self.process_request(request, client_address)
except:
 self.handle_error(request, client_address)
 self.shutdown_request(request)
else:
 self.shutdown_request(request)

_handle_request_noblock函数是一个内部函数,首先是接收客户端连接请求,底层其实是封装了系统调用accept函数,然后验证请求,最后调用process_request来处理请求;其中get_request是属于子类的方法,因为tcp和udp接收客户端请求是不一样的(tcp有连接,udp无连接)

我们接下来再看下process_request具体做了什么;

defprocess_request(self, request, client_address):
 self.finish_request(request, client_address)
 self.shutdown_request(request)
# -------------------------------------------------
deffinish_request(self, request, client_address):
 self.RequestHandlerClass(request, client_address, self)

defshutdown_request(self, request):
 self.close_request(request)
You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图