モノリスMVCアプリケーション(つまり、Rails)で、コントローラー以外のエントリポイントがある場合に、承認をモデルレイヤーに拡張するためのベストプラクティスは何ですか?すなわち、バックグラウンドジョブ、またはモデルの相互作用?
通常、メインスレッドをオフロードするためにジョブをキューにエンキューし、次のティックまたは固定間隔時間の間にキューを処理しようとするタスクがあります。このタスクは通常、モデルをCRUDしているときに、誤ったアクセスレベルを回避するために、特定のユーザーに偽装する必要があります。
私が目にするもう1つの関連する問題は、監査に関するものです。ユーザーインスタンスまたはモデルへの参照を渡さないと、current_user
をモデルに渡さずに、モデルイベントに基づいてコールバックを起動してアクションを適切に記録する方法がありません。
そのための1つの解決策は、監査生成をコントローラーに含めることですが、IMO監査ジェネレーターはコントローラーに属していません(通常、メインスレッドではなく非同期に発生する可能性があります)。同じことを繰り返す必要があります。他の場所での「監査生成」、つまり「バックグラウンドジョブ」でも、モデルの相互作用は盲目的に起こります。そのObserverパターンは私が探しているものですか?
監査、ポリシーチェック、または認証を達成するためのRuby gems
には興味がありませんが、モデルと対話する唯一の方法がコントローラーではない場合に認証と承認を操作するための設計パターンに興味があります。
だから、まず理論:
一般的に言えば、認証と承認は、モデルの一部であってはならない横断的な関心事です。
MVCでは、コントローラーは、ユーザーが生成したかどうかに関係なく、すべてのアクションの「エントリーポイント」です。同じアプリケーションの一部であるバックグラウンドプロセスは、コントローラーを呼び出す必要があります。
実際には、1つのアプリケーションに詰め込みすぎているようです。これは今後のWebアプリケーションであると想定します。単一のアプリケーションの代わりに、次のように分割します
認証サービス
これは、認証を実行し、役割を持つトークンを発行します
APIまたはAPI
これにより、BuyProduct、RegisterCustomer、CalibrateModelなどのサービスメソッドが公開されます。 Authサービスからのトークンベースの認証を使用します
ウェブサイト
これは、Authサービスを使用してユーザーがログインできるようにし、その機能を実行するために生成されたトークンをAPIレイヤーに渡します
バックグラウンドジョブ
これらは別々のサーバーで実行され、それぞれに独自のサービスユーザーがいます。アプリケーションはそのユーザーを使用して認証サービス経由でログインし、生成されたトークンをAPIレイヤーに渡してその機能を実行します
これで、これらの各レイヤーには、モデルビューとコントローラーが含まれる可能性があります。一部のモデルは共有される場合がありますが、モデルの外部でユーザーコンテキストを使用してログ記録と認証チェックを適用できる複数の「エントリポイント」があります。
レイヤーとしてプロジェクトを分離し、MVCやバックグラウンドジョブなどの多くのプロジェクトで使用できるService Layerを使用することを強くお勧めします。この承認サービスをサービスレイヤーに追加できます。したがって、これを呼び出すことができますAuthorization Service Layer。
このように、モデルレイヤーに認証を((== --- ==)拡張する必要はありませんため、他のエントリポイントはこのレイヤーを呼び出すことができます。さらに、Modelsは認証について責任を負いません。コントローラ上にいるよりも悪いです。
さらに、Role-Right Managementなどの承認があります。これらの承認は、ビジネスロジックごとに行うことができます。その場合、必要な検証メソッドを呼び出して、ビジネスロジックレイヤーからこの承認を確認する必要もあります。これにより、ユーザーが(特にバックグラウンドジョブから)承認しているかどうかを確認しなくても、承認されていなければこのアクションを実行できません。
public class TaskService
{
private readonly AuthorizationService _authorizationService;
public TaskService(AuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public void AssingTask(int taskId, int userId)
{
// you can perform this validation by attributes that properly generated for your design.
// It is just sample
// by this validate there is no user can do this action without having required right(s).
this.Validate(Rights.AssignTask, userId);
// do your assign business.
}
private void Validate(Rights right, int userId)
{
if (!_authorizationService.IsUserAuthorize(right, userId))
throw new Exception("User has not right for this action.");
}
}
次のステップは、モノリスMVCレイヤーでこの認証方法を使用するためのより良い方法です。繰り返しますが、静的な拡張メソッドを作成し、承認のためにプロジェクト全体で呼び出すことを強くお勧めします。この静的拡張メソッドは、ユーザーオブジェクトから次のように呼び出すことができます。
ビューで:
@if(User.IsAuthorize("Something"))
{
<submit type = "button" text ="you can do this" />
}
およびコントローラー:
public class TaskController : Controller
{
[CustomAuthorize(Right = Rights.AssignTask)]
public ActionResult TaskAssing()
{
_taskService.AssignTask(userId);
}
}
CustomAuthorize
属性:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class CustomAuthorize : // ...
{
public Rights Right { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
if (!AuthorizeExtension.IsAuthorize(filterContext.HttpContext.User.Identity, Right))
{
redirect("UnAutorize");
}
}
}
静的拡張メソッド:
public static class AuthorizeExtension
{
public static bool IsAuthorize(this IPrincipal principal, Rights right)
{
return _authorizationService.IsUserAuthorize(right, principal.Identity.Id);
}
}
最後、何がユーザーを許可するのですか?それは完全にあなたとあなたのデザイン次第です。しかし、私が提案できる2つのオプションがあります。
1。役割-権利管理
ビジネス機能ごとに権限を定義できます。例タスクの作成、タスクの割り当て、タスクの削除、タスクの開始、タスクの停止、ユーザーの作成、ユーザーの更新、レポートの準備、レポートの詳細...必要すべてのビジネス機能に対するすべての権利を定義しました。次に、ロールに複数の権限を付与できます。((n)Role->(n)Right)。次に、ユーザーとロールの関係を作成します。 (n)User->(n)Role関係でもあります。これらすべての関係をデータベースに保存する必要があります。
この設計では、ロールに権限を追加し、ユーザーにロールを割り当てることにより、すべてのビジネス機能の承認を管理できます。 Authorization Service LayerのIsUserAuthorize
メソッドで、ユーザーが自分の役割によって権利を必要としているかどうかを確認できます。
2。許可ユーザーの最小タイプ
許可されたユーザーの最小タイプを決定し、ユーザーが何を実行できるかを決定できます。すべてのものをenum
として定義し、どのユーザーがどのタイプかをコードで判別できます。
public enum Roles
{
AdminRole,
StandartUser,
NewOne,
Guest
}
public enum Services
{
Task,
Report,
User
//...
}
public class AuthorizationService
{
public bool IsUserAuthorize(int userId, Services service)
{
Roles role = DetermineUserRole(userId);
switch(role)
{
case Roles.AdminRole:
return true;
case Roles.Guest:
return service == Services.Report ;
// ...
default:
throw new Exception("Can not determine user role");
}
}
}
どちらを選択する必要がありますか?
最初のオプションは本当に良い習慣であり、すべての機能を管理できます。また、ユーザーが新しいロールを作成して他のユーザーに割り当てることを許可できます。
But、先ほどお伝えしたように、プロジェクトは一枚岩で、おそらく大きな変更や新しい機能の追加はありません。したがって、2番目のものを選択する方が良いようです。したがって、すべてのビジネス機能の権利を定義し、それらの権利と関係をデータベースに保存する必要はありません。
モノリスMVCアプリケーション(Railsなど)の場合、コントローラー以外のエントリポイントがありますか?すなわち、バックグラウンドジョブ、またはモデルの相互作用?
workers(jobs executors)をシステムの actors と見なします。他の俳優と同じようにセキュリティサービスに関与できるように、セキュリティサービスでアイデンティティを提供します。必要に応じて、特別な権限を付与してください。あなたは言います:-このタスクは通常、誤ったアクセスレベルを避けるために、特定のユーザーに偽装する必要があります。いいですね、あなたは途中です。
アプリケーションの入力ポートをワーカーがアクセスできるようにします。入力ポートは、外層がシステムにアクセスするためのゲートです。これらのポートはすでにあります。コントローラ。それらは目的に完全に適合しますが、おそらくWeb入力の処理に非常に特化しているため、 adaptations を実行する必要があります。たぶん、運がよければ、いくつかの基本的な proxies をいくつかのコントローラーで使用できます。おそらく、もう少しアクセシブルにする必要があります。それから gateway も仕事をするはずです。どちらもセキュリティに取り組むための優れた抽象概念です。
public class BatchWorker {
//....
public void execute(){
securityService.doLogin(credentials);
job.run();
}
}
public class Job {
//....
public void run(){
salesController.aSalesMethod(input);
}
}
public interface SalesController {}
public final class SalesControllerProxy implements SalesController {
private final SalesController salesController;
private final SecurityService;
//...
public void aSalesMethod(InputData data){
if(securityService.userInRoles("role_a")){
salesController.aSalesMethod(data);
};
}
}
監査に関しては、あなたは正しいです。監査、ロギング、セキュリティは、通常、ソースコードを膨らませるような横断的な関心事であり、ビジネスの読み取りを少し難しくしています。カップリングは言うまでもありません。 アスペクト指向プログラミング またはプロキシ|ゲートウェイを使用して、これらの問題を(ある程度まで)軽減することができます。
public final class SalesControllerProxy implements SalesController {
// the previous code ...
private final Auditor auditor;
private final Logger logger;
public void aSalesMethod(InputData data){
...
doLog(data);
...
doAudit(data);
}
// rest of the code ...
public void doLog(InputData data){
if(logger ! = null){
logger.debug("Useful messages for developers go here");
}
}
public void doAudit(InputData data){
Principal principal = securityContext.getPrincipal();
if(auditor ! = null){
auditor.trace("Useful messages for monitoring and traceability go here",principal);
}
}
}
プロキシパターンはどのレイヤーにも適合します。モデルでも(ドメイン)。
Product
と2つの実装のインターフェイスを宣言するとします。1つはビジネスルール用で、もう1つは監査用のプロキシです。それぞれのインスタンス化は factories または builders に委任されます。好みに応じて。ビジネスルールがインターフェイスProduct
を使用するとすぐに、プロキシとエンティティのすべての機能が区別できなくなり、同時に分離されます。とは言っても、私たちがそのように下がる場合は、反射を避けることをお勧めします。魅力的ですが、OOPの原則のいくつかに違反しており、コードが扱いにくくなっています。
このアプローチで私は提案します: