照猫画虎owin oauth for qq and sina

时间:2020-12-19 19:58:02

ms随vs2013推出了mvc5,mvc5自带的模板项目中引用了新的身份认证框架 ms identity。其中owin部分实现了google,facebook,twitter等国外常见的第三方用户。可惜国内没人用这些。

只能照猫画虎实现以下qq和sina的了

首先看ms自带的google和facebook的代码

照猫画虎owin oauth for qq and sina

每个类库里,有6个类一个接口,其中扩展类放在Owin命名空间下,是为了在startup中方便调用

照猫画虎owin oauth for qq and sina

看看startup中被注释掉的代码就知道了,都是UsexxxAuthentication。同理,希望可以有

app.UseQQAuthentication和app.UseSinaAuthentication

通过查看扩展方法,可以发现,最终调用了app.Use

照猫画虎owin oauth for qq and sina

照猫画虎owin oauth for qq and sina

对应的FacebookAuthenticationMiddleware和GoogleAuthenticationMiddleware才是owin相关的关键部分

XXXMiddleware是一个实现了OwinMiddleware的类(Middleware译为中间件),继承他,然后Use到app里,就可以在Invoke时去处理请求

不过我们不需要继承OwinMiddleware这么底层的类,因为有与身份认证相关的中间件基类了

public abstract class AuthenticationMiddleware<TOptions> : OwinMiddleware where TOptions : AuthenticationOptions

看此类的签名就知道,我们还需要一个Options类,应该去继承AuthenticationOptions类

照猫画虎owin oauth for qq and sina

照猫画虎owin oauth for qq and sina
看看微软给的。在options类里,一般都会有一些属性,去让使用者设定appid和appkey等信息,对oauth验证来说,基本就这两个是最重要的。

稍微需要注意一下的是

照猫画虎owin oauth for qq and sina

base(“Google”)中的google是写死传进去的,这个对应的是

照猫画虎owin oauth for qq and sina这里的文字

而Caption对应的是

照猫画虎owin oauth for qq and sina这里的文字(鼠标悬停出现的)

如果希望按钮的文字也由使用者提供,可以实现带参的构造

来看看最核心的中间件吧。

照猫画虎owin oauth for qq and sina

已google的为例,发现他并没有实现Invoke方法,那就应该是在基类里已经实现了。

反而是实现了一个CreateHandler方法

照猫画虎owin oauth for qq and sina

new了一个GoogleAuthenticationHandler返回

再看看facebook的

照猫画虎owin oauth for qq and sina

也一样。在对象浏览器里没发现这个Handler类啊。好在有各种反编译。

照猫画虎owin oauth for qq and sina

这个是个internal的类(最恨internal和sealed)

看看中间件类里,比较简单

1、构造函数就是给options赋一些默认值

2、创建Handler

3、私有方法,在构造中调用

没了

那核心明显从中间件转到了Handler

再看Handler之前,先看看其他的那几个类

照猫画虎owin oauth for qq and sina

照猫画虎owin oauth for qq and sina

XXXReturnEndpointContext实现ReturnEndpointContext,除了构造没别的

太简单了,咱也整个QQReturnEndpointContext和SinaReturnEndpointContext放着

再看IGoogleAuthenticationProvider和IFacebookAuthenticationProvider,都一样的

Task Authenticated(FacebookAuthenticatedContext context);
Task ReturnEndpoint(FacebookReturnEndpointContext context);

除了参数的类型不一样外,其他的都一样的。我们也写一个放着。

再看接口的实现,也一样的,代码不贴了。写两个放着。

到目前为止,我们有以下几个类

1、扩展方法来

2、Options类

3、中间件类

4、Handler类

5、ReturnEndpointContext类

6、Provider接口

7、Provider实现

还剩下一个XXXAuthenticatedContext类

看google和facebook的实现可以发现,只有属性,没有方法,属性就是记录以下用户id,用户名字acesskey等信息

我们可以先把类建好,具体属性需要什么根据对应的api再加。

OK,我们回来看核心的Handler类。

可以发现,他们都实现了基类的3个方法

1、

protected override Task ApplyResponseChallengeAsync()

2、

public override async Task<bool> InvokeAsync()

3、

protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()

先说第一个,他的作用就是方法第三方api,看看是否授权了

对应的qq地址是:

https://graph.qq.com/oauth2.0/authorize?client_id={0}&response_type=code&redirect_uri={1}

对应的sina地址是:

https://api.weibo.com/oauth2/authorize?client_id={0}&redirect_uri={1}&response_type=code&state={2}
在方法的最后,通过
this.Response.StatusCode = 302;
this.Response.Headers.Set("Location", url);

来设置浏览器跳转

在连接中,我们需要有一个state参数,这个参数由app生成,传给oauth,oauth在传回来,对比验证,state生成后,会自动保存到cookie里,这是基类帮我们做好的。

