一、寫在微信前
是前段時間還是上幾年來着,我記不太清了,只記得那個時候的我手機流量每個月是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);
}
四、寫在結尾處
很長時間不更新網站,借機偷個懶,記錄一下。加油!!!