پشت صحنه Spring Security – Spring MVC

Spring Security یک چارچوب Java/Java EE است که امکانات احراز هویت، مجوز دهی و دیگر ویژگی های امنیتی را برای برنامه های سازمانی فراهم می کند. این پروژه در اواخر سال ۲۰۰۳ با عنوان Acegi Security و توسط بن الکس آغاز شد، سپس در مارس ۲۰۰۴ تحت مجوز آپاچی در اختیار عموم قرار گرفت. پس از آن، Acegi با نام Spring Security در مجموعه پروژه های اسپرینگ قرار گرفت. اولین نسخه عمومی با نام جدید Spring Security، نسخه ۲٫۰٫۰ بود که در آوریل ۲۰۰۸ با پشتیبانی تجاری و آموزشی موجود از طرف SpringSource ارائه گردید.

وظایف امنیتی مانند احراز هویت کاربر و مجوز کاربر برای دیدن منابع برنامه، معمولا توسط سرور نرم افزار انجام می شود. این وظایف را می توان تحت کنترل Spring Security قرار داد و بار سرور نرم افزار را از این وظایف کم کرد. Spring Security این وظایف را با پیاده سازی استاندارد javax.servlet.Filter کنترل می کند. برای مقدار دهی اولیه Spring Security برنامه، نیاز است فیلتر زیر در web.xml اعلان شود:

 

با این تعریف، فیلتر springSecurityFilterChain صرفا درخواست ها را به سمت چهارچوب Spring Security روانه می کند، جایی که وظایف امنیتی تعریف شده، توسط فیلترهای امنیتی تعریف شده در زمینه نرم افزار[۱] انجام می شود. و اما چگونه این اتفاق می افتد؟

در داخل متد doFilter از DelegatingFilterProxy (پیاده سازی شده javax.servlet.Filter)، متن برنامه اسپرینگ به دنبال یک bean خاص با نام springSecurityFilterChain می گردد. در واقع springSecurityFilterChain یک نام مستعار است که برای زنجیره فیلتر اسپرینگ تعریف می شود.

 

بنابراین، هنگامی که این جستجو در زمینه نرم افزار انجام می شود، filterChainProxy را به عنوان bean نتیجه برمی گرداند. این زنجیره نسبت به فیلتر javax.servlet.FilterChain که توسط فیلترهای جاوا استفاده می شود و در web.xml تعریف شده است، متفاوت می باشد. FilterChain به دنبال فیلتر بعدی می گردد و در صورت وجود، درخواست را به آن می دهد. در صورتی که فیلتر دیگری وجود نداشته باشد، درخواست به یک servlet/JSP تحویل داده می شود. filterChainProxy شامل یک لیست مرتب از فیلترهای امنیتی است که در زمینه نرم افزار اسپرینگ تعریف شده اند. در این جا ممکن است سوالات زیر تداعی گردند:

  1. چه کسی filterChainProxy را مقدار دهی اولیه و تعریف می کند؟
  2. چه فیلترهای امنیتی در زمینه برنامه اسپرینگ تعریف شده اند؟
  3. چگونه بین این فیلترهای امنیتی و فیلترهای عادی تعریف شده در xml اختلاف گذاشته می شود؟

در پاسخ به سوال اول، زمانی filterChainProxy مقداردهی اولیه می شود که عنصر <http> از فضای دامنه امنیت[۲]در متن نرم افزار تعریف شود. در اینجا یک نمونه از ساختار ابتدایی عنصر <http> دیده می شود:

 

با این تعریف، HttpSecurityBeanDefinitionParser موجود در چارچوب اسپرینگ عنصر <http> را برای ثبت filterChainProxy در زمینه نرم افزار می خواند. عنصر http به همراه پارامتر auto-config با مقدار true، در واقع معادل کوتاهی برای تنظیمات زیر است:

 

عناصر زیرین <http> در قسمت های بعدی توضیج داده خواهد شد. پرسش دوم این بود که، چه فیلترهای امنیتی به صورت پیش فرض در زمینه برنامه اسپرینگ تعریف شده اند؟ در پاسخ به این سوال بخشی از سند اسپرینگ را در این جا آورده ایم:

بلاک فضای نام <http>، همیشه یک SecurityContextPersistenceFilter، یک ExceptionTranslationFilter و یک FilterSecurityInterceptor ایجاد می کند. این ها ثابت هستند و قابل جایگزین شدن با گزینه دیگری نمی باشند.

