IdentityServer4(10)- 添加对外部认证的支持之QQ登录

前言

前面我们提到过IdentityServer4是可以添加外部认证的,如果外部认证支持OAuth2,那么添加到IdentityServer4是非常简单的,在ASP.NET Core下提供了非常多的外部认证实现,比如Google,Facebook,Twitter,Microsoft帐户和OpenID Connect等,但是对于我们国内来说最常用的莫过于QQ登录。

申请QQ登录

1.访问QQ互联官方网站:https://connect.qq.com/

2.点击“应用管理”-> “创建应用”,填写你的网站信息,这里的信息请不要胡乱填写,这个会影响审核的,以后要是修改了这些信息需要重新审核。

填写完善资料的时候,唯一一个需要注意的就是回调地址,这里我们后面详细介绍。

3.等待审核结果,这里审核还是非常快的,一般一天左右就行了

注意:如果网站没有备案号我不知道是否能通过申请,我自己是拥有备案号的,然后网站LOGO必须上传,不然会申请不过的。

添加QQ登录

QQ登录是支持OAuth2,所以可以集成到IdentityServer4。本来是打算自己写一个的,但是在查找信息的过程中,发现已经有人实现了,组件名为:Microsoft.AspNetCore.Authentication.QQ,Nuget可以直接安装。

1.先将 Microsoft.AspNetCore.Authentication.QQ 组件添加到项目中

2.配置QQ登录信息

Startup类的ConfigureServices方法里添加如下代码:

services.AddAuthentication()
                .AddQQ(qqOptions =>
                {
                    qqOptions.AppId = "";
                    qqOptions.AppKey = "";
                })

3.在QQ互联后台配置回调地址

回调地址是随时可以在QQ互联后台配置的,因为这个回调地址已经在QQ登录组件里定义了的,所以此处配置为:

http://你的域名/signin-qq

比如:

http://localhost:2692/signin-qq
http://www.baidu.com/signin-qq

4.添加跳转的action

[HttpGet]
public async Task<IActionResult> ExternalLogin(string provider, string returnUrl)
{
    var props = new AuthenticationProperties()
    {
        RedirectUri = Url.Action("ExternalLoginCallback"),
        Items =
        {
            { "returnUrl", returnUrl }
        }
    };

     // start challenge and roundtrip the return URL
    props.Items.Add("scheme", provider);
    return Challenge(props, provider);
}

5.添加回调处理成功跳转的Action

HttpGet]
public async Task<IActionResult> ExternalLoginCallback()
{
    // read external identity from the temporary cookie
    var result = await HttpContext.AuthenticateAsync("QQ");

    if (result?.Succeeded != true)
    {
        throw new Exception("External authentication error");
    }

    // retrieve claims of the external user
    var externalUser = result.Principal;
    var claims = externalUser.Claims.ToList();

    // try to determine the unique id of the external user (issued by the provider)
    // the most common claim type for that are the sub claim and the NameIdentifier
    // depending on the external provider, some other claim type might be used
    var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject);
    if (userIdClaim == null)
    {
        userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
    }
    if (userIdClaim == null)
    {
        throw new Exception("Unknown userid");
    }

    // remove the user id claim from the claims collection and move to the userId property
    // also set the name of the external authentication provider
    claims.Remove(userIdClaim);
    var provider = result.Properties.Items["scheme"];
    var userId = userIdClaim.Value;

    // this is where custom logic would most likely be needed to match your users from the
    // external provider's authentication result, and provision the user as you see fit.
    // 
    // check if the external user is already provisioned
    var user = _users.FindByExternalProvider(provider, userId);
    if (user == null)
    {
        // this sample simply auto-provisions new external user
        // another common approach is to start a registrations workflow first
        user = _users.AutoProvisionUser(provider, userId, claims);
    }

    var additionalClaims = new List<Claim>();

    // if the external system sent a session id claim, copy it over
    // so we can use it for single sign-out
    var sid = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
    if (sid != null)
    {
        additionalClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
    }

    // if the external provider issued an id_token, we'll keep it for signout
    AuthenticationProperties props = null;
    var id_token = result.Properties.GetTokenValue("id_token");
    if (id_token != null)
    {
        props = new AuthenticationProperties();
        props.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } });
    }

    // issue authentication cookie for user
    await _events.RaiseAsync(new UserLoginSuccessEvent(provider, userId, user.SubjectId, user.Username));
    await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, props, additionalClaims.ToArray());

    // delete temporary cookie used during external authentication
    await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);

    // validate return URL and redirect back to authorization endpoint or a local page
    var returnUrl = result.Properties.Items["returnUrl"];
    if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
    {
        return Redirect(returnUrl);
    }

    return Redirect("~/");
}

