zoukankan      html  css  js  c++  java
  • 照猫画虎owin oauth for qq and sina

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

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

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

    image

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

    image

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

    app.UseQQAuthentication和app.UseSinaAuthentication

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

    image

    image

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

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

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

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

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

    image

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

    稍微需要注意一下的是

    image

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

    image这里的文字

    而Caption对应的是

    image这里的文字(鼠标悬停出现的)

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

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

    image

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

    反而是实现了一个CreateHandler方法

    image

    new了一个GoogleAuthenticationHandler返回

    再看看facebook的

    image

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

    image

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

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

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

    2、创建Handler

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

    没了

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

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

    image

    image

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

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

    再看IGoogleAuthenticationProviderIFacebookAuthenticationProvider,都一样的

    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表中会加入一条数据

    image

    联合主键,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转换

  • 相关阅读:
    安装Redis
    IntelliJ IDEA 2016.1 创建Maven Java Web项目(图解)
    MongoDB入门
    Oracler的锁概念
    Oracle 数据类型
    《锋利的jQuery》随笔(一)
    IList<T>之 First【学习笔记之Linq】
    入驻新园子
    使用nodejs下载风景壁纸
    nodejs爬取博客园的博文
  • 原文地址:https://www.cnblogs.com/czcz1024/p/3498118.html
Copyright © 2011-2022 走看看