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

摘要:
paramname=“code”>Domain.ValueObject.RestfulData<[System.Web.Http.FromBody]模型.RequestAuthViewModelrequestAuth){varmodel=newDomain.ValueObject.RestfulData<

一、写在微信前

是前段时间还是上几年来着,我记不太清了,只记得那个时候的我手机流量每个月是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);
}

四、写在结尾处

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

免责声明:文章转载自《从微信授权到JWT认证——玩转token之路》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇win10+jdk+mysql+tomcat+jpress环境搭建与部署Android WebView 的 addJavascriptInterface 探究下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

win32-localtime的使用

下面的例子用于反映本地系统的日期格式变化 // locale test #include <stdio.h> #include <locale.h> #include <time.h> #include <locale> #include <Windows.h> #pragma warning(...

Java邮件开发电子邮件的基本概念介绍

电子邮件用于网上的信心传递和交流,它是最重要的Internet服务之一。据统计Internet有30%的业务是电子邮件有关的。同时我们也不可否认它在我们的日常生活、工作办公方面扮演着很重要的角色。譬如:许多办公自动化项目(OA)中都要附带发送邮件的功能,如果还要使用OutLook等手工方式就不适合,在这个高速的时代,我们需要提供工作效率,让工作能够自动化。...

Linux 内核 hlist 详解

在Linux内核中,hlist(哈希链表)使用非常广泛。本文将对其数据结构和核心函数进行分析。 和hlist相关的数据结构有两个:hlist_head 和 hlist_node //hash桶的头结点struct hlist_head {   struct hlist_node *first;//指向每一个hash桶的第一个结点的指针};//hash桶的...

spring-入门

1.spring是什么?   spring是一个轻量型的框架,主要体现在管理每一个Bean组件的生命周期,不同Bean组件之间依赖关系上面。   它主要是通过顶层容器BeanFactory来管理每一个Bean组件的生命周期,通过子类ApplicationContext实现工厂模式下每一个Bean组件的创建。 2.两大核心:   IOC/DI:控制反转/依赖...

CSS中关于多个class样式设置的不同写法

html中:  <div class="containerA"> 这是AAAAAAAAAAAAAAAAAAAAAAA样式 <div class="containerB"> 这是BBBBBBBBBBBBBBBBBBBBBB样式 </div> </div>  css中: .containerA .containe...

C# 获取config文件 实体转换

随着项目的扩展,单独的key,value配置文件已经不能满足需求了 这里需要自定义配置节点,例如 1 <!--自定义 具体实体类配置问节点信息--> 2 <School Name="红旗小学" Number="1008" Address="北京市,西城区……"></School> 当然,这样的节点可以有多重获取...