• Web Api跨域登录问题


    最近项目第一次尝试使用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 == 0)
    return null;
    foreach (var item in Param)
    {
    if (!item.Contains("Token"))
    continue;
    string[] value = item.Split('=');
    if (value.Length == 0)
    return null;
    return value[1];
    }
    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……希望各位大神小神大牛小牛不吝赐教,指点指点我应该往哪个方向优化登录机制

  • 相关阅读:
    java加密算法-MD5
    java加密算法-DES
    java加密算法-AES
    java写入内容到本地文件 -读取文件内容
    java 图片base64互转
    java上传文件
    判断请求是否是同一个域名
    java计算两个经纬度之间的距离
    java请求url可以带参数
    Java编程基础篇第五章
  • 原文地址:https://www.cnblogs.com/chimeile/p/WebApi.html
Copyright © 2020-2023  润新知