一、写在微信前
是前段时间还是上几年来着,我记不太清了,只记得那个时候的我手机流量每个月是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);
}
四、写在结尾处
很长时间不更新网站,借机偷个懒,记录一下。加油!!!