最近在搞一个基于WEB API的项目,因为之前没接触过这块,所以走了很多弯路。现在把之前实现授权管理的思路记录一下。
(1)URL里带一个GUID当作令牌
用户输入用户名和密码,提交至api/login/login,API确认身份后在传回的HttpResponseMessage里带一个随机的GUID,服务器上有一个List存放这个ID和用户的对应关系。之后每次访问API时需要提交这个ID作为URL参数,API中使用一个公用方法进行验证。
public static bool LegalRequest(string id)
{//判断客户端提供的令牌是否合法,每次执行时清理无效令牌
var delete = IdentityList.Where(tb => tb.InvalidTime <= DateTime.Now).ToList();
foreach (var a in delete)
{
IdentityList.Remove(a);
}
bool isLegal = false;
if (id == null)
return isLegal;
Guid identity = Guid.Empty;
Guid.TryParse(id, out identity);
if (identity == Guid.Empty)
return isLegal;
if (IdentityList.Where(tb => tb.IdentityID == identity).ToList().Count > 0)
isLegal = true;
return isLegal;
}
这个方式可以拦截未授权的访问,但问题是所有的API以及访问API的地方都要添加相应的代码,而且安全性很低(只要拦截这个ID然后自己拼一个URL就可以绕过登陆)。所以后来改代码的时候用了别的方案。
(2)自定义Cookie+过滤器
后来网上找资料的时候学了过滤器(Filter)的使用方法,果断加到项目里。(http://www.cnblogs.com/Flyear/p/4875066.html)
基本逻辑是登录成功的时候生成一个票据,加密以后存到COOKIE里面。每次请求API的时候在HTTP请求里面带上这个COOKIE,然后在ActionFilterAttribute里对票据进行验证。
public override void OnActionExecuting(HttpActionContext actionContext)
{
try
{
if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0)
{//过滤允许匿名访问的action
base.OnActionExecuting(actionContext);
return;
}
var a = actionContext.Request.Headers;
var cookie = a.GetCookies();//获取Cookies
if (cookie == null || cookie.Count < 1)
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
return;
}
FormsAuthenticationTicket ticket = null;
//遍历Cookies,获取验证Cookies并解密
foreach (var perCookie in cookie[0].Cookies)
{
if (perCookie.Name == FormsAuthentication.FormsCookieName)
{
ticket = FormsAuthentication.Decrypt(perCookie.Value);
break;
}
}
if (ticket == null)
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
return;
}
if (ticket.Expired)
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
return;
}
if (合法)//去数据库里验证
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
return;
}
//if (actionContext.Response.StatusCode!= HttpStatusCode.Accepted)
//{
// actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
// return;
//}
// TODO: 添加其它验证方法
base.OnActionExecuting(actionContext);
}
catch
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
其实这么做和第一种方法差不多,不过拦截和重发cookie的难度稍微大一点点。。。
(3)Session
后来用Fiddler测试API的时候发现,WEB端每次登陆的时候会传一个.NET自带的Cookie(ASP.NET_SessionId),我想了一下,直接用这个Cookie来做身份验证不就省事了。API在登录成功后添加一条Session(同时会自动在context.Request.Cookies里面添加上面的Cookie)客户端提交这个Cookie,API收到Cookie就可以直接从Session中获取用户信息。
//API:登录成功以后添加Session
var context = HttpContext.Current;
context.Session["user"] = userinfo[0].F_UserName;
context.Session["userid"] = userinfo[0].F_UserID;
//WEB端后台:获取登陆成功信息以后修改Cookie
Response.Cookies.Clear();
if (response.Headers["Set-Cookie"] != null)
{
var s = response.Headers["Set-Cookie"].Split(';')[0].Split('=');
Response.Cookies.Add(new HttpCookie(s[0], s[1]));
}//设置cookie为API上的sessioncookie
//API:获取用户信息
Guid.TryParse(HttpContext.Current.Session["userid"].ToString(), out user);//这个代码既可以用来验证身份也可以用来获取用户信息
这个地方实现的时候有一点很坑,就是我项目里的WEB API 和WEB端是放一块的,但是Login页面提交的时候会自带一个SessionID,这个ID和API里的不一样,所以Login页面的后台在登录成功以后要清空原有的Cookie,然后把API返回的Cookie添加进去,而且Login页面的后台还要再添加一次Session供WEB端使用。至于Winform和Android端直接在提交HTTP请求的时候带上这个Cookie就行了。
这种方式不需要自己写很多代码,虽然仍然无法防御重放攻击,不过对于一般的应用已经足够了。