背景
移动互联网一般都需要用 imei 来识别用户,就像我们的业务接口,手机的 imei 和 sn 基本都是必传的参数
最近公司安全部门发布了新的安全规范,所有接口都不允许传 imei 了,甚至这个参数名都是禁忌(估计是担心用户抓包时看到有这么个参数吧),那怎么识别用户呢?
新规范是:客户端将 imei 和 sn 拼接在一起,然后加密,这个密文称为 DeviceID,后台服务一律使用 DeviceID 来识别用户
这样一来,所有需要接收 imei/sn 的接口都受影响,需要改成接收 DeviceID 参数并解密,再解析出 imei 和 sn 来,要改的接口太多,有什么简单的办法批量解决呢?
HttpServletRequestWrapper
介绍
- HttpServletRequestWrapper 实现了 HttpServletRequest 接口,可以让开发人员很方便的改造发送给 Servlet 的请求
- 应用了装饰模式
- 一般要和 Filter 配合应用
简单的说,HttpServletRequestWrapper 可以对原始的请求进行二次封装,比如我们需要 imei 参数,但是客户端没有传,那没关系,我把原始请求封装一层,从 DeviceID 里解析出 imei,使业务接口可以像从前一样拿到 imei 参数值
实现
首先修改 web.xml,添加一个解析 DeviceID 的 Filter
deviceIdParseFilternet.refusea.DeviceIdParseFilterdeviceIdParseFilter/*
Filter 代码
public class DeviceIdParseFilter implements Filter { private static final String KEY = "my-rc4-key"; private static final Logger log = Logger.getLogger(DeviceIdParseFilter.class); private static final String[] DEFAULT_RESULT = {"",""};@Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String deviceId = request.getParameter("deviceId"); if (deviceId != null && deviceId.length() > 0) { String[] result = parseDeviceId(deviceId); DeviceIdParseRequest req = new DeviceIdParseRequest((HttpServletRequest) request, result[0], result[1]); chain.doFilter(req, response); } else { chain.doFilter(request, response); } } /** *
* 从 deviceId 里解析出 imei 和 sn * * imei = result[0] * sn = result[1] *
* * @param deviceId * @return */ private static final String[] parseDeviceId(String deviceId) { try { String src = Rc4Util.decrypt(deviceId, KEY); if (src.indexOf('_') >= 0) { return src.split("_"); } } catch (Exception e) { log.error(e, e); } return DEFAULT_RESULT; } @Override public void destroy() { }}
这个 Filter 会将包含 deviceId 参数的请求进行如下处理
- 将 deviceId 的值用 RC4 算法进行解密
- 从解密出来的 deviceId 里解析出 imei 和 sn
- 将请求改封装成 DeviceIdParseRequest,即 HttpServletRequestWrapper
- 不包含 deviceId 参数的请求不做任何处理
HttpServletRequestWrapper 代码
public class DeviceIdParseRequest extends HttpServletRequestWrapper { private String imei; private String sn; public DeviceIdParseRequest(HttpServletRequest request) { super(request); this.imei = ""; this.sn = ""; } public DeviceIdParseRequest(HttpServletRequest request, String imei, String sn) { super(request); this.imei = imei; this.sn = sn; } @Override public String getParameter(String name) { if ("imei".equals(name)) { return imei; } else if ("sn".equals(name)) { return sn; } else { return super.getParameter(name); } } @Override public String[] getParameterValues(String name) { if ("imei".equals(name)) { return new String[] { imei }; } else if ("sn".equals(name)) { return new String[] { sn }; } else { return super.getParameterValues(name); } }}
在 DeviceIdParseRequest 里针对 imei 和 sn 进行了特殊处理,返回的不是客户端提交的参数,而是在 Filter 里通过解析 deviceId 得到的 imei 和 sn
需要注意的是
- 如果你的业务代码是用 request.getParameter() 获取客户端请求参数的值,那么只需要重写该方法就行了
- 如果你的业务代码用 SpringMVC 的 @RequestParam 注解来获取请求参数的值,那么需要重写 getParameterValues 方法:因为 SpringMVC 是用这个方法来获取参数值的
现在,不用修改任何原有的接口,我们已经实现了从 imei 到 DeviceID 的切换
![3ea4e3fe6016ffd185e40ae7138b7169.png](https://i-blog.csdnimg.cn/blog_migrate/52a47c71d0bacb41446746169122c81b.jpeg)