بسیار عالی! من دنبال همین بودم (همیشه نباید مشق رو یه جوری طرح کرد که طرف مقابل بتونه کامل حلش کنه! ). البته توی نسخههای قبلی جاوا، روی String نمیشد switch گذاشت، و مجبور بودیم با یه مشت if-else حلش کنیم.
ولی روش دومتون خوبه. من یه کم دقیقترش میکنم:
میتونیم برای هر action، یه آرایه داشته باشیم از نقشهایی که بهش دسترسی دارن، و بعد بررسی کنیم که آیا نقش مورد نظر توی آرایهی مربوط به action مورد نظر موجود هست یا نه (البته به طور کلی برای بررسی کردن موجود بودن یه المان توی یه فهرست، بهتره از HashSet به جای آرایه یا List استفاده کنیم که با سرعت بالا جوابمون رو میده). این Map رو هم به صورت static یه بار میسازیم که هر دفعه بتونیم توش نگاه کنیم. پس میشه اینطوری:
...
private static final Map<String, Set<String>> ACL = new HashMap<String, Set<String>>();
static {
ACL.put("create", new HashSet<String>(Arrays.asList("admin")));
ACL.put("list", new HashSet<String>(Arrays.asList("admin", "operator", "guest")));
ACL.put("edit", new HashSet<String>(Arrays.asList("admin", "operator")));
ACL.put("delete", new HashSet<String>(Arrays.asList("admin")));
}
public void route(String action, String role) throws Expection {
if (!ACL.get(action).contains(role))
throw new AccessDeniedException();
Method m = MyController.class.getDeclaredMethod(action + "Action");
m.invoke(null);
}
این روش به خوبی جواب میده و ما خوشحالیم!
حالا فرض کنید کد شما بزرگ و بزرگتر شد، و از ۴ تا دونه action هی گسترشش دادید، تا این که شد ۱۰۰ تا action! چی کار باید بکنیم؟ باید دونه به دونه هر action ای که اضافه میکنیم، حواسمون باشه که بیایم اینجا هم یه مدخل توی map مون اضافه کنیم. این کار چند تا اشکال داره. یکی این که به خاطر این که این ۲ تا کار از هم مجزا هستن، یعنی action رو یه جا تعریف میکنیم و یه جای دیگه باید بیایم کنترل دسترسیش رو اضافه کنیم، کاملاً ممکنه که یادمون بره سطح دسترسی رو اضافه کنیم. یه اشکال دیگهاش اینه که اینجا یه کدی داریم که هی بزرگتر و بزرگتر میشه! شما اگه مطابق MVC کد بزنید، action های مختلف رو توی controller های مختلف مینویسید، ولی دسترسیها همه توی این یه کلاس تجمیع میشه و کد به شددددددت ناخوانا میشه (تصور کنید از بین صد خط کد دسترسی (که همه شبیه هم هستن!)، بخواین یکی رو ویرایش کنید!).
یه اشکال بزرگ دیگه که این روش داره، اینه که اینطوری نمیتونید یه کتابخونه بسازید که عملیات کنترل دسترسی رو انجام بده. یعنی فرض کنید میخواین این روش route کردن رو به صورت کتابخونه در بیارید، و بشه ازش توی جاهای دیگه هم استفاده کرد. یعنی من نفر دوم بتونم با وابستگی به کتابخونهی شما، یه controller جدید با action های custom خودم رو بنویسم و روش بتونم سطوح دسترسی رو کنترل کنم. برای این کار باید به متغیر ACL که اینجا تعریف کردید دسترسی داشته باشم. اگه این متغیر رو public کنید که من بهش دسترسی داشته باشم (یا یه متد add به صورت public static تعریف کنید که بتونم به کمک اون دسترسیهای مورد نظر خودم رو اضافه کنم)، مشکلی که پیش میاد اینه که میتونم بقیهی سطوح دسترسی رو هم دستکاری کنم. یعنی مثلاً میتونم این Map رو clear کنم و همهی دسترسیها بپره!
برای حل این ۲ مشکل، میتونیم از annotation استفاده کنیم. چه جوری؟ یه Annotation تعریف میکنیم به نام AccessControl، که توش اسم role مورد نظر هست:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface AccessControl {
String[] value() default "guest";
}
اولین خط (Retention) برای این هست که به JVM بگیم اطلاعات مربوط به این Annotation رو موقع اجرای برنامه حفظ کنه، که بشه بازیابیشون کرد (اگه این رو نگیم، پیشفرضش RetentionPolicy.CLASS هست که یعنی موقع اجرا نمیشه اطلاعات مربوط به این Annotation رو بازیابی کرد).
خط دوم یعنی این Annotation رو میشه روی method ها به کار برد، نه کلاسها و پارامترها و غیره.
بعد هم که تعریف خود Annotation هست، که یه المان به صورت آرایهای از String داره (که نقشها توشن).
خوب حالا بیایم ازش استفاده کنیم. روی هر کدوم از action هایی که تعریف کردیم، یه دونه از این Annotation ها میذاریم:
public class MyController {
@AccessControl("admin")
public static void createAction() {
...
}
@AccessControl({"admin", "operator", "guest"})
public static void listAction() {
...
}
@AccessControl({"admin", "operator"})
public static void editAction() {
...
}
@AccessControl("admin")
public static void deleteAction() {
...
}
}
میبینید چقدر قشنگ و تمیز شد؟ هر کسی action تعریف میکنه، همونجا هم کنارش دسترسیهاش رو میگه (و کسی نمیتونه دسترسیهای کس دیگه رو عوض کنه).
دقت کنید که وقتی نوع رو آرایه تعریف میکنید، میتونید مقدار رو یه دونه بدید، یا آرایه بدید.
حالا بیاین توی تابع route مون بخونیمش و ازش استفاده کنیم:
public void route(String action, String role) throws Expection {
Method m = MyController.class.getDeclaredMethod(action + "Action")
AccessControl ac = m.getDeclaredAnnotation(AccessControl.class);
if (ac == null)
throw new ActionNotAnnotatedException();
if (!Arrays.asList(ac.value()).contains(role))
throw new AccessDeniedException();
m.invoke(null);
}
اینجا چند تا نکته هست:
یکی این که وقتی میگیم m.getDeclaredAnnotation و نوع مورد نظر رو بهش میدیم، در صورتی که روی تابع annotation ای از نوع مورد نظر ما تعریف نشده باشه، null برمیگردونه، که در این صورت داریم یه exception از نوع ActionNotAnnotatedException پرتاب میکنیم. پس باز هم ممکنه یه نفر یادش بره روی action اش annotation ما رو بزنه، ولی احتمالش کمتره.
دوم این که مقدار annotation مورد نظر رو که میخونیم، یه آرایه هست از نقشها. بنابراین باید توش بگردیم ببینیم نقش مورد نظرمون توش هست یا نه. البته این کار یه مقدار هزینه داره (چون دونه به دونه توی خونههای آرایه رو میگرده)، ولی برای مثال ما کافیه.
در نهایت هم بگم که برای پیادهسازی Access Control معمولاً از این روش استفاده نمیکنن. اگه بخواین کنترل دسترسی کاملاً پویا داشته باشید، بهترین راه اینه که از پایگاه داده استفاده کنید. ما برای مثال از Annotation این کارو کردیم.
نکتهی دیگه این که معمولاً توی یه پروژه، زیاد با تعریف کردن Annotation سر و کار نداریم و بیشتر ازشون استفاده میکنیم. یعنی Annotation رو برای کاربردهای یه مقدار خاص تعریف میکنن و بعیده توی یه پروژهی معمولی نیاز بشه که شما برای خودتون یه Annotation تعریف کنید. به عنوان مثال، توی کل بخشهای جاوایی موتور جستجوی یوز (که خیلی هم زیاد هستند)، من یک جا رو یادم میاد که دوستان برای یه کار خاص Annotation تعریف کرده بودن (دقت کنید! از Annotation زیاد استفاده میشه! ولی Annotation جدید زیاد تعریف نمیشه).
نکته در مورد Annotation ها زیاده (مثلاً این که اسم المان رو بذاریم value، یا مقدار پیشفرضشون، یا این که توی جاوا ۸ مفهوم Repeated Annotation اضافه شده، ...)، نکات بیشتر رو به منابعی که دوستان معرفی کردن مراجعه کنید.