我画了一张图来表示这个流程:

具体的code请大家查看demo,这里就不帖太多了。

运行测试

1.打开登录页面,点击“QQ”

2.从QQ登录

我们通过第一步,跳转到了QQ的登录页面:

登录之后,QQ也有相应的提醒:

登录之后跳转回我们自己的程序:

这里显示的名称是根据QQ获取用户信息接口返回的QQ昵称

同时,我们也可以在QQ互联里面的授权管理查看我们刚刚授权登录的信息:

其他说明

1.大家下载demo查看之后会发现,我没有从nuget使用Microsoft.AspNetCore.Authentication.QQ这个组件,是因为这个组件在根据QQ返回的用户信息封装Claim时,少了两个字段,过不了IdentityServer4的检测,我修改补上了。

2.如果遇到其他异常可以用抓包软件比如fiddler,抓一下与QQ通信的请求信息,看看是否有异常。

3.Demo运行,只运行QuickstartIdentityServer这一个项目就可以看到效果。

Demo地址:https://github.com/stulzq/IdentityServer4.Samples/tree/master/Quickstarts/4_ImplicitFlowAuthenticationWithExternal

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏古时的风筝

ASP.NET是如何在IIS下工作的

ASP.NET与IIS是紧密联系的,由于IIS6.0与IIS7.0的工作方式的不同,导致ASP.NET的工作原理也发生了相应的变化。 IIS6(IIS7的经典模...

2658
来自专栏技术博客

Asp.Net MVC3.0网站统计登录认证的在线人数

  对于一个网站来说,统计在线人数是一个很重要的工作。平时也发现很多的网站论坛等都有在线人数的显示。对于一个网站如果在线人数很多,用户看到了这么个数字也是很了不...

1132
来自专栏丑胖侠

《Drools7.0.0.Final规则引擎教程》第3章 3.1 Hello World 实例

3.1 Hello World 实例 在上一章中介绍了Drools5x版本中规则引擎使用的实例,很明显在Drools7中KnowledgeBase类已经标注为“...

3096
来自专栏草根专栏

使用Identity Server 4建立Authorization Server (4)

上一篇讲了使用OpenId Connect进行Authentication. 下面讲 Hybrid Flow和Offline Access 目前我们解决方案里面...

5425
来自专栏依乐祝

Asp.Net Core Web Api图片上传(一)

阅读本文章,需要你具备asp.net core的基础知识,至少能够创建一个Asp.Net Core Web Api项目吧!其次,我不会跟你说MongoDB是什么...

4621
来自专栏技术博客

WCF HttpContext.Current为空的问题

原来在项目中使用HttpContext.Current没什么问题,但是到了中期阶段,项目重构等,并且要求使用WCF,所以就出现了HttpContext.Curr...

932
来自专栏跟着阿笨一起玩NET

Asp.net中把DataTable或DataGrid导出为Excel

当前编码的一个项目中有把查询结果(显示在DataGrid)导出为excel的需求,尝试了几种方法,作为技巧拿来和大家分享。 内容: 服务器端实现Data...

1291
来自专栏技术小讲堂

ASP.NET AJAX(9)__Profile Service什么是ASP.NET Profile如何使用ASP.NET ProfileProfile ServiceProfile Service预

什么是ASP.NET Profile 可以为每个用户(包括匿名用户)储存信息 通过在Web.config中的配置即可在应用程序中使用 强类型的属性 可以定义属性...

4379
来自专栏林德熙的博客

VisualStudio 2017 项目格式 自动生成版本号 添加注释防止警告生成的文件自动添加版本

最近我把很多项目都使用了 VisualStudio 2017 新项目格式,在使用的时候发现一些比较好用的功能。 本文告诉大家如何使用 VisualStudio ...

4122
来自专栏GreenLeaves

模块和处理程序之通过HttpModule和HttpHandler拦截入站HTTP请求执行指定托管代码模块

1、简介 大多数情况下,作为一个asp.net web开发对整个web应用程序的控制是十分有限的,我们的控制往往只能做到对应用程序(高层面)的基本控制。但是,很...

23110

扫码关注云+社区