• 从微信授权到JWT认证——玩转token之路


    一、写在微信前

    是前段时间还是上几年来着,我记不太清了,只记得那个时候的我手机流量每个月是150MB,当然不是这个数字问题,而是由该数字引发的周围人对本人的各种调侃,使大家惊讶的是一个月150MB流量竟然用不完,而使我不解的是他们竟然接受不了用不完这个事实。那个时候的我,对流量的认知,更多的是手机上,直到后来参加工作,才开始广泛起来。

    流量这个东西,很神奇,我曾一度请教百度百科;流量,本义是单位时间内通过河、渠或管道某一横截面的流体的量,或是通过道路的车辆、人员等的数量;在互联网时代,也指在一定时间内网站的访问量,以及手机等移动终端上网所耗费的字节数。

    不难理解,一个网站的访问量大,说明该网站知名度高,受欢迎程度就高。从产品的角度出发,进而有了引流的概念。1月9号,也就是前几天,在微信公开课PRO微信之夜上,官方给出,微信已达到了拥有10亿DAU,那么,通过微信来引流,无疑是一个非常之简单又有效的途径。

    二、微信授权

    不通过用户登录名和密码而获取到用户信息,需要微信授权登录,根据微信返回的授权码code,获取访问令牌:

    /// <summary>
    /// 获取访问令牌
    /// </summary>
    /// <param name="code"></param>
    /// <param name="requestAuth"></param>
    /// <returns></returns>
    [HttpPost]
    public async Task<Domain.ValueObject.RestfulData<Domain.ValueObject.AccessTokenObj>> GetAccessToken([System.Web.Http.FromUri]string code,[System.Web.Http.FromBody]Models.RequestAuthViewModel requestAuth)
    {
    var model = new Domain.ValueObject.RestfulData<Domain.ValueObject.AccessTokenObj>();
    try
    {
    if (string.IsNullOrEmpty(code))
    {
    throw new ArgumentNullException("code");
    }
    if (string.IsNullOrEmpty(requestAuth.encryptData))
    {
    throw new ArgumentNullException("encryptData");
    }
    if (string.IsNullOrEmpty(requestAuth.iv))
    {
    throw new ArgumentNullException("iv");
    }
    var result = await GetOpenIdAndSessionId(code: code);

    Domain.ValueObject.AccessTokenObj accessToken = null;
    if (result.code == 1)
    {
    var data = (Dictionary<string, object>)result.data;
    if (data.TryGetValue("session_key", out object sessionKey))
    {
    //处理字符
    string strSessionKey = sessionKey.ToString();//处理
    string strUserInfoData = Common.Security.AES.Decrypt(requestAuth.encryptData.Trim(), strSessionKey, requestAuth.iv);

    var userData = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(strUserInfoData);//从解密数据中,拿到用户信息字典
    string strOpenId = Convert.ToString(userData["openId"]);

    //根据openid或unionid判断用户是否存在
    var user = await userService.GetUserByOpenId(strOpenId);
    //用户存在
    if (user != null)
    {
    accessToken = CreateJwtToken(uid: user.Id, name: user.NickName);
    }
    //用户不存在
    else
    {
    //注册实体
    Domain.Entity.User userEntity = new Domain.Entity.User()
    {
    Mobile = string.Empty,
    //Password = Guid.NewGuid().ToString("N"),
    NickName = Convert.ToString(userData["nickName"]),
    Avatar = Convert.ToString(userData["avatarUrl"]),
    OpenId = strOpenId,
    CreatedOn = DateTime.Now,
    ModifyOn = DateTime.Now,
    LastLoginTime = DateTime.Now
    };
    var userId = await userService.Register(user: userEntity);//注册新用户
    if (userId > 0)
    {
    accessToken = CreateJwtToken(uid: userId, name: userEntity.NickName);
    }
    }
    }
    model.code = 1;
    model.data = accessToken;
    model.message = "授权成功!";
    }

    }
    catch (Exception ex)
    {
    model.code = 0;
    model.message = ex.Message;
    logger.Error("根据授权码,获取用户访问令牌", ex);
    return model;
    }
    return model;
    }

    /// <summary>
    /// 根据授权码,获取用户OpenId
    /// </summary>
    /// <param name="code"></param>
    /// <returns></returns>
    public async Task<Domain.ValueObject.RestfulData<object>> GetOpenIdAndSessionId(string code)
    {
    var result = new Domain.ValueObject.RestfulData<object>();
    //公众号()
    string appId = "", secret = "";
    string strWxApiUrl = $"https://api.weixin.qq.com/sns/jscode2session?appid={appId}&secret={secret}&js_code={code}&grant_type=authorization_code";
    //string strWxApiUrl = string.Format("https://api.weixin.qq.com/sns/jscode2session?appid=@appId&secret=@secret&js_code=@code&grant_type=authorization_code", new { appId, secret, code });
    try
    {
    using (HttpClient client = new HttpClient())
    {
    var response = await client.GetAsync(requestUri: strWxApiUrl);
    var strResponse = await response.Content.ReadAsStringAsync();
    result.code = 1;
    result.message = "数据获取成功";
    var data = Deserialize<System.Collections.Generic.Dictionary<string, object>>(strResponse);
    result.data = data;
    }
    }
    catch (Exception ex)
    {
    result.code = 0;
    result.message = ex.Message;
    logger.Error("获取用户OpenId:", ex);
    return result;
    }
    return result;
    }

    /// <summary>
    /// 创建令牌
    /// </summary>
    /// <param name="uid"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    public Domain.ValueObject.AccessTokenObj CreateJwtToken(long uid, string name)
    {
    var result = new Domain.ValueObject.AccessTokenObj();
    var claims = new List<Claim>
    {
    new Claim(ClaimTypes.NameIdentifier,uid.ToString()),
    };
    if (string.IsNullOrEmpty(name) == false)
    {
    claims.Add(new Claim(ClaimTypes.Name, name));
    }
    var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(System.Configuration.ConfigurationManager.AppSettings["JwtSecurityKey"]));
    var expires = DateTime.Now.AddDays(28);//
    var token = new JwtSecurityToken(
    issuer: "www.iyuanchang.com",
    audience: "www.iyuanchang.com",
    claims: claims,
    notBefore: DateTime.Now,
    expires: expires,
    signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
    //生成Token
    string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
    result.AccessToken = jwtToken;
    result.Expires = Common.Utility.Util.ToUnixTime(expires);
    return result;
    }

    三、JWT认证

    public void Configuration(IAppBuilder app)
    {

    #region JWT 认证中间件
    var issuer = System.Configuration.ConfigurationManager.AppSettings["issuer"];//发行者
    var audience = System.Configuration.ConfigurationManager.AppSettings["audience"];//观众
    //var secret = TextEncodings.Base64Url.Decode(SharpFramework.Common.Constants.SystemConstant.JWT.Security.SecretKey);//秘钥
    var secret = System.Text.Encoding.UTF8.GetBytes(System.Configuration.ConfigurationManager.AppSettings["JwtSecurityKey"]);
    var signingKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(secret);

    //令牌验证参数
    TokenValidationParameters tokenValidationParameters = new TokenValidationParameters()
    {
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = signingKey,
    // Validate the JWT Issuer (iss) claim
    ValidateIssuer = true,
    ValidIssuer = issuer,
    // Validate the JWT Audience (aud) claim
    ValidateAudience = false,
    ValidAudience = audience,

    // Validate the token expiry
    ValidateLifetime = true,

    ClockSkew = TimeSpan.Zero
    };

    //配置JwtBearer授权中间件 这个中间件的作用主要是解决由JWT方式提供的身份认证。算是Resource资源,认证服务器需要另外开发
    app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions()
    {
    TokenValidationParameters = tokenValidationParameters,
    AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
    AuthenticationType = "Bearer",
    AllowedAudiences = new[] { audience },
    Description = new AuthenticationDescription(),
    Realm = "",//领域,范围;
    Provider = new OAuthBearerAuthenticationProvider()
    {
    //验证当前访客身份
    OnValidateIdentity = context =>
    {
    AuthenticationTicket ticket = context.Ticket;

    //校验身份是否过期
    if (ticket.Properties.ExpiresUtc < DateTime.Now)
    {
    context.SetError("the token has expired!");
    context.Rejected();
    }
    else
    {
    context.Validated(ticket);
    }
    return Task.FromResult<object>(context);
    },

    //处理授权令牌 OAuthBearerAuthenticationHandler
    //headers Authorization=Bear:token
    OnRequestToken = (context) =>
    {
    try
    {
    context.Token = context.Token ?? context.Request.Query["access_token"];
    if (context.Token != null ||
    context.Request.Headers["Authorization"] != null)
    {
    context.Response.Headers["WWW-Authorization"] = "Bearer";
    //protector
    IDataProtector protector = app.CreateDataProtector(typeof(OAuthAuthorizationServerMiddleware).Namespace, "Access_Token", "v1");
    JwtFormat ticketDataFormat = new JwtFormat(tokenValidationParameters);
    var ticket = ticketDataFormat.Unprotect(context.Token);//从令牌字符串中,获取授权票据
    context.Request.User = new ClaimsPrincipal(ticket.Identity);
    }
    }
    catch (Microsoft.IdentityModel.Tokens.SecurityTokenValidationException ex)
    {
    Logger.Error("", ex);
    context.Response.ContentType = "application/json;charset=utf-8";
    context.Response.StatusCode = 400;
    return context.Response.WriteAsync("{"code":400,"message":"令牌无效或令牌已过期!"}");
    }
    catch (Exception ex)
    {
    Logger.Error("", ex);
    context.Response.ContentType = "application/json;charset=utf-8";
    context.Response.StatusCode = 500;
    return context.Response.WriteAsync("{"code":500,"message":"" + ex.Message + "!"}");
    }
    return Task.FromResult<object>(context);
    },
    OnApplyChallenge = (context) => { return Task.FromResult<object>(context); }
    }

    });
    #endregion JWT 认证中间件

    }

    辅助代码:

    public class RequestAuthViewModel
    {
    /// <summary>
    ///
    /// </summary>
    public string encryptData { get; set; }

    /// <summary>
    ///
    /// </summary>
    public string iv { get; set; }
    }

    /// <summary>
    ///
    /// </summary>

    public class AccessTokenObj
    {
    /// <summary>
    ///
    /// </summary>
    public string AccessToken { get; set; }

    /// <summary>
    ///
    /// </summary>
    public long Expires { get; set; }
    }

    /// <summary>
    ///
    /// </summary>
    public class RestfulData
    {
    /// <summary>
    /// <![CDATA[错误码]]>
    /// </summary>
    public int code { get; set; }
    /// <summary>
    ///<![CDATA[消息]]>
    /// </summary>
    public string message { get; set; }
    /// <summary>
    /// 用户Id
    /// </summary>
    public int user { get; set; }
    /// <summary>
    /// <![CDATA[相关的链接帮助地址]]>
    /// </summary>
    public string url { get; set; }

    }
    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class RestfulData<T> : RestfulData
    {
    /// <summary>
    /// <![CDATA[数据]]>
    /// </summary>
    public virtual T data { get; set; }
    }
    /// <summary>
    /// <![CDATA[返回数组]]>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class RestfulArray<T> : RestfulData<IEnumerable<T>>
    {
    /// <summary>
    /// 当前页
    /// </summary>
    public int page { get; set; }

    /// <summary>
    /// 每页显示记录
    /// </summary>
    public int size { get; set; }

    /// <summary>
    /// 总记录数
    /// </summary>
    public int count { get; set; }

    /// <summary>
    /// [只读]页数
    /// </summary>
    public int pageCount
    {
    get
    {
    if (count > 0 && size > 0)
    {
    return (count + size - 1) / size;
    }
    return 0;
    }
    }
    }

    /// <summary>
    /// <![CDATA[反序列化]]>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="input"></param>
    /// <returns></returns>
    protected virtual T Deserialize<T>(string input)
    {
    return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(input, SerializerSettings);
    }
    /// <summary>
    /// <![CDATA[序列化]]>
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    protected virtual string S(object value)
    {
    return Newtonsoft.Json.JsonConvert.SerializeObject(value, SerializerSettings);
    }

    四、写在结尾处

    很长时间不更新网站,借机偷个懒,记录一下。加油!!!

  • 相关阅读:
    所有选择器
    display:block、display:inline与displayinline:block的概念和区别
    jQuery 选择器
    JS日历制作获取时间
    HTML DOM 事件
    访问HTML元素(节点)
    HTML常用标签
    flask+mysql的Docker 部署
    docker(三)
    flask如何部署
  • 原文地址:https://www.cnblogs.com/shiyige-216/p/14642382.html
Copyright © 2020-2023  润新知