بنابراین زمانی که ما عنصر <http> را اضافه می کنیم، به طور پیش فرض سه فیلتر بالا اضافه خواهند شد و از آن جایی که ما پارامتر auto-config را برابر true تنظیم کرده ایم، BasicAuthenticationFilter، LogoutFilter و UsernamePasswordAuthenticationFilter نیز به زنجیره فیلتر اضافه می شوند. اگر شما به کد منبع هر یک از این فیلترها نگاه کنید، خواهید دید که آن ها در واقع پیاده سازی استاندارد javax.servlet.Filter هستند. اما با تعریف این فیلترها در زمینه نرم افزار، به جای web.xml، سرور برنامه برای انجام وظایف امنیتی، کنترل را به اسپرینگ منتقل می کند و filterChainProxy موجود در اسپرینگ از فیلترهای امنیتی زنجیره ای برای اعمال بر درخواست استفاده خواهد کرد. این جمله پاسخ پرسش سوم نیز می باشد.

برای این که کنترل بهتری بر روی فیلترهای امنیتی ای که باید بر روی درخواست ورودی اعمال شوند، به دست آوریم می توانیم FilterChainProxy خود را این گونه تعریف کنیم:

 

در XML بالا دیده می شود که قرار نیست هیچ گونه فیلتری بر روی تصاویر اعمال شود، در حالی که برای سایر درخواست ها، یک لیست فیلتر مشخص شده است که باید بر روی آن ها اعمال گردد. بنابراین، ما زنجیره های فیلتر را به ترتیب حداقل محدودیت تا حداکثر محدودیت مرتب می کنیم. اما در کل، این نوع ثبت زنجیره های فیلتر مورد نیاز نمی باشد. اسپرینگ، در عنصر <http>، چند قلاب[۳] تهیه می کند که از طریق آن ها بتوان کنترل بهتری بر روی چگونگی اعمال امنیت داشت. در ادامه، جزئیات آن چه که می تواند از طریق عنصر <http> پیکربندی شود، آورده شده است.

  1. احراز هویت: HttpBasicAuthentication و احراز هویت مبتنی بر فرم ورود
  2. پشتیبانی از مجوزدهی از طریق ACL (لیست کنترل دسترسی)
  3. پشتیبانی از خروج از سیستم
  4. پشتیبانی از ورود ناشناس[۴]
  5. احراز هویت Remember-me
  6. مدیریت نشست همزمان[۵]

(۱) احراز هویت: احراز هویت می تواند به دو روش انجام شود: HttpBasicAuthentication و مبتنی بر فرم. در مورد هر یک توضیحات کوتاهی آورده شده است. قبل از این توضیحات، بهتر است درک ابتدایی از AuthenticationManager داشته باشیم که در هسته پیاده سازی احراز هویت Spring Security نشسته است. در داخل عنصر مدیر احراز هویت یا همان AuthenticationManager، تمامی گزینه های احراز هویت موجود برای برنامه تعریف می شود و هر یک از این گزینه ها، درون خود یک پیاده سازی از UserDetailsService را دارند. اسپرینگ اطلاعات کاربر را در UserDetailsService بارگذاری و نام کاربری/رمز عبور را با مقادیری که در ورودی داده شده است، مقایسه می کند. رابط UserDetailsService در زیر آمده است:

 

اسپرینگ دو پیاده سازی مجزا از این سرویس را درون خود قرار داده است:

(الف) جزئیات نام کاربری/رمزعبور را در زمینه نرم افزار ذخیره می کند:

این روش زمانی مناسب است که تعداد کاربران نرم افزار کم باشد. این پیاده سازی می تواند به شکل زیر مقداردهی اولیه شود:

 

تگ ‹authentication-provider› مربوط به کلاس DaoAuthenticationProvider است که در واقع پیاده سازی UserDetailsService را فراخوانی می کند. ما در این مورد، نام کاربری و کلمه عبور را مستقیما در XML قرار داده ایم. هنگامی که پایگاه داده کاربر برنامه بزرگ است، بهتر است برای ذخیره اطلاعات از پایگاه داده استفاده شود. در این مثال شی bean که برای تگ ‹user-service› مقداردهی اولیه می شود نمونه ای از کلاس org.springframework.security.core.userdetails.memory.InMemoryDaoImpl است.