AuthenticationProperties properties = responseChallenge.Properties;
this.GenerateCorrelationId(properties);
var protector=this.Options.StateDataFormat.Protect(properties);

只需要3行代码,就可以生成一个state验证串,把这个字符串附加在连接后面传给oauth即可

oauth的回调地址,是定义在Options里的CallbackPath

在对应的Options类里可以看到地址为/signin-google和signin-facebook,我们自己的qq和sina自然可以定义成signin-qq和signin-sina

找遍项目,也没有发现signin-google这个controller或者页面什么的,那为什么这个地址可以访问呢,访问他又会干什么呢

来看第二个方法InvokeAsync()

已google为例

public override async Task<bool> InvokeAsync()
{
bool flag;
if (this.Options.CallbackPath.HasValue && this.Options.CallbackPath == this.Request.Path)
flag = await this.InvokeReturnPathAsync();
else
flag = false;
return flag;
}

在这里判断了,如果当前访问的路径是CallbackPath的路径,则去执行一些东西,下面的InvokeReturnPathAsync是一个protected方法

真正的逻辑在这个方法里

public async Task<bool> InvokeReturnPathAsync()
{
AuthenticationTicket model = await this.AuthenticateAsync();
bool flag;
if (model == null)
{
LoggerExtensions.WriteWarning(this._logger, "Invalid return state, unable to redirect.", new string[0]);
this.Response.StatusCode = 500;
flag = true;
}
else
{
GoogleReturnEndpointContext context = new GoogleReturnEndpointContext(this.Context, model);
context.SignInAsAuthenticationType = this.Options.SignInAsAuthenticationType;
context.RedirectUri = model.Properties.RedirectUri;
model.Properties.RedirectUri = (string) null;
await this.Options.Provider.ReturnEndpoint(context);
if (context.SignInAsAuthenticationType != null && context.Identity != null)
{
ClaimsIdentity claimsIdentity = context.Identity;
if (!string.Equals(claimsIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal))
claimsIdentity = new ClaimsIdentity(claimsIdentity.Claims, context.SignInAsAuthenticationType, claimsIdentity.NameClaimType, claimsIdentity.RoleClaimType);
this.Context.Authentication.SignIn(context.Properties, new ClaimsIdentity[1]
{
claimsIdentity
});
}
if (!context.IsRequestCompleted && context.RedirectUri != null)
{
if (context.Identity == null)
context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied");
this.Response.Redirect(context.RedirectUri);
context.RequestCompleted();
}
flag = context.IsRequestCompleted;
}
return flag;
}
(google和facebook的,除了类名不一样,其他的都一样,copy一下就可以了。)
所以,当回调到signin-google这个地址的时候,是可以正常访问的。
 
最后来看AuthenticateCoreAsync
首先是检查回调地址的参数,如果没有state,就返回null了。
然后是
this.ValidateCorrelationId(properties, this._logger)

通过这个方法,来与cookie里保存的state对比,如果不对,也返回null了

剩下的就是回调正确,改进行下一步了。
 
最终的目的是要返回一个AuthenticationTicket
public AuthenticationTicket(ClaimsIdentity identity, AuthenticationProperties properties)

需要2个参数,其中properties我们已经有了(在前面就可以构造出来,具体可看源码)

这里主要是需要构造一个ClaimsIdentity
ClaimsIdentity identity = new ClaimsIdentity(this.Options.AuthenticationType);

理论上new一个就ok,并且,如果仅仅是这么写,他也能过去的,会进入下一步,但是到下一步的时候,有时候会包nullreference异常。

这里应该传入当前用户id和名字

identity.AddClaim(new Claim(http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier, 
“id”, "http://www.w3.org/2001/XMLSchema#string", this.Options.AuthenticationType));
identity.AddClaim(new Claim(http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name,
 “名字”, "http://www.w3.org/2001/XMLSchema#string", this.Options.AuthenticationType));

至少需要id,第一次用第三方登录进来时,他会让你在本地注册

此时AspNetUserLogins表中会加入一条数据

照猫画虎owin oauth for qq and sina

联合主键,UserId对应AspNetUsers表中的主键

LoginProvider是你所使用的第三方登录的标识就是Options调用base(“xxxx”)这里传进去的那个值

而最后一个ProviderKey,则是你这个第三方登录返回的用户id,比如你用sina的登陆,这里记录的应该是你sina的那个id

qq的,就是qq记录你的那个id(qq的叫open id)

具体这几个表是如何存储,是和MS Identity有关的。

这个id和名字这么获得?

当然是调用oauth的api了。

目前,我们只是调用了授权服务,回调的参数里有authorization code,我们需要拿这个authorization code 去请求access token,再拿access token 去请求当前的人的昵称和id等其他信息

简单实现了一下

https://github.com/czcz1024/OwinQQ

包括qq和sina的,因为不想引入其他的类库,所以id,名字还包括access token等都是拿正则截取的,如果你觉得不爽,可以自己做json转换