App接口之Token令牌实现
《App接口之Token令牌实现》
转载请注明来自 傻小孩b_移动开发(http://www.jianshu.com/users/d388bcf9c4d3)喜欢的可以关注我,不定期总结文章!您的支持是我的动力哈!
1、目的
众所周知,在web端中,Token(令牌)只是作为防止用户重复提交表单的作用而存在。但是对于App客户端而言,Token却充当着另一种角色,类似现实生活中代表每个人的角色认证、或者类似浏览器cookie代表你访问网站的角色认证。前提,在有用户系统的应用中,在每次访问接口的时候,为了避免接口裸露被被无止境的请求攻击,往往我们会利用一种机制,过滤一切非应用用户端的非合法请求。首先我们不可能每次利用账号密码作为我们的过滤标准(会存在被抓包密码泄露风险),因此便有
Token(令牌)的存在。即在存在这里的Token是指在指定有效时间内可以代表用户角色,具有请求接口的权限。
当然,这里有开发者会提问,为什么不适用session。理论上是可以的,只是如果是有接触过这部分的移动开发者,session本地是不好处理的,并且完全依赖
session,会被黑客截取后模拟请求,依然会存在被攻击的风险。
2、实现思路
这里我不做加密方式选择的举例,这里只是做了简单的做了不可逆的MD5加密方式(账号+时间戳)。首先web框架是利用(spring + struct2 + mybatis),简单说明下实现思路:
1、用户登录。请求登录接口,如果账号密码核对正确,会根据账号和时间戳进行Md5加密生成Token
2、服务端双向保存token。服务端根据有效时间内生成对应的token之后,服务端双向保存了Memcache中(
Memcache 是一种分布式缓存存储机制,这里我就不详细说明了),最后通过登录接口返回Token信息至客户端中
3、客户端保存返回Token。
客户端通过登录接口成功返回的token,保存的内存中,在每次请求接口都要携带这个token,进行接口请求。
具体思路图如下所示:
![](https://img.haomeiwen.com/i2516602/cfa831b976d5b952.png)
![](https://img.haomeiwen.com/i2516602/0717acbd55e69e27.png)
3、案例
(1)登录接口实现
/**
* 用户登录
* @throws IOException
*/
public void login() throws IOException{
System.out.println("- AppUserManagerAction -" + "login");
String token = TokenUtils.setToken(testAccount);
System.out.println("账号 " + testAccount + "生成的token:"+token);
JSONObject json = new JSONObject();
json.put("result", "0");
json.put("reason", "用户");
json.put("token", token);
this.getResponse().setContentType("text/plain");
this.getResponse().setCharacterEncoding("UTF-8");
this.getResponse().getWriter().write(json.toString());
this.getResponse().getWriter().flush();
}
(2)token生成
/**
* 为了登陆 或 刷新 Token 生成对应token
*/
public static String setToken(String account){
String token = generateToken(account);
// 存储正反向
MemcacheManager.set(account + TOKEN_MARKER, token,TOKEN_VAILD_TIME);
MemcacheManager.set(token,account + TOKEN_MARKER,TOKEN_VAILD_TIME);
return token;
}
(3)struct2 请求拦截
首先在接口请求中,为了对token进行验证,这里是直接在
struct2中写了一个拦截器对请求接口用户Token的验证,有疑问的可以自己谷歌搜索下struct2自定义拦截器
/**
* Token 拦截器 用于检测Token是否过去,过期直接不执行Action
* @author wsy
*
*/
public class TokenInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
System.out.println("-- TokenInterceptor --");
Object action = invocation.getAction();
if (action instanceof MngUserAction || action instanceof AppUserManagerAction) {
if (invocation.getProxy().getActionName().equals("login")) {
System.out.println("-- TokenInterceptor -- " + "login 不需要拦截");
return invocation.invoke();
}
}
// 取得请求相关的ActionContext实例
ActionContext actionContext = invocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST);
HttpServletResponse response = (HttpServletResponse)actionContext.get(org.apache.struts2.StrutsStatics.HTTP_RESPONSE);
if (TokenUtils.isVaild(request)) {
System.out.println("token 有效");
invocation.invoke();
}else{
System.out.println("token 过期");
JSONObject json = new JSONObject();
json.put("result", "002");
json.put("reason", "Token 过期");
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json.toString());
response.getWriter().flush();
}
return null;
}
}
(4)struct2 配置
<!-- struts 设置默认配置 -->
<package name="struts-shop" extends="struts-default">
<interceptors>
<!-- 默认拦截器 -->
<interceptor name="authority" class="employee.utils.TokenInterceptor" />
<!-- 拦截器栈 -->
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="authority" />
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack" />
</package>
<package name="/employee/application/action" namespace="/employee/application/action"
extends="struts-shop">
<action name="login" class="employee.application.action.AppUserManagerAction"
method="login" />
<action name="getPersonInfo" class="employee.application.action.AppUserManagerAction"
method="getPersonInfo"/>
</package>
4、总结
当然在正式平台,加密方式不会这么简单,具体可以看下
http://blog.csdn.net/jack85986370/article/details/51362278 这篇文章,非对称加密就有很强的加密方式,通过公钥与私钥进行信息加密。有什么问题可以联系笔者,欢迎技术交流哈~