(ب) ذخیره سازی اطلاعات کاربران در پایگاه داده: نحوه مقداردهی اولیه در این جا نشان داده شده است:

 

کلاس مربوطه در اسپرینگ org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl است. با نگاهی به این کلاس، متوجه می شوید که نام کاربری و رمز عبور در جدول users و نقش هایی که می تواند به کاربران اختصاص داده شود در جدول authorities ذخیره می شود. ما در مورد نقش ها صحبت خواهیم کرد. نمونه ای از پرس و جوهایی که این کلاس برای دریافت اطلاعات شناسایی[۶] و اعتباری[۷] کاربران از پایگاه داده فراخوانی می کند را در زیر می بینید:

 

 

حال فرض کنید که شما یک پایگاه داده دارید که در آن جزئیات اطلاعات کاربران در جداول دیگری ذخیره شده باشد. بنابراین ما می توانیم پرس و جوی واکشی را که اسپرینگ انجام می دهد به گونه ای تنظیم کنیم که اطلاعات اعتباری کاربر را از آن جداول بازخوانی کند. فرض کنید جدولی با نام member وجود دارد که شامل شناسه، نام کاربری، رمز عبور است. همچنین جدولی با نام role وجود دارد که فیلد های نام کاربری و نقش، درون آن قرار گرفته است. بنابراین باید این تنظیمات صورت پذیرد:

 

حال به بررسی انجام احراز هویت بپردازیم:

HttpBasicAuthentication: این شی را می توان به شکل زیر پیکربندی کرد:

 

به طور پیش فرض، زمانی که این فعال باشد، مرورگر فرم ورود کاربران را برای ورود، نمایش خواهد داد. ما می توانیم به گونه ای تنظیم کنیم که به جای فرم ورود پیش فرض، یک صفحه ورود خاص نمایش داده شود. این نوع از احراز هویت به شکل رسمی در استاندارد پروتکل انتقال ابرمتن تعریف شده است. اطلاعات ورود (که با کد گذاری base64 کد می شوند) تحت سرآیند Authentication پروتکل http به سرور ارسال می شود. اما این روش جنبه های منفی خود را دارد. بزرگترین مشکل این روش آن است که امکان خروج از سیستم وجود ندارد. اکثر مرورگرهای وب نشست ها را در حافظه نهان[۸] ذخیره می کنند و با این کار، کاربران مختلف نمی تواند با بارگذاری مجدد[۹] مرورگر، دوباره وارد سیستم شوند. تعریف ‹http-basic› در واقع یک فیلتر BasicAuthenticationFilter، در پشت صحنه تعریف می کند. در صورت احراز هویت موفق، شی Authentication درون securityContext اسپرینگ قرار می گیرد. بستر امنیتی[۱۰] می تواند از طریق کلاس SecurityContextHolder در دسترس قرار گیرد. نمونه ای از اعلان BasicAuthenticationFilter در زیر آورده شده است:

 

احراز هویت بر اساس فرم ورود: برای فعال کردن این روش باید این گونه عمل کرد:

 

اسپرینگ برای این تگ چند قلاب[۱۱] یا خصیصه تعریف کرده است. خصیصه default-target-url آدرسی را مشخص می کند که کاربر پس از ورود به آن صفحه هدایت می شود و خصیصه authentication-failure-url آدرس صفحه ای را مشخص می کند که کاربر در صورت عدم احراز هویت باید به آن جا هدایت شود.

 

 

دیگر خصیصه ها عبارتند از: always-use-default-target، authentication-success-handler-ref و authentication-failure-handler-ref.

authentication-success-handler-ref و authentication-failure-handler-ref به ترتیب در صورت احراز هویت موفق و نا موفق فراخوانی می شوند. رابط های AuthenticationSuccessHandler و AuthenticationFailureHandler در این جا آورده شده اند.

 

اسپرینگ برای کنترلر اهراز هویت موفق، پیاده سازی هایی با نام های SimpleUrlAuthenticationSuccessHandler و SavedRequestAwareAuthenticationSuccessHandler دارد. پیاده سازی دومی یک گسترش از پیاده سازی اول است.

