移动开发中一定会涉及后台API如何访问,如何控制访问权限,保证系统安全等问题。
介绍一下我最近自己做的一个移动端访问后端API的例子,例子写的很粗糙,但是基本实现了以token auth 为核心的demo。
在实现demo过程中也找到了不少的好资料,这一篇《APP中用户验证方案》详细的介绍了APP用户验证的不同方案。
大体思路:
APP登录界面输入用户信息登录;服务端验证登录信息,验证成功,向APP端发送token,将token保存到数据库或者缓存服务器中;APP登录成功后,每次调用后端API时都需要带着token、时间戳、sign(token+时间戳的md5字符串,sign可以自己定义算法以防止被盗用)信息,以便后台验证访问API权限;后端在接收到APP访问请求时,比对APP发送来的token与缓存服务器中已经存在的token是否一致,并验证token时效性。
好了,废话少说,上代码:
前端代码(APP,最近写的APP基本都是H5方式实现):
<label>用户名</label>
<input type="text" name="username" id="username" value="" />
<br/>
<label>密码</label>
<input type="password" name="password" id="password" value="" />
<input type="button" name="login" id="login" value="登录" />
<script type="text/javascript">
document.getElementById("login").addEventListener('click',function () {
//alert('hello');
//ajax提交验证信息到后端
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
var data = "{\"username\":\"" + username +"\",\"password\":\"" + password + "\"}";
var url = "/AppTest/loginServlet";
var wd = "123";
var headData = ["123","123456789012345","gsfgfgbdf"];
postData(url,data, headData,function (backdata) {
if(backdata != null){
console.log("返回的信息为: " + backdata);
}
},wd);
});
</script>
这里的ostData使用的是封装好的Ajax函数,下面一并放出代码:
ajax.js:
/*对ajax进行简易封装,便于每次调用,省去参数设置*/
function postData(url, data, headData, callback, waitingDialog) {
mui.ajax(url,{
data:data,
dataType:'json',
type:'post',
headers: {
"token" : headData[0],
"timesamp" : headData[1],
"sign" : headData[2]
},
contentType:"application/x-www-form-urlencoded; charset=utf-8",
timeout:20000,
success:callback,
error:function(xhr,type,errorThrown){
//waitingDialog.close();
alert("<网络连接失败,请重新尝试一下>", "错误", "OK", null);
}
});
}
ajax中加上了headers参数,这里加headers参数的目的是,后台使用servlet+filter的方式控制访问,如果将token等其他参数直接和表单数据同时提交,filter在获取token信息后,servlet就不能获取到表单数据,这就比较尴尬了,或者后台使用AOP的方式控制访问权限 ,这个还没有来得及尝试。如果我能把token信息加到于表单数据不同的存储区域就好了,Google后找到了可以把数据加到请求的header中可以实现将token数据与表单数据分离的效果。
过滤器(Java实现 JDK1.8 Tomcat8.5.6):
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import me.wlc.wx.web.tool.Md5;
/**
* 使用注解标注过滤器
* @WebFilter将一个实现了javax.servlet.Filte接口的类定义为过滤器
* 属性filterName声明过滤器的名称,可选
* 属性urlPatterns指定要过滤 的URL模式,也可使用属性value来声明.(指定要过滤的URL模式是必选属性)
* urlPatterns="/*" 表示过滤掉所有请求
*/
@WebFilter(filterName="AccessFilter",urlPatterns="/*")
public class AccessFilter implements Filter {
@Override
public void destroy() {
System.out.println("过滤器销毁");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("执行过滤操作");
HttpServletRequest req = (HttpServletRequest) request;
String uri = req.getRequestURI();
System.out.println("uri is : " + uri);
//对请求的uri(即api)进行判断,如果是登录的uri则直接放行,如果是其他api则对sign进行验证操作
if( !uri.endsWith("loginServlet") ){
//从请求的url中取出token、时间戳、sign
String token = req.getHeader("token");
String timesamp = req.getHeader("timesamp");
String sign = req.getHeader("sign");
System.out.println("sign is : " + sign);
StringBuffer requestUrl = req.getRequestURL();
System.out.println("请求的Url是: " + requestUrl);
//对token、timesamp 进行md5计算
String signMd5 = Md5.getMD5(token + timesamp);
if(sign.equals(signMd5)){
//签名通过
chain.doFilter(request, response);
}else{
//签名不通过,向app后端发送错误信息,提示重新登录
}
}else{
//登录操作
chain.doFilter(request, response);
}
//请求通过
//chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("过滤器初始化");
}
}
后端还没有实现验证token时效性功能,也没有将token存储到缓存服务器中,这些需要具体方案定下来以后再加上了。再说一句,生成的token保证token值唯一即可,可以使用最方便的UUID(Java中)。
大体就是这样了,有问题随时提给我哦!
PS:2018年9月28日更新
一直没有登录查看留言,给留言板的各位兄弟道个歉,没有及时回复
token存储方案:
方案一:使用session的超时时间来控制token的失效时间
方案二:使用redis存储token的失效时间
方案一适用于单机环境,可以作为小型系统的token时效性验证方案,此方案若处于分布式环境会导致session在分布式机器间的时间不同步,但跟组里的其他工程师沟通,在分布式环境下是不是还可以同步session?
方案二适用于大型系统,还要使用redis服务,使用redis存储token可是实现快速读取token时效数据,若将toke失效时间存储到数据库中,频繁操作数据库,会影响数据库的响应时间,进而应用程序也会出现卡顿等情况。
另,token的失效时间应该设置多长需要根据所在项目的实际情况确定。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。