最近项目第一次尝试使用web api,照搬了一般mvc的Forms登录方式,在和前端对接的时候出现一个问题:

  前端使用ajax调用登录接口完成登录后,再调用别的接口,被判断为未登录。

  如果直接在浏览器中先后访问登录接口和别的接口,则能识别为已登录。

对于asp.net的这些机制其实我了解不多,所以我猜测为跨域导致了两次调用的http上下文不一致造成的,当时我们解决跨域问题的方式是在服务端配置文件的system.webServer节点下加入:

<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
</customHeaders>
</httpProtocol>

我以为是这种解决方式不够完善,于是我开始在网上寻找并尝试各种解决跨域的方案,最重型的尝试就是把我的.net4.0版本的web api升级到.net4.5的web api2来使用Microsoft ASP.NET Web API 2 Cross-Origin Suppor支持跨域,结果发现这种方法只是控制更精细,和我们最开始用的那种简单粗暴的方式没有本质上的差别,别的方法也都是大同小异,于是此路不通。

此时我想到自己定义一个简单的登录,具体实现就是:

  1.登录时创建一个票据,并在服务端保存一组票据和用户信息的键值对,将票据返回给客户端,客户端在访问需要登录的接口时必须带上此票据

  2.给需要登录验证的接口添加一个自定义的AuthorizeAttribute,在此属性中获取客户端传递的票据,来验证票据是否存在或者过期,如果票据合法,将对应的用户信息添加到http上下文,如果票据不合法,返回用户未登录的提示

由于本人功力有限,并且因为项目涉及到充值提现等资金操作,已经定了要使用SSL,所以关于篡改,复用等传输安全方面的问题没有纳入考虑。

以下是代码:

用户信息模型(登录时将返回此信息至客户端):

public class MemberTicket
{
public string ID { get; set; }
public string LoginName { get; set; }
public string Token { get; set; }
public DateTime LoginDate { get; set; }
}

登录处理类:

/// <summary>
/// 自定义登录
/// </summary>
public class LoginHelper
{
/// <summary>
/// 用户信息集合
/// </summary>
private static Dictionary<string, MemberTicket> Members = new Dictionary<string, MemberTicket>();
/// <summary>
/// 登录
/// </summary>
/// <param name="ticket">用户信息</param>
public static void Login(MemberTicket ticket)
{
if (Members.Keys.Contains(ticket.Token))
Members[ticket.Token] = ticket;
else
Members.Add(ticket.Token, ticket);
}
/// <summary>
/// 退出登录
/// </summary>
/// <param name="Token">票据</param>
public static void SignOut(string Token)
{
if (Members.Keys.Contains(Token))
Members.Remove(Token);
}
/// <summary>
/// 根据票据检查票据是否合法
/// </summary>
/// <param name="Token">票据</param>
/// <returns></returns>
public static MemberTicket Check(string Token)
{
if (!string.IsNullOrEmpty(Token) && Members.Keys.Contains(Token))
{
MemberTicket ticket = Members[Token];
if (ticket != null && ticket.LoginDate.AddMinutes(CommonData.TimeOut) > CommonData.TimeNow())
return ticket;
}
return null;
}
}

定义用户对象(能保存于http上下文的结构):

public class MemberPrincipal : IPrincipal
{
private string loginname; public string Loginname
{
get { return loginname; }
set { loginname = value; }
} private IIdentity _Identity; public IIdentity Identity
{
get { return _Identity; }
set { _Identity = value; }
} public bool IsInRole(string role)
{
return false;
} public MemberPrincipal(string Name)
{
loginname = Name;
_Identity = new GenericIdentity(loginname, "Forums");
bool isok = _Identity.IsAuthenticated;
}
}

保存用户信息到http上下文:

public class PrincipalHelper
{
public static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
}

关键部分,自定义登录策略:

public class LoginAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext httpContext)
{
MemberTicket ticket = LoginHelper.Check(GetToken(httpContext.Request.RequestUri.Query));
if (ticket == null)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); ResponseMessage<string> result = new ResponseMessage<string>();
result.Header = new ResponseHeader();
result.Header.State = (int)ResponseHeaderState.SignOut;
result.Header.Message = "用户未登录";
response.Content = new StringContent(JsonConvert.SerializeObject(result)); httpContext.Response = response;
}
else
PrincipalHelper.SetPrincipal(new MemberPrincipal(ticket.LoginName)); } public string GetToken(string Query)
{
if (!Query.Contains("Token"))
return null;
string[] Param = Query.Split('&');
if (Param.Length == )
return null;
foreach (var item in Param)
{
if (!item.Contains("Token"))
continue;
string[] value = item.Split('=');
if (value.Length == )
return null;
return value[];
}
return null;
}
}

返回消息结构:

public class ResponseMessage<T>
{
/// <summary>
/// 消息头
/// </summary>
public ResponseHeader Header { get; set; }
/// <summary>
/// 消息本体
/// </summary>
public T Body { get; set; }
} public class ResponseHeader
{
/// <summary>
/// 执行状态
/// </summary>
public int State { get; set; }
/// <summary>
/// 消息
/// </summary>
public string Message { get; set; }
}

最后将此属性加在需要登录验证的控制器或方法上即可。

做完之后,目前是达到了我的预期,但是我的预期太低,所以自己也感觉弄得很low……希望各位大神小神大牛小牛不吝赐教,指点指点我应该往哪个方向优化登录机制

