MVC设计模式授权第三方登录
我本次的分析是以“蚂蜂窝”网站为例来分析MVC设计模式授权第三方登录的。具体分析如下:
(一) 结合六个基本质量属性分析登录模块的MVC设计实现:
- 可用性
可用性是指系统正常运行时间的比例,是通过两次故障之间的时间长度或在系统崩溃情况下能够恢复正常运行的速度来衡量的。业界习惯以多少个9来衡量网站的可用性,目前的大部分网站都可以达到2个9的要求。
“蚂蜂窝”作为一个大型的旅游网站,拥有庞大的用户群,所以它的网站的登录目前没有出现过太大的问题,应该可以达到2个9的要求。
- 可修改性
可修改性是控制实现、测试和部署变更的时间和成本。
MVC将3个模块进行分割,方便修改。
“蚂蜂窝”作为一个旅游网站,它的景点的信息的推送和更换是比较频繁的,包括它的用户可以在上面发布自己的游记和旅游照片,所以它的可修改性是很重要的。在登录界面的时候,它旁边的背景图片会换为当季最热的景点,所以它的可修改性也是可以体现的。
- 性能
性能是指系统的响应能力,即对外部刺激(事件)做出反应时所需要的时间或在某段时间内所处理的事件个数。
“蚂蜂窝”在登录时可以使用第三方账号,比如QQ、微博等,在第三方账号登录的时候的网站响应和普通登录时的响应速度是基本相同的,所以它的性能也是比较好的。
- 安全性
“蚂蜂窝”在登录时可以使用第三方账号,比如QQ、微博等,但是网站是不可能得到你的用户名和密码的。我们可以通过第三方授权登录得到如昵称、性别、注册地址、年龄、头像等基本信息是因为我们可以从这些网站中提供的API接口中得到你账户因为的唯一编码,就是OAuthId。所以第三方登录的安全性还是很可靠的。
- 易用性
易用性是指在指定条件下使用时,软件产品被理解、学习、使用和吸引用户的能力。
使用第三方账号登录可以减少用户的注册和登录的学习成本,方便了用户的使用。
- 可测试性
可测试性是允许在完成软件开发的一个增量后,较轻松地对软件进行测试。
对登录模块我们可以采用比较常见的软件测试的方法进行测试,满足网站的可测试性。
(二)分析MVC设计模式在具体网站中的应用
以QQ为例,分析MVC设计模式在“蚂蜂窝”网站中的应用:
获得openId以及QQ获得用户信息需要三步:
第一步,封装请求链接,然后服务的返回浏览器302跳转至微信或QQ等用户授权窗口
public ActionResult QQLogin(string returnUrl)
{
AuthenticationScope scope=new AuthenticationScope(){
State=Guid.NewGuid().ToString().Replace("-", ""),
Scope="get_user_info"
};
if (!string.IsNullOrEmpty(returnUrl))
{
Session["returnUrl"] = returnUrl;
}
Session["requeststate"] = scope.State;
string url=_tencentHandler.GetAuthorizationUrl(scope);
return Redirect(url);
}
public override string GetAuthorizationUrl(AuthenticationScope scope)
{
string url = string.Empty;
if (string.IsNullOrEmpty(scope.Scope))
{
url = string.Format("{0}/oauth2.0/authorize?response_type=code&client_id={1}&redirect_uri={2}&state={3}", _options.AuthorizeUrl, _options.AppId, string.Concat(_options.Host, _options.Callback), scope.State);
}
else
{
url = string.Format("{0}/oauth2.0/authorize?response_type=code&client_id={1}&redirect_uri={2}&state={3}&scope={4}", _options.AuthorizeUrl, _options.AppId, Uri.EscapeDataString(string.Concat(_options.Host, _options.Callback)), scope.State, scope.Scope);
}
return url;
}
成功后浏览器会跳转至redirect_uri传递的链接窗口即QQOAuthController下面的CallBack
public ActionResult CallBack()
{
var verifier = Request.Params["code"];
string state = Session["requeststate"].ToString();
QQAuthenticationTicket ticket = new QQAuthenticationTicket()
{
Code=verifier,
Tag = "Tencent.QQ"
};
ticket = _tencentHandler.PreAuthorization(ticket);
ticket = _tencentHandler.AuthenticateCore(ticket);
UserClaim userClaim = getUserClaimByOpenIdOrUnionId(ticket.OpenId, "", ticket.Tag);
if (userClaim != null)
{
FormsAuthentication.SetAuthCookie(userClaim.User.UserName, true);
if (Session["returnUrl"] != null && string.IsNullOrEmpty(Session["returnUrl"].ToString()))
{
return Redirect(Session["returnUrl"].ToString());
}
return RedirectToAction("Index", "Home");
}
SocialUser user = _tencentHandler.GetUserInfo(ticket);
Session["social.current"] = user;
return RedirectToAction("social", "members");
}
这方法的主要作用是依据返回的code,与Authorization Server即(QQ或者微博)进行授权认证,获取token,依据token获取openId以及微信的unionId等账号信息
public override QQAuthenticationTicket PreAuthorization(QQAuthenticationTicket ticket)
{
string tokenEndpoint = string.Concat(_options.AuthorizeUrl, "/oauth2.0/token?grant_type=authorization_code&client_id={0}&client_secret={1}&code={2}&redirect_uri={3}");
var url = string.Format(
tokenEndpoint,
Uri.EscapeDataString(_options.AppId),
Uri.EscapeDataString(_options.AppSecret),
Uri.EscapeDataString(ticket.Code), Uri.EscapeDataString(string.Concat(_options.Host, _options.Callback)));
string tokenResponse = _httpClient.GetStringAsync(url).Result.ToString();
if (tokenResponse.IndexOf('&') > 0)
{
var parameters = tokenResponse.Split('&');
foreach (var parameter in parameters)
{
var accessTokens = parameter.Split('=');
if (accessTokens[0] == "access_token")
{
ticket.AccessToken = accessTokens[1];
}
else if (accessTokens[0] == "refresh_token")
{
ticket.RefreshToken = accessTokens[1];
}
}
}
return ticket;
}
public override QQAuthenticationTicket AuthenticateCore(QQAuthenticationTicket ticket)
{
string tokenEndpoint = string.Concat(_options.AuthorizeUrl, "/oauth2.0/me?access_token={0}");
var url = string.Format(
tokenEndpoint,ticket.AccessToken);
string tokenResponse = _httpClient.GetStringAsync(url).Result.ToString();
string strJson = tokenResponse.Replace("callback(", "").Replace(");", "");
var payload = JsonHelper.DeserializeObject<Callback>(strJson);
ticket.OpenId=payload.openid;
return ticket;
}
执行到这里时应该已经获取到openId以及token等相关信息,在自身服务器的用户管理中查找这个账户的绑定记录,如果有相关信息依据取出的相关信息进行用户授权写入cookie或者其他操作,如无调用Authorization Server的相关接口获取相关信息,用QQ的昵称头像,新浪微博貌似无法获取相关信息
protected UserClaim getUserClaimByOpenIdOrUnionId(string openId, string unionId, string tag)
{
UserClaim claim = MembershipService.GetExtendSocialByOpenId(openId, tag);
if (claim != null)
{
return claim;
}
return MembershipService.GetExtentSocialByUnionId(unionId, tag);
}
UserClaim为第三方账户的相关信息类
public class UserClaim
{
/// <summary>
/// 用户Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 用户认证类型
/// </summary>
public string Tag { get; set; }
/// <summary>
/// OpenId
/// </summary>
public string OpenId { get; set; }
/// <summary>
/// UnionId
/// </summary>
public string UnionId { get; set; }
/// <summary>
/// Token
/// </summary>
public string Token { get; set; }
/// <summary>
/// RefreshToken
/// </summary>
public string RefreshKey { get; set; }
public virtual MembershipUser User { get; set; }
}