巨大的建筑,总是由一木一石叠起来的,我们何妨做做这一木一石呢?我时常做些零碎事,就是为此。
这是对的,但是我没有说过这句话! —— 鲁迅
问题描述
通过Api为第三方公司提供接口服务是一项很common的需求,其中一种实现方式就是利用restful的形式,提供http接口服务.
提供出接口之后,谁都可以来访问,对系统本身是有一定危险性的,所以我们要做一下验证信息,来确认对方的身份.
对于自己内部的接口,比如用于提供给mask(前台页面)访问的api,可以做登录验证,只有登录了,才可以访问。
但是对于第三方公司而言,不是我们的用户,属于合作伙伴,性质不一样.
我们需要给他们提供10个接口,这10个接口是需要放开权限的,不然直接访问的话,会直接提示:*未登录* 的错误。
权限放开了,那么权限如何验证呢.
权限验证
不增加任何验证,直接可以访问
安全性差,比如推送数据的接口(一般数据是有格式的,他要是自己猜确实不好猜到),假如第三方公司在正常使用的时候,请求数据推送,
被坏人截取到了相应的信息。通过分析,他就可以一直推送,知道把你的数据库填满.
接口的验证
- 客户端访问接口,需要佩戴token,假如这个token正好是我们产生的,则验证通过,token有时效限制。
- 这样一来,需要提供一个接口,便是产生token的接口,而这个接口是开放的,谁都可以访问。
- 那么这时候,假如一个坏人想要恶意访问的话,他首先需要知道这个产生token的接口是什么,其次需要知道自己要访问的接口是什么,然后再访问
安全性比开始的已经大一些了。此时假如他截取到了某次请求的数据,再请求的话,就只能在实效内管用了.
对token产生的接口进行验证处理
上面是对token产生的接口可以随意访问,一旦被人知道了这个接口,则可以随意获取token值了,加一个判断的话,比如需要用户名,和密码,则可以进行一种验证。
简单的写法,就是对不同的第三方协议公司定一个用户名,密码,直接传过来.
除了验证身份之外,加上授权处理.
这种情况就跟咱们的登录差不多了,但是身份不一样。
一般登录的设计里面,都会有角色的设计,身份不一致的话,可以新增一种角色,这种角色就给服务公司提供一种控制,这样就类似于登录了.
但是一般对接公司需要的接口都是固定的,假如需要根据功能收费的话,授权是需要考虑的.
以下主要说一下接口的简单验证.
接口的验证
提供产生token接口
比如直接返回UUID
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseBody
public Result<String, State> getToken(HttpServletRequest request, HttpServletResponse response) {
Result<String, State> result = new Result<>();
UUID uuid = UUID.randomUUID();
String token = uuid.toString();
//放到redis里面,实效为30分钟.
RedisCacheUtil.setex(BUSSINESSKEY, token, token, 30 * 60);
result.setCode(State.SUCCESS);
result.setData(token);
return result;
}
这样,就把生成的token返回了,那么接口的写法如下:
@RequestMapping(value = "/", method = RequestMethod.GET)
@ResponseBody
public Result<String, State> getSomeThing(@RequestParam String token,
String param) {
Result<String, State> result = new Result<>();
String tokenRedis = RedisCacheUtil.get(BUSSINESSKEY, token, String.class);
if (token.equals(tokenRedis) {
result.setCode(State.SUCCESS);
} else {
result.setCode(State.FAILED);
}
return result;
}
关于redis里key值的讨论
开始我打算用请求方的ip,假如是ip的话,那么同一个ip则会用同一个token,不同的ip用不同的token,假如你
知道了别人的token,不是同一个ip,也照样不能访问。
现实发现问题,是因为我们的接口是写在api服务里面,而api服务是一个内网服务,第三方(App)是通过请求我们的nginx服务转发到api里面。
这样的话,每次我获取到的ip,都是我们自己服务器的内网地址,并不能用.
后来发现用token作为key值也可以,这样每个用户,就拥有自己的token了,但是问题是随便一个人一旦获取到了别人的token,在token相应未失效的时候,也是可以用的.
用拦截器来处理token的验证。
按照上面的方式写接口,假如提供10个接口的话,则在每个接口中都需要接收token参数.不好维护。
配置拦截器
<mvc:interceptors>
<!--跨域过滤器-->
<bean class="com.enn.zhwl.interceptor.CrossInterceptor"/>
<mvc:interceptor>
<mvc:mapping path="/hdd/out/**" />
<mvc:exclude-mapping path="/hdd/out/token/**" />
<bean class="com.enn.zhwl.interceptor.HddInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
- springMVC框架中,在servlet.xml中加入拦截器的配置
- mvc:mapping 是改拦截器对哪些类生效
- mvc:exclude-mapping 是将上面生效的文件中剔除一些类
- bean 则是拦截器的实现类.
拦截器实现
public class HddInterceptor implements HandlerInterceptor {
private static final String BUSSINESSKEY = "HDDTOKEN";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getParameter("token");
String tokenRedis = RedisCacheUtil.get(BUSSINESSKEY, token, String.class);
if (token.equals(tokenRedis) {
return true;
} else {
PrintWriter writer = response.getWriter();
writer.write("token error");
writer.close();
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
token作为参数传递的问题
这样是让客户端将token值放到请求的参数中,来进行接收。
现在有一种情况,就是一般接口提供有两种方式。
- 接收普通变量,比如String 等类型的值
- 接收对象类型(@RequestBody)。
接收普通变量和对象类型时,发送的ajax请求的 contentType 是不一样的。
发送对象数据的时候,contentType='application/json',对于这种情况,直接用 request.getParameter 就获取不到了.
token放到cookie中进行传递
一般登录验证的时候,token就是放到cookie中的.放到cookie中可以解决上面的问题。
而且还方便,客户端完全感知不到token的存在,也不用在调用方法的时候,显示传递.
修改设置token的方法
将token放到cookie中而不是放到data中.
public Result<String, State> getToken(HttpServletRequest request, HttpServletResponse response) {
Result<String, State> result = new Result<>();
UUID uuid = UUID.randomUUID();
String token = uuid.toString();
//放到redis里面,实效为30分钟.
RedisCacheUtil.setex(BUSSINESSKEY, token, token, 30 * 60);
//放到cookie中而不是放到data中.
Cookie cookie = new Cookie("hddtoken", token);
cookie.setPath("/");
response.addCookie(cookie);
result.setCode(State.SUCCESS);
return result;
}
修改拦截器的实现
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//String token = request.getParameter("token");
Cookie[] cookies = request.getCookies();
Optional<Cookie> cookieToken = Arrays.stream(cookies).filter(cookie -> cookie.getName().equals("hddtoken")).findFirst();
//这里需要判断token是否存在,就不写了
String token = cookieToken.get().getValue();
String tokenRedis = RedisCacheUtil.get(BUSSINESSKEY, token, String.class);
if (token.equals(tokenRedis) {
return true;
} else {
PrintWriter writer = response.getWriter();
writer.write("token error");
writer.close();
return false;
}
}
这样客户端也不用传了,接收端也不用接收了,一切都默默的执行,只需要在请求之前,先调用一下获取token的接口就行了.
这里是写了一些简单验证是思路与遇到的问题,至于加密啊,密码啊,授权啊等便是另外的事了.
网友评论