هدف از SavedRequestAwareAuthenticationSuccessHandler این است که کاربر را به صفحه ای منتقل کند که از آن جا برای تایید هویت به صفحه ورود هدایت شده بود. در صورتی که عنصر ‹form-login› تعریف شده باشد، این کنترلر، به صورت پیش فرض اجرا خواهد شد. هرچند ما می توانیم این پیاده سازی را با پیاده سازی سفارشی خودمان جایگزین کنیم. فرض کنید ما می خواهیم در زمانی که کاربر تایید هویت شد، به جای این که او را به صفحه ای هدایت کنیم که قبلا قصد رفتن به آن جا را داشت، به او یک صفحه خاص را نشان دهیم. برای این کار باید خصیصه always-use-default-target برابر true گردد.

همچنین ۲ پیاده سازی برای کنترلر عدم موفقیت در احراز هویت وجود دارد: SimpleUrlAuthenticationFailureHandler و ExceptionMappingAuthenticationFailureHandler. در این جا نیز پیاده سازی دوم یک گسترش از پیاده سازی اول است.

در صورت استفاده از SimpleUrlAuthenticationFailureHandler، ما تنها یک URL را مشخص می کنیم تا هنگامی که کاربر موفق به احراز هویت نشود، به آن جا هدایت شود. در حالی که در صورت استفاده از ExceptionMappingAuthenticationFailureHandler، ما چندین URL را مشخص می کنیم و بسته به نوع استثنا در احراز هویت، کاربر به یکی از این آدرس ها هدایت می شود. این استثناها از کلاس org.springframework.security.core.AuthenticationException مشتق می شوند.

هنگامی که ما صفحه ورود سفارشی خود را تعریف می کنیم، فیلدهای نام کاربری و کلمه عبور را به ترتیب با نام های j_username و j_password مشخص کرده و مقدار خصیصه action نیز به طور پیش فرض برابر مقدار j_spring_security_check قرار می دهیم. همچنین ما می توانیم این نام ها و متغیرها را با خصیصه های زیر تغییر دهیم: username-parameter، password-parameter و login-processing-urlدر اینجا یک نمونه از تعریف فیلتر دیده می شود:

 

 

در صورت استفاده از فرم ورود، مشکل خروج که در زمان استفاده از basic authentication وجود داشت، دیگر مطرح نخواهد بود. اما مشکل جدید این است که نام کاربری و رمز عبور به صورت متنی ساده، در سرآیند فرستاده می شوند. این مشکل را می توان با رمزگذاری کلمه عبور با استفاده از تکنیک های رمزنگاری برطرف کرد. اسپرینگ با استفاده از عنصر ‹password-encoder›  در authentication-provider، از این مورد پشتیبانی کرده است. نمونه ای از این تنظیمات در اینجا دیده می شود:

 

(۲) پشتیبانی از مجوزدهی از طریق ACL: اسپرینگ امکان احراز هویت را از طریق ‹intercept-url› در ‹http› فراهم می کند:

 

هر intercept-URL یک الگوی URL و نقش هایی را مشخص می کند که کاربر برای دسترسی به URLهایی که با آن الگوی مطابقت دارند، باید این نقش ها را داشته باشد. توجه داشته باشید که الگوهای آدرس همیشه با یک “*” به پایان می رسند. اگر “*” مشخص نشده باشد، این مشکل پیش خواهد آمد که هکر می تواند تنها با ارسال برخی از پارامترها در URL، مکانیزم امنیتی را دور بزند.

آنچه در پشت صحنه اتفاق می افتد زمانی است که اسپرینگ همه این آدرس ها را به عنوان فراداده به FilterSecurityInterceptor ارسال می کند. در زیر همین پیکربندی، بدون استفاده از <intercept-url> آورده شده است:

 

