Abp.io(vNext)开发日志:单页面应用与外部/社交登录

Posted 上将军

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Abp.io(vNext)开发日志:单页面应用与外部/社交登录相关的知识,希望对你有一定的参考价值。

@TOC

Abp.io(vNext)开发日志:单页面应用与外部/社交登录

如果不使用外部/社交登录,实现很简单,使用Identity Server 4的令牌端点(Toekn EndPoint)实现密码登录获取到令牌就行了。

如果要使用外部/社交登录,因为没有用户名和密码,需要使用令牌端点(Toekn EndPoint)的授权码(authorization_code)来登录。因而,如何获取授权码是整个流程的关键。

获取授权码的流程

Identity Server 4的端点中,提供了一个名未授权端点(Authorize Endpoint)的端点,而这就是获取授权码的关键。整个登录流程的执行步骤如下:

  1. 将应用程序的地址作为返回地址生成一个访问授权端点的地址
  2. 将第1步生成的地址作为返回地址调整到登录页面
  3. 在用户登录后,会跳转到授权端点
  4. 授权端点会根据返回地址,将授权码等信息与查询字符串的形式返回应用程序首页
  5. 应用程序首页在获取查询字符串中的授权码后,通过令牌端点(Toekn EndPoint)的授权码方式获取访问令牌

在以上5个步骤中,最大的难度在生成授权端点的地址和拆解授权码,因为这涉及到sha256加密等许多安全操作,因而,最简捷的方式是使用封装好的库。如果非要全部自己来实现,如果没时间限制,可以慢慢做,如果有时间限制,建议还是使用库,或者根据现有的库实现适用于自己的简版。

Angular的库

基于Angular的应用程序可以直接使用库angular-oauth2-oidc,这是有完整功能的库,直接使用就行了。其实,abp已经为你封装好全流程了,不需要做任何修改就能用了。

其他框架的库

如果不是使用Angular框架,建议使用[oidc-client-js](https://github.com/IdentityModel/oidc-client-js)库,做点简单的配置就可以实现所需的功能了。

自定义功能

使用oidc-client-js的唯一缺点就是库太大了,整个压缩包是373K。笔者习惯的框架是Ext JS,打包后的应用程序是2M起步的,不想再加任何大包了。于是决定自定义登录功能。

在项目明确需要使用微信登录后,需要将原有的密码登录修改为授权码登录。整个登录流程都需要改变。

首先要修改的登录方法,代码如下:

   login()
        let me = this;
        if(!me.tryLogin())
        
            let url = this.createLoginUrl('', '', null, false, );
            window.location.href = url;    
        
    ,

这里的主要变化是先尝试调用tryLogin方法来获取授权码,如果获取不到,说明这不是登录后跳转回来的地址,需要调用createLoginUrl方法创建登录地址,然后跳转到登录页面。

以下是tryLogin方法的代码:

    tryLogin() 
        const me = this; 
            options = AppConfig.oAuthConfig;
        const querySource = window.location.search;
        const parts = me.getCodePartsFromUrl(querySource);
        const code = parts['code'];
        const state = parts['state'];
        const sessionState = parts['session_state'];
        if (!options.preventClearHashAfterLogin) 
            const href = location.href
                .replace(/[&\\?]code=[^&\\$]*/, '')
                .replace(/[&\\?]scope=[^&\\$]*/, '')
                .replace(/[&\\?]state=[^&\\$]*/, '')
                .replace(/[&\\?]session_state=[^&\\$]*/, '');
            history.replaceState(null, window.name, href);
        
        let [nonceInState, userState] = me.parseState(state);
        me.state = userState;
        if (parts['error']) 
            return false;
        
        if (!nonceInState) 
            return false;
        
        const success = me.validateNonce(nonceInState);
        if (!success) 
            return false;
        
        AppStorage.set('session_state', sessionState);
        if (code) 
            me.getTokenFromCode(code, options);
            return true;
        
        else 
            return false;
        
    ,

代码的主要功能是从查询字符串中获取到codestatesessionState的值,然后拆解状态值,验证nonce值是否正确,其实整个过程就是一次验签过程。在验签通过后,就要调用getTokenFromCode方法来获取访问令牌了。使用授权码获取令牌,以下参数是必须的

  • grant_type:值必须为authorization_code
  • code: 就是在tryLogin方法中获取的code
  • redirect_uri: 就是本地url地址redirectUri,更准确的说是生成授权码跳转地址时使用的redirectUri
  • code_verifier:在生成授权码跳转地址时保存到本地存储的验证码(PKCE_verifier)
  • client_id:客户端标识
  • client_secret:客户端密钥

以下是createLoginUrl的代码:

    createLoginUrl() 
        let me = this,
            redirectUri = me.redirectUri,
            nonce = me.createAndSaveNonce(),
            state = nonce,
            seperationChar = me.loginUrl.indexOf('?') > -1 ? '&' : '?';
        let scope = AppConfig.oAuthConfig.scope;
        if (me.oidc && !scope.match(/(^|\\s)openid($|\\s)/)) 
            scope = 'openid ' + scope;
        
        let url = me.loginUrl +
            seperationChar +
            'response_type=' +
            encodeURIComponent(me.responseType) +
            '&client_id=' +
            encodeURIComponent(me.clientId) +
            '&state=' +
            encodeURIComponent(state) +
            '&redirect_uri=' +
            encodeURIComponent(redirectUri) +
            '&scope=' +
            encodeURIComponent(scope);
        if (me.responseType.includes('code') && !me.disablePKCE) 
            const [challenge, verifier] = me.createChallangeVerifierPairForPKCE();
            AppStorage.set('PKCE_verifier', verifier);
            url += '&code_challenge=' + challenge;
            url += '&code_challenge_method=S256';
        
        if (me.resource) 
            url += '&resource=' + encodeURIComponent(me.resource);
        
        if (me.oidc) 
            url += '&nonce=' + encodeURIComponent(nonce);
        
        return url;
    ,

整个createLoginUrl方法的主要功能就是构建一个访问地址,其中比较重要的步骤是调用createAndSaveNonce方法创建随机数,以及调用createChallangeVerifierPairForPKCE方法创建一个类似于签名的验证码。这些方法都可以从上面提到的两个库中抄,这里就不赘述了。

在调用createChallangeVerifierPairForPKCE方法时,需要对数据进行SHA256加密操作,需要一个加密库,建议使用Angular库的那个,库文件才9k,非常实用。

以上是关于Abp.io(vNext)开发日志:单页面应用与外部/社交登录的主要内容,如果未能解决你的问题,请参考以下文章

Abp.Io(vNext)开发体会

Abp.Io(vNext)开发体会

abp.io(vNext)部署备忘

abp.io(vNext)部署备忘

ABP vNext微服务架构详细教程——分布式权限框架(上)

ABP Vnext 4.4:统一Ef Core的DbContext/移除EF Core Migrations项目