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.Cookies
のset
メソッドを呼び出す際に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.js
のinit
メソッドの最後に以下のコードを追加してログイン情報を取得できます.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\Host
にDefaultApplicationLanguageTextCreator
というクラスを追加できます.具体的なコードは次のとおりです. 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
クラスでは、XmlEmbeddedFileLocalizationDictionaryProvider
をJsonEmbeddedFileLocalizationDictionaryProvider
に置き換えればよいが、具体的には、参照文書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();
}
これで、ログインプロセス全体が完了します.