در کد بالا مشاهده می شود که کاربران گمنام می توانند تنها به صفحه messageList دسترسی پیدا کنند و برای این که بتوانند سایر صفحات را مشاهده کنند، باید به عنوان یک کاربر نرم افزار تایید هویت شوند. در صورت بررسی دقیق تعریف bean در بالا، خصیصه accessDecisionManager دیده می شود. هدف از خصیصه چیست؟ در واقع این bean در مورد کنترل دسترسی تصمیم گیری می کند. این bean باید رابط AccessDecisionManager را پیاده سازی کند. اسپرینگ، درون خود سه مدیر-تصمیم-دسترسی[۱۲] را فراهم کرده است. قبل از درک چگونگی کارکرد مدیر-تصمیم-دسترسی، نیاز است کمی در مورد AccessDecisionVoter بدانیم. AccessDecisionManager در واقع از یک یا چند رای دهنده-تصمیم-دسترسی[۱۳] تشکیل شده است. یک رای دهنده منطق مورد نیاز را برای اجازه/رد/خودداری[۱۴] از کاربر برای مشاهده منابع، درون خود دارد. رای خودداری در مورد یک تصمیم، کم و بیش شبیه به رای ندادن است. نتایج رای گیری توسط فیلدهای مقدار ثابت[۱۵] ACCESS_GRANTED، ACCESS_DENIED و ACCESS_ABSTAIN که در رابط AccessDecisionVoter تعریف شده است نشان داده می شود. ما می توانیم رای دهنده های-تصمیم-دسترسی سفارشی خود را تعریف کرده و آن ها را در تعریف مدیر-تصمیم-دسترسی تزریق کنیم. حال به مدیران-تصمیم-دسترسی که در اسپرینگ تعریف شده اند باز می گردیم:

  1. AffirmativeBased: حداقل یک رای دهنده باید رای مثبت دهد تا مجوز دسترسی داده شود
  2. ConsensusBased: اکثر رای دهندگان باید رای مثبت دهند تا مجوز دسترسی داده شود
  3. UnanimousBased: همه رای دهندگان یا باید رای مثبت و یا رای خودداری دهند تا مجوز دسترسی داده شود (به عبارت دیگر، هیچ رای دهنده ای رای به عدم دسترسی ندهد)

یک مدیر-تصمیم-دسترسی از نوع AffirmativeBased، به طور پیش فرض با ۲ رای دهنده از نوع RoleVoter و AuthenticatedVoter ایجاد می شود. RoleVoter به کاربرانی دسترسی اعطا می کند که حداقل یکی از نقش هایی را که منبع مورد نظر نیاز دارد، داشته باشند. توجه داشته باشید که برای این که این رای دهنده بتواند به این نقش ها دسترسی بدهد، اسامی آن ها باید با پیشوند “ROLE_” شروع شود. در صورت تمایل می توانید این پیشوند را به پیشوند دیگری تغییر دهید. در ادامه چگونگی انجام این کار آمده است. AuthenticatedVoter تنها به کاربرانی دسترسی می دهد که تایید هویت شده باشند. سطوح احراز هویت پذیرفته شده عبارتند از: IS_AUTHENTICATED_FULLY، IS_AUTHENTICATED_REMEMBERED و IS_AUTHENTICATED_ANONYMOUSLY. فرض کنید قرار است یک رای دهنده سفارشی را تعریف و آن را به مدیر-تصمیم-دسترسی اضافه کنیم. روش انجام این کار در این جا نشان داده شده است:

 

(۳) پشتیبانی از خروج از سیستم: اسپرینگ، یک کنترلر برای کنترل خروج از سیستم فراهم کرده است. این کنترلر می تواند به شکل زیر پیکربندی شود:

 

URL خروج از سیستم، به طور پیش فرض به j_spring_security_logout/ نگاشت می شود. ما می توانیم این URL را به وسیله تنظیم ویژگی logout-url سفارشی کنیم. همچنین زمانی که یک کاربر از سیستم خارج می شود، به ریشه مسیر هدایت می شود. در صورتی که کاربر باید به آدرس دیگری هدایت شود، این آدرس از طریق logout-success-urlمشخص می گردد. چگونگی انجام این کار به این صورت است:

 

اگر می خواهید صفحه مقصد در حالات مختلف، به جای یک آدرس خاص و یکسان، متفاوت باشد، باید LogoutSuccessHandler را پیاده سازی کرده و در عنصر ‹logout› یک ارجاع به آن ایجاد کنید.

 

در قطعه کد زیر، فیلتر مورد نیاز بدون استفاده از عنصر ‹logout› تعریف شده است:

 

