Asp.NET Core+ABPフレームワーク+IdentityServer 4+MySQL+Ext JSの登録、権限、メニュー、登録


  • 新しい開発グループを試みる:Asp.NET Core+ABPフレームワーク+IdentityServer 4+MySQL+Ext JS
  • Asp.NET Core+ABPフレームワーク+IdentityServer 4+MySQL+Ext JSの構成IdentityServer
  • Asp.NET Core+ABPフレームワーク+IdentityServer 4+MySQL+Ext JSのデータ移行
  • Asp.NET Core+ABPフレームワーク+IdentityServer 4+MySQL+Ext JSの追加エンティティ
  • Asp.NET Core+ABPフレームワーク+IdentityServer 4+MySQL+Ext JSの表示ログインビュー
  • Asp.NET Core+ABPフレームワーク+IdentityServer 4+MySQL+Ext JSの検証コード
  • Asp.NET Core+ABPフレームワーク+IdentityServer 4+MySQL+Ext JSの登録、権限、メニュー、登録
  • ログイン
    ログインに成功すると、サーバはアクセストークン(accessToken)、暗号化アクセストークン(encryptedAccessToken)、トークン有効期限(tokenExpireDate)の3つのデータを返し、クライアント側ではアクセストークンと暗号化アクセストークンをCookieに保存します.具体的なコードは以下の通りです.
        onLoginButton: function() {
            ...
                    success: function(form, action) {
                        var rememberMe = form.getValues().rememberClient || false,
                            obj = Ext.decode(action.response.responseText, true),
                            tokenExpireDate,
                            msg = '    ';
                        if (obj.success) {
                            tokenExpireDate = rememberMe ? (new Date(new Date().getTime() + 1000 * obj.result.expireInSeconds)) : null
                            HEADERS.setCookies(HEADERS.authTokenCookieName, obj.result.accessToken, tokenExpireDate);
                            HEADERS.setCookies(HEADERS.encrptedAuthTokenName, obj.result.encryptedAccessToken, tokenExpireDate, LOCALPATH);
                            window.location.reload();
                        } else {
                            if (result.error && result.error.message)
                                msg = result.error.message + (result.error.details ? result.error.details : '');
                            TOAST.toast(
                                msg,
                                form.owner.el,
                                'bl'
                            );
                        }
                    },
            ...
        },

    戻り結果が成功した場合(successがtrue)、rememberMeがtrueであるか否かを判断し、trueである場合は戻り期限を基準にクッキーの期限を設定します.有効期限が計算されると、setCookiesメソッドを呼び出してクッキーを設定します.
    従来のsetCookiesメソッドは、Ext.util.Cookiessetメソッドを呼び出す際に6つのパラメータをすべて使用していたが、現在は4つのパラメータしか設定されていないため、エラーが発生し、クッキーを設定できないため、setCookiesメソッドを修正し、パラメータを4つに変更する必要がある.
    クッキーを設定すると、reloadメソッドを呼び出してページを再リフレッシュして新しいフローから進みます.このとき,onMainViewRenderメソッドでは,既にクッキーがあるため,ログインページに移動せず,現在のユーザ情報をロードする必要がある.ABPフレームワークでは、初期化データを取得する際に、ユーザーデータを2つの場所に分けて、1つはプロジェクト開始時にSession情報を取得し、その中にユーザー名、電子メール、ユーザー番号などの情報が含まれている.1つは現在のユーザー構成情報を取得し、テナント(MultiTenancy)、Session、ローカライズ(Localization)、機能(Features)、認証(Auth、すべての権限とユーザーが許可した権限を含む)、ナビゲーション(Nav)、設定(Settings)、クロック(Clokc)、時間構成(Timing)、セキュリティ(Security)などの情報は、AppソースコードのAbp.Web.Common\Web\Configuration\AbpUserConfigurationBuilder.csファイルを参照することによって詳細を知ることができます.AbpUserConfiguration/GetAllにアクセスしてユーザ構成情報を取得するつもりだったのですが、意外にもユーザ名などの情報が含まれていないことに気づき、自分で加えればAbpUserConfigurationBuilderクラスを修正するのが面倒なので、angularのロードフローに従ってロードしておきます.angularのプロセスでは、SessionはPromiseオブジェクトによって非同期でロードされていますが、Ext JS 6.2ではPromiseオブジェクトのパッケージが含まれており、このクラスを使用してSessionのロードを実現することもできます.app\utilフォルダの下にSession.jsというファイルを作成し、次のコードを追加します.
    Ext.define('SimpleCMS.util.Session',{
        alternateClassName: 'SESSION',
        singleton: true,
    
        requires:[
            'SimpleCMS.util.Failed'
        ],
    
        init: function(){
            return new Ext.Promise(function (resolve, reject) {
                Ext.Ajax.request({
                    url: URI.get('Session', 'GetCurrentLoginInformations'),   
                    success: function (response) {
                        resolve(response.responseText);
                    },   
                    failure: function (response) {
                        reject(response.status);
                    }
                });
            });
        },
    
        processData: function(content){
            var obj = Ext.decode(content, true);
            if(obj.success){
                Ext.apply(CFG, obj.result);
            }
        }
    });

    コードでは、initメソッド内にExt.Promiseオブジェクトが作成され、Session/GetCurrentLoginInformationsにアクセスしてSession情報を取得し、正常に戻った後、processDataメソッドを呼び出して返されたデータを処理し、ここでは単純に返された結果をCFGオブジェクトにコピーするだけである.app.jsにSimpleCMSが追加する.util.セッションのアクセス後、Application.jsinitメソッドの最後に以下のコードを追加してログイン情報を取得できます.
    SESSION.init().then(SESSION.processData);

    はい、ログイン後のユーザー名とメールアドレスを取得できます.メインビューのビューコントローラのonMainViewRenderメソッドでは、後続のコードを追加してユーザー構成情報を取得できます.具体的なコードは以下の通りです.
        onMainViewRender: function() {
            var me = this,
                token = HEADERS.getAuthToken();
            if (Ext.isEmpty(token)) {
                me.setCurrentView("login");
                return;
            }
            Ext.Msg.wait(I18N.GetUserInfo);
            Ext.Ajax.request({
                url: URI.get('AbpUserConfiguration', 'GetAll', true),
                success: function(response, opts) {
                    var me = this,
                        refs = me.getReferences(),
                        navigationList = refs.navigationTreeList,
                        store = navigationList.getStore(),
                        root = store.getRoot(),
                        viewModel = me.getViewModel(),
                        obj = Ext.decode(response.responseText, true),
                        hash, node, parentNode, roles, reuslt;
                    Ext.Msg.hide();
                    if (obj.success) {
                        result = obj.result;
                        if (!result.session.userId) {
                            me.setCurrentView("login");
                            return;
                        }
                        Ext.apply(CFG, result);
                        viewModel.set('UserName', CFG.user.userName);
                        me.processMenu(root, result.nav.menus.MainMenu.items);
                        me.isLogin = true;
                        hash = window.location.hash.substr(1);
                        me.setCurrentView(Ext.isEmpty(hash) || hash === 'login' ? "articleView" : hash);
                    }
                },
                failure: FAILED.ajax,
                scope: me
            });
        },
    AbpUserConfiguration/GetAllはapiのアクセスアドレスではないため、getメソッドを呼び出すときに3番目のパラメータを追加し、値をtrueにする必要がある.
    ユーザー構成情報を正常に取得した後、sessionにuersIdが存在するかどうかを判断し、存在しない場合は、アクセストークンが期限切れであり、リソースにアクセスできないことを示し、再ログインする必要があります.存在する場合は、既にリソースにアクセス可能であることを示すメッセージをCFGオブジェクトにコピーし、ページにユーザーを表示するためにビューモデルのUserNameを設定します.
    ABPフレームワークのメニュー定義は、必要なメニュー定義のフォーマットとは異なるため、processMenuメソッドを呼び出して変換する必要があります.具体的なコードは以下の通りです.
        processMenu: function(root, menus) {
            var ln = menus.length,
                i = 0,
                result = [],
                routeId = '';
            for (i; i < ln; i++) {
                menu = menus[i];
                routeId = menu.url;
                result.push({
                    text: menu.displayName,
                    iconCls: menu.icon,
                    rowCls: 'nav-tree-badge',
                    viewType: routeId,
                    routeId: routeId,
                    leaf: true
                });
            }
            if (result.length > 0) root.appendChild(result);
        },

    変換プロセスでは、返されるメニュー表示名(displayName)をナビゲーションノードのテキスト値(text)とし、アイコン(icon)をiconClsの値とし、urlをrouteIdとViewTypeの値とします.ここで注意したいのは,処理過程でサブメニューを考慮していない場合,サブメニューがあれば再帰処理を行う必要があることである.メニュー変換が完了すると、ナビゲーションツリーにメニューを追加できます.
    メニュー処理が完了したら、setCurrentViewメソッドを呼び出して初期ビューを設定します.
    これで、ログインプロセスが完了します.
    メニュー
    ABPにナビゲーションメニューを追加するには、NavigationProviderクラスから独自のナビゲーションプロバイダクラスを派生させる必要があります.Web.Coreプロジェクトでは、Navigationというフォルダを追加し、フォルダの下にSimpleCmsWithAbpAppNavigationProviderというクラスを作成し、次のコードを追加します.
        public class SimpleCmsWithAbpAppNavigationProvider : NavigationProvider
        {
            public override void SetNavigation(INavigationProviderContext context)
            {
                context.Manager.MainMenu
                    .AddItem(
                        new MenuItemDefinition(
                            "ArticleManagement",
                            L("ArticleManagement"),
                            url: "articleView",
                            icon: "fa fa-file-text-o",
                            requiresAuthentication:true,
                            requiredPermissionName: PermissionNames.Pages_Articles
                        )
                    )
                    .AddItem(
                        new MenuItemDefinition(
                            "MediaManagement",
                            L("MediaManagement"),
                            url: "mediaView",
                            icon: "fa fa-file-image-o",
                            requiresAuthentication: true,
                            requiredPermissionName: PermissionNames.Pages_Articles
                        )
                    )
                    .AddItem(
                        new MenuItemDefinition(
                            "UserManagement",
                            L("UserManagement"),
                            url: "userView",
                            icon: "fa fa-user",
                            requiresAuthentication: true,
                            requiredPermissionName: PermissionNames.Pages_Users
                        )
                    );
            }
    
            private static ILocalizableString L(string name)
            {
                return new LocalizableString(name, SimpleCmsWithAbpConsts.LocalizationSourceName);
            }
    

    各メニューには、メニュー名(name)、表示名(displayName)、アクセスアドレス(url)、アイコン(icon)、要求検証(requiresAuthentication)、必要な権限(requiredPermissionName)の3つのメニューがコードに追加されています.requiresAuthenticationをtrueに設定すると、ユーザーがアクセス権を持つメニューのみがクライアントに戻り、アクセス権のないメニューはクライアントに戻りません.判断権限の根拠はrequiredPermissionNameの定義です.
    メニュー定義後、SimpleCmsWithAbpWebCoreModule.csファイルを開き、PreInitializeメソッドのConfigureTokenAuthに次のコード構成メニューを追加します.
    Configuration.Navigation.Providers.Add();

    メニューのローカライズ作業では、CoreプロジェクトのLocalization\SourceFilesフォルダの下にあるファイルを直接変更したり、データベースに追加したりすることができます.
    ローカライズ情報のデータベースへの追加を容易にするため、EntityFrameworkCoreプロジェクトのEntityFrameworkCore\Seed\HostDefaultApplicationLanguageTextCreatorというクラスを追加できます.具体的なコードは次のとおりです.
        public class DefaultApplicationLanguageTextCreator
        {
            public static List InitialLanguageTexts => GetInitialLanguageTexts();
    
            private readonly SimpleCmsWithAbpDbContext _context;
            private const string DefaultLanguageName = "zh-CN";
    
            private static List GetInitialLanguageTexts()
            {
                return new List
                {
                    new ApplicationLanguageText()
                    {
                        CreationTime = Clock.Now,
                        Source = SimpleCmsWithAbpConsts.LocalizationSourceName,
                        LanguageName = DefaultLanguageName,
                        Key = "verifyCodeInvalid",
                        Value = "     "
                    },
                    new ApplicationLanguageText()
                    {
                        CreationTime = Clock.Now,
                        Source = SimpleCmsWithAbpConsts.LocalizationSourceName,
                        LanguageName = DefaultLanguageName,
                        Key = "UserManagement",
                        Value = "    "
                    },
                    new ApplicationLanguageText()
                    {
                        CreationTime = Clock.Now,
                        Source = SimpleCmsWithAbpConsts.LocalizationSourceName,
                        LanguageName = DefaultLanguageName,
                        Key = "ArticleManagement",
                        Value = "    "
                    },
                    new ApplicationLanguageText()
                    {
                        CreationTime = Clock.Now,
                        Source = SimpleCmsWithAbpConsts.LocalizationSourceName,
                        LanguageName = DefaultLanguageName,
                        Key = "MediaManagement",
                        Value = "    "
                    },
    
                };
            }
    
            public DefaultApplicationLanguageTextCreator(SimpleCmsWithAbpDbContext context)
            {
                _context = context;
            }
    
            public void Create()
            {
                CreateLanguages();
            }
    
            private void CreateLanguages()
            {
                foreach (var languageText in InitialLanguageTexts)
                {
                    AddLanguageIfNotExists(languageText);
                }
            }
    
            private void AddLanguageIfNotExists(ApplicationLanguageText languageText)
            {
                if (_context.LanguageTexts.IgnoreQueryFilters().Any(m=>m.LanguageName == languageText.LanguageName && m.Key == languageText.Key ))
                {
                    return;
                }
    
                _context.LanguageTexts.Add(languageText);
    
                _context.SaveChanges();
            }
    
        }
    DefaultApplicationLanguageTextCreatorクラスが完了したら、InitialHostDbBuilder.csファイルを開き、Createメソッドに次のコードを追加します.
    new DefaultApplicationLanguageTextCreator(_context).Create();

    これにより、Migrationsプロジェクトを実行してローカライズ情報をデータベースに追加できます.
    全体的に、ローカライズ情報をデータベースに追加するのは面倒ですが、xmlファイルを直接変更するほうが便利です.xml形式の定義が気に入らない場合は、JSON形式の定義も使用できます.SimpleCmsWithAbpLocalizationConfigurerクラスでは、XmlEmbeddedFileLocalizationDictionaryProviderJsonEmbeddedFileLocalizationDictionaryProviderに置き換えればよいが、具体的には、参照文書6.3 ABP表現層−ローカライズを用いることができる.
    ここでは、ローカライズされたリソースが多い場合に、クライアントにローカライズされたリソースを返すのが適切かどうかという問題を考慮する必要があります.筆者は,Ext JSを利用する際には,ユーザ情報をカスタマイズして結果を返すことも考えられ,Sessionというものを統合して,2回の情報取得を避けることができると考えている.
    アクセス権
    メニューを定義するときに、パーミッションPermissionNames.Pages_Articlesが追加され、このパーミッションを定義します.CoreプロジェクトのAuthorizationフォルダの下にあるPermissionNames.csファイルを開き、まず権限名を追加します.コードは次のとおりです.
    public const string Pages_Articles = "Pages.Articles";

    その後、SimpleCmsWithAbpAuthorizationProvider.csファイルを開き、アクセス権を追加します.コードは次のとおりです.
    context.CreatePermission(PermissionNames.Pages_Articles, L("Articles"));

    はい、権限は追加されました.カテゴリ、記事、メディアを処理する権限が大きすぎると判断した場合は、複数の権限を自分で定義できます.
    ログアウト
    トークンによってアクセスが許可されるため、トークンのクリーンアップは終了に相当し、サーバにログアウトを要求する必要はありません.メインビューのビューコントローラ内のonLogoutメソッドを次のコードに変更すればいいです.
        onLogout: function() {
            HEADERS.setCookies(HEADERS.authTokenCookieName, null, null, null);
            HEADERS.setCookies(HEADERS.encrptedAuthTokenName, null, null, LOCALPATH);
            window.location.reload();
        }

    これで、ログインプロセス全体が完了します.