Web Api跨域登录问题的更多相关文章

  1. Web Api跨域访问配置及调用示例

    1.Web Api跨域访问配置. 在Web.config中的system.webServer内添加以下代码: <httpProtocol> <customHeaders> &l ...

  2. ASP.NET Web API 跨域访问(CORS)

    一.客户端用JSONP请求数据 如果你想用JSONP来获得跨域的数据,WebAPI本身是不支持javascript的callback的,它返回的JSON是这样的: {"YourSignatu ...

  3. ABP框架Web API跨域问题的解决方案

    ​1.在Web Api 项目下安装 Microsoft.AspNet.WebApi.Cors 包 Install-Package Microsoft.AspNet.WebApi.Cors 2.在Web ...

  4. web api 跨域请求,ajax跨域调用webapi

    1.跨域问题仅仅发生在Javascript发起AJAX调用,或者Silverlight发起服务调用时,其根本原因是因为浏览器对于这两种请求,所给予的权限是较低的,通常只允许调用本域中的资源,除非目标服 ...

  5. Web Api 跨域解决方案

    一.跨域问题的由来 同源策略:出于安全考虑,浏览器会限制脚本中发起的跨站请求,浏览器要求JavaScript或Cookie只能访问同域下的内容. 正是由于这个原因,我们不同项目之间的调用就会被浏览器阻 ...

  6. Web API 跨域请求

    分布式技术在项目中会频繁用到,以前接触过WebService(可跨平台).WCF(功能强大,配置繁琐),    最近由于上层业务调整,将原来的MVC项目一分为三,将数据层提取出来,用API去访问.然后 ...

  7. asp.net web api 跨域问题

    缘起 以前在asp.net mvc时代,很少出现跨域问题 自从使用了asp.net web api + angular (1/2)之后,开始有跨域问题了. 简单普及下跨域: 我的理解是只要是前台页面与 ...

  8. ASP.NET web api 跨域请求

    1.学习文章:AJAX 跨域请求 - JSONP获取JSON数据 1.asp.net代码 参考文章:http://www.sxt.cn/info-2790-u-756.html (1).增加CorsH ...

  9. Web API(五):Web API跨域问题

    一.什么是跨域问题 跨域:指的是浏览器不能执行其他网站的脚本.是由浏览器的同源策略造成的,是浏览器施加的安全限制.(服务端可以正常接收浏览器发生的请求,也可以正常返回,但是由于浏览器的安全策略,浏览器 ...

随机推荐

  1. 集成学习_Bagging 和随机森林(rf)

       集成学习方式总共有3种:bagging-(RF).boosting-(GBDT/Adaboost/XGBOOST).stacking      下面将对Bagging 进行介绍:(如下图所示) ...

  2. [CodeForces] CF226D The table

    Harry Potter has a difficult homework. Given a rectangular table, consisting of n × m cells. Each ce ...

  3. 爬虫文件存储:txt文档,json文件,csv文件

    5.1 文件存储 文件存储形式可以是多种多样的,比如可以保存成 TXT 纯文本形式,也可以保存为 Json 格式.CSV 格式等,本节我们来了解下文本文件的存储方式. 5.1.1 TXT文本存储 将数 ...

  4. Jupyter Notebook 下安装 PHP 内核

    我最近被强烈安利了 Jupyter Notebook 这个交互式笔记本.然后试用了它自带的 Python 内核后,这个应用整体给我的感觉很不错,就去搜索了下它所支持的其它内核 Jupyter Kern ...

  5. 腾讯云,搭建LNMP环境

    LNMP代表的就是:Linux系统下Nginx+MySQL+PHP这种网站服务器架构. Linux是一类Unix计算机操作系统的统称,是目前最流行的免费操作系统.代表版本有:debian.centos ...

  6. .Net防sql注入的方法总结

    #防sql注入的常用方法: 1.服务端对前端传过来的参数值进行类型验证: 2.服务端执行sql,使用参数化传值,而不要使用sql字符串拼接: 3.服务端对前端传过来的数据进行sql关键词过来与检测: ...

  7. selenium常用操作,查找元素,操作Cookie,获取截图,获取窗口信息,切换,执行js代码

    目录: 1. 常用操作 2. 查找元素 3. 操作Cookie 4. 获取截图 5. 获取窗口信息 6. 切换 7. 执行JS代码 简介 selenium.webdriver.remote.webdr ...

  8. Maven学习总结(7)——eclipse中使用Maven创建Web项目

    Maven学习总结(七)--eclipse中使用Maven创建Web项目 一.创建Web项目 1.1 选择建立Maven Project 选择File -> New ->Project,如 ...

  9. noip模拟赛 逃避

    题目描述 给定一篇只含有大小写字母,空格以及 ′.′(不含引号)的长度为 L 的文章.文章被若干个 ′.′ 划分 成若干个句子,句子被若干个空格划分成单词.你需要将文章中每个句子第一个单词的首字母改成 ...

  10. 中缀表达式转逆波兰式(后缀表达式)求值 C++ Stack

    给一个包含小数的中缀表达式 求出它的值 首先转换为后缀表达式然后利用stack求出值 转换规则: 如果字符为'('  push else if 字符为 ')' 出栈运算符直到遇到‘(' else if ...