(۴) پشتیبانی از ورود ناشناس: به طور پیش فرض یک نقش ناشناس توسط اسپرینگ ایجاد می شود. بنابراین هنگامی که شما نقش ROLE_ANONYMOUS و یا IS_AUTHENTICATED_ANONYMOUSLY را برای یک صفحه مشخص می کنید، هر کاربر ناشناس می تواند آن صفحه را مشاهده کند. در مدیر-تصمیم-دسترسی AffirmativedBased، هنگامی که RoleVoter ببیند مقدار ویژگی دسترسی برابر ROLE_ANONYMOUS قرار گرفته است، دسترسی را اعطا می کند. AuthenticatedVoter نیز به طور مشابه دسترسی را در صورتی اعطا می کند که ویژگی دسترسی برابر IS_AUTHENTICATED_ANONYMOUSLY تنظیم شده باشد.

در صورتی که بخواهید یک نام نقش متفاوتی را برای کاربران ناشناس در نظر بگیرید، می توانید تنظیمات پیش فرض را به شکل زیر سربارگذاری کنید:

 

در قطعه کد زیر، فیلتر مورد نیاز بدون استفاده از عنصر ‹anonymous› تعریف شده است:

 

 

(۵) احراز هویت Remember-me: این مورد به وب سایت هایی اشاره دارد که قادر هستند هویت افراد را، در بین نشست ها به یاد داشته باشند. اسپرینگ این کار را در زمان احراز هویت تعاملی موفق، با ارسال یک کوکی به مرورگر انجام می دهد. این کوکی از مقادیر زیر تشکیل شده است:

 

پس از این، زمانی که مرورگر درخواست بعدی را به سرور ارسال می کند، این کوکی را نیز همراه با آن را می فرستد. با دریافت این درخواست، اسپرینگ در پشت صحنه قدم های زیر را برمی دارد:

(الف) به کمک نام کاربری ارسال شده، رمز عبور را از پس زمینه بازیابی می کند.

(ب) رمز عبور بازیابی شده از پایگاه داده را برای محاسبه ()md5Hex مقادیر نام کاربری، رمز عبور، زمان انقضا[۱۶] و کلید استفاده کرده و مقدار به دست آمده را با مقدار موجود در کوکی مقایسه می کند.

(ج) اگر آنها مطابقت داشته باشند در این صورت مشخص می شود که شما احراز هویت شده اید. اگر مطابقت صورت نپذیرد، در این صورت این کوکی جعلی بوده و یا یکی از موارد نام کاربری/رمز/کلید تغییر کرده است.

ما می توانیم تنها با اضافه کردن عنصر remember-me در داخل <http>، این امکان را فعال کنیم. به عنوان مثال:

 

به طور معمول شی UserDetailsService به طور خودکار انتخاب می شود. اگر بیش از یک نمونه از این کلاس در زمینه نرم افزار موجود باشد، باید به وسیله خصیصه user-service-ref، مشخص شود که کدام یک از این نمونه ها باید استفاده شود. مقدار این خصیصه باید برابر نام bean مورد نظر باشد. مشکل امنیتی که باید به آن توجه داشته باشید این است که توکن remember-me می تواند توسط شخص دیگری ذخیره شده و تا زمانی که اعتبار دارد، از آن سوء استفاده شود. با استفاده از توکن های چرخنده[۱۷] می توان از این کار اجتناب کرد. روش پیاده سازی remember-me مبتنی بر توکن در اینجا آمده است:

 

در این جا لازم است به چند نکته توجه شود:

(الف) در صورتی که مقدار createTableOnStartup را برابر true تنظیم کرده باشیم، در زمان تعریف tokenRepository جدولی با نام persistent_logins در پایگاه داده ایجاد می شود. دستور SQL برای ایجاد جدول به این صورت است:

 

(ب) توکن امنیتی توسط خود ما تولید نمی شود. اسپرینگ به طور خودکار این توکن را تولید کرده و آن را در جدول persistent_tokens قرار می دهد و یا آن جدول را به روز می کند. هنگامی که کاربر از طریق یک مرورگر به برنامه دسترسی پیدا می کند و با انتخاب گزینه remember-me وارد نرم افزار می شود، یک سطر جدید در این جدول ایجاد می شود. دفعه بعد که کاربر از همان مرورگر به برنامه متصل شود، به طور خودکار وارد سیستم شده و مقدار توکن در پایگاه داده به روز خواهد شد، اما مقدار سری بدون تغییر باقی می ماند. فرض کنید کاربر از طریق مرورگر دیگری وارد سیستم شده و گزینه remember-me را نیز انتخاب کند، در این صورت یک سطر جدید برای این مرورگر به این جدول اضافه خواهد شد. به روز رسانی های بعدی زمانی برای آن ردیف خاص اتفاق می افتد که کاربر از آن مرورگر، به نرم افزار دسترسی پیدا کند.

