【ASP.NET Core 认证】远程认证

对于本地的验证方案,我们可以很容易了解验证过程。但是远程的验证方案是特殊的,我们往往会单独来处理它,就像下方的中间件代码,您会发现会优先判断是否为远程验证,然后再执行本地验证。

// 判断当前是否需要进行远程验证,如果是就进行远程验证
var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
    var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
    if (handler != null && await handler.HandleRequestAsync())
    {
        return;
    }
}

//获取本地的验证方案,进行验证
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
    var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
    if (result?.Principal != null)
    {
        context.User = result.Principal;
    }
}

await _next(context);

为什么呢?因为当使用远程验证方案的时候,所有的验证逻辑其实都是在外部,那么本地是如何跟它进行交互进行验证的呢? 难道每一次访问API都要去远程验证服务器进行验证一次?
当然不是啦,接下来我将用一个不严谨的远程验证例子来为大家举例。

案例1、QQ登录

登录页有QQ登录按钮,用户需要点击QQ登录按钮进行远程登录

services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = CookieAuthenticationDefaults.DisplayName;
    })
    .AddCookie()
    .AddQQAuthentication('xxx', options =>
    {
       //省略....
    });

此时有远程验证服务器A,和我本地业务服务器B。B会在A处申请一个密匙,该密匙是用来进行验证Token。当一个请求来到B的时候,它会进入到验证中间件,此时我已经在service中注册了对应的远程验证方案(好比services.AddQQAuthentication())。那么B发现该请求没有携带Cookies,那么B将直接拒绝此次请求。

这个时候客户端会尝试进行在登录页进行登录后再访问,登录页为它展示了一个QQ的登录按钮,毫无疑问,用户会点击该按钮进行使用QQ账号登录。而该按钮指向的地址是远程服务器A的登录地址,而地址中携带了回调的本地地址。比如像这样的URL:"https://QQService.com/sign?callback=http://localhost/sign-qq"。 远程服务器就会处理该请求,等待用户登录成功之后,他会生成一个Token,然后重定向到本地服务器的地址,该地址是刚才传入的回调地址,比如: "http://localhost/sign-qq?token=xxxxx"。

这个时候,就证明您正在访问本地的服务器,而此时注册的远程验证Handler会根据url的参数进行判断,是否需要进行拦截处理,比如QQHandler看到了该url的参数为sign-qq,那么它就会认为它要处理该请求,然后它将获取到的Token进行验证(根据申请到的密匙),验证成功的话就会解析出该Token所携带的Claims,自然而然就会生成一个ClaimsPrincipal出来。最终将该ClaimsPrincipal传递给本地登录方案,生成一个Cookies。这样就完成了本地的身份验证,下次访问的时候,带上该Cookies,就会通过验证啦。

所以再来回顾中间件代码:

//1. 远程验证成功,返回到http://localhost/sign-qq?token=xxxxx

//4. 下次正常访问,携带上了Cookies。
var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
    //2.获取到了QQHandler,该Handler看到URL的参数为sign-qq,那么将对他进行处理。处理过程为解析Token,然后保存到本地Cookies。

    //5. 发现正常访问时候的URL不在拦截范围内,则不做处理。
    var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
    if (handler != null && await handler.HandleRequestAsync())
    {
        //3.处理成功,本次请求结束。
        return;
    }
}

// 6. 找到本地验证方案,比如Cookies,那么对携带的Cookies进行验证。
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
    var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
    if (result?.Principal != null)
    {
        context.User = result.Principal;
    }
}

所以远程登录的本质其实就是携带某些信息,让远程服务器返回一个Token,然后本地根据从远程服务器处申请到的密匙进行Token解析的过程。
远程登录往往会衍生出另外一个概念就是外部登录,比如从QQ出登录后返回了qqUserId = 3的用户,但是该用户是存在QQ系统的,我们的系统是没有的,所以需要处理该用户,常用的手段就是绑定该账号。让QQ的userid与我们系统的UserId关联起来。这也是为什么您会在一些框架中看到一些叫做"xxExternalLoginInfo"的表或者信息的原因。
这种方案您可以在该文章所携带的代码中看到,我们使用了微信小程序的用户与业务用户相关联。

案例2、OAuth

未登录就自动跳转认证中心

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OAuthDefaults.DisplayName;
    })
    .AddCookie()
    .AddOAuth(OAuthDefaults.DisplayName, options =>
    {
        //省略...
    });

注意,DefaultChallengeScheme 改成了OAuth认证方案,内部会执行跳转,跳转到OAuth认证中心进行认证.
Challenge:质询/挑战,意思是当发现没有从当前请求中发现用户标识时怎么办,可能是跳转到登录页,也可能是直接响应401,或者跳转到第三方(如QQ、微信)的登录页 

posted @ 2019-12-19 13:53  .Neterr  阅读(644)  评论(0编辑  收藏  举报