بنابراین فایده استفاده از این روش نسبت به رویکرد تک توکنی که بالاتر در مورد آن صحبت شد این است که یک مهاجم تنها تا زمانی قادر است از یک کوکی سرقت رفته استفاده کند که کاربر قربانی، مجددا به نرم افزار دسترسی پیدا کند. هنگامی که قربانی دوباره به سایت دسترسی پیدا کند، از همان کوکی مشابه استفاده خواهد کرد. در این لحظه اسپرینگ یک CookieTheftException ایجاد می کند که می تواند به کاربر اطلاع دهد که یک سرقت رخ داده است.

ما می توانیم به جای استفاده از این عنصر به روشی که تاکنون استفاده کردیم، یک فیلتر سفارشی در زنجیره امنیتی تعریف کنیم. این کار در زیر نشان داده شده است:

 

(۶) مدیریت نشست همزمان: فرض کنید ما نمی خواهیم یک کاربر در یک زمان از بیش از یک محل برای ورود به نرم افزار استفاده کند، در این صورت باید این ویژگی را در اسپرینگ فعال کنیم. برای این کار به این صورت عمل می کنیم:

 

همچنین نیاز است یک شنونده[۱۸] در web.xml تعریف شود که زمانی که کاربر از سیستم خارج شد، یک رویداد از نوع  org.springframework.security.core.session.SessionDestroyedEvent را کنترل کند.

 

چه اتفاقی در پشت صحنه می افتد؟ چگونه اسپرینگ به این پشتیبانی دست پیدا می کند؟ برای این کار اسپرینگ از یک فیلتر امنیتی، مشابه آنچه که تاکنون مشاهده کردیم استفاده می کند. علاوه بر آن، اسپرینگ ApplicationEvents را نیز به کار می گیرد. وقتی اسپرینگ می بیند که کنترل همزمانی مورد نیاز است، لیستی از نشست ها مرتبط با یک کاربر را نگه می دارد. ساختار این لیست مشابه زیر است (این ساختار در org.springframework.security.core.session.SessionRegistryImpl تعریف شده است):

 

کلید این لیست، یک شی کاربر و مقدار آن شناسه نشست مربوط به آن کاربر است. بنابراین هنگامی که اندازه مجموعه، بیش از مقدار تعریف ‹concurrency-control› شده باشد، یک استثنا رخ می دهد. وقتی اسپرینگ متوجه حضور عنصر concurrency-control می شود، SessionRegistryImpl (مکانی که این لیست تعریف شده است) در داخل ConcurrentSessionControlStrategy اضافه شده و به UsernamePasswordAuthenticationFilter تزریق می شود. حال هر زمان احراز هویت کاربری موفق بود، اسپرینگ یک ردیف به لیست تعریف شده در بالا اضافه می کند.

همانطور که در بالا در هنگام تعریف یک شنونده در web.xml دیده شد، زمانی که کاربر از سیستم خارج می شود، یک رویداد SessionDestroyedEvent رخ می دهد. SessionRegistryImpl به این رویداد گوش فرا داده و شناسه نشست را از لیست خارج می کند. بدون این کار، کاربر دیگر هرگز قادر نخواهد بود دوباره وارد شود، حتی اگر از سایر نشست ها نیز خارج شود و یا آن نشست ها منقضی گردند. پیکربندی معادل ‹concurrency-control› در زیر آورده شده است:

 

به عنوان نتیجه، در این قسمت با نحوه پیکربندی ابتدایی و کلاس های مرتبط چارچوب اسپرینگ که برای درک نحوه سفارشی سازی امنیت، با توجه به نیازهای خاص، بسیار مهم هستند آشنا شدیم.

[۱] Application Context

[۲] Security Namespace

[۳] Hook

[۴] Anonymous Login

[۵] Concurrency Session Management

[۶] Credentials

[۷] Authorites

[۸] Cache

[۹] Refresh

[۱۰] Security Context

[۱۱] Hook

[۱۲] Access Decision Manager

[۱۳] Access Decision Voter

[۱۴] Abstain

[۱۵] Constant

[۱۶] Expiration Time

[۱۷] Rolling Tokens

[۱۸] Listener

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *