اصلاح هویت در امنیت بهار

احراز هویت یکی از اساسی ترین ویژگی ها در برنامه های مدرن است-خواه وارد یک وب سایت شوید ، به API دسترسی پیدا کنید یا حتی یک رمز عبور یک بار تأیید کنید. اگر در حال کار در اکوسیستم بهار هستید ، احتمالاً از Security Security ، چارچوب رفتن به تأیید اعتبار و امنیت شنیده اید.
اما بیایید صادق باشیم … امنیت بهار می تواند احساس غرق شدن کند! 🌀 بین ارائه دهندگان احراز هویت ، جزئیات کاربر و مدیران تأیید اعتبار ، گم شدن آن آسان است. اگر تا به حال خود را تعجب کرده اید که “رمز عبور من از کجا بررسی می شود؟” – شما تنها نیستید!
در این مقاله ، احراز هویت را در امنیت بهار در مراحل ساده و آسان برای دنبال کردن تجزیه می کنم. ما یک جریان احراز هویت اساسی (مانند بررسی نام کاربری و رمز عبور) را با نحوه امنیت بهاری در هر مرحله در زیر کاپوت مقایسه خواهیم کرد. در پایان ، شما درک روشنی از آنچه در پشت صحنه اتفاق می افتد خواهید داشت. 🚀
بیایید شیرجه بزنیم! 🔍
درک احراز هویت اساسی
قبل از اینکه به چگونگی تأیید اعتبار امنیتی بهار بپردازیم ، بیایید در مورد چرا در وهله اول به تأیید اعتبار نیاز داریم. در بیشتر برنامه ها ، شما می خواهید دسترسی به برخی از ویژگی ها یا داده ها را کنترل کنید. این بدان معناست که شما به روشی نیاز دارید تا تأیید کنید که کاربران کسانی هستند که می گویند هستند. این دقیقاً همان کاری است که احراز هویت انجام می دهد!
در یک سیستم رایانه ای ، احراز هویت (“Auth” به طور خلاصه) فرایندی است که تأیید می کند که کاربر کسی است که ادعا می کند. IBM احراز هویت چیست؟ | IBM
اگرچه برنامه های وب می توانند کاملاً متفاوت از یکدیگر باشند ، اما جریان احراز هویت اغلب بسیار شبیه است. یک جریان کلی و عمومی ممکن است اکثریت قریب به اتفاق موارد استفاده را پوشش دهد. در اینجا نمودار یک جریان احراز هویت مشترک وجود دارد:
بیایید قدم به قدم آن را بشکنیم. 🏃♂
بازدید از سرور
همه چیز از زمانی شروع می شود که کاربر از سایت شما بازدید کند یا درخواست اولیه سرور شما را ارائه دهد. در بعضی موارد ، سرور بسته به نیازهای امنیتی شما ممکن است شامل یک نشانه CSRF در پاسخ خود باشد (این مراحل 1 و 1.1 در نمودار است).
به نشانه CSRF به عنوان یک “دست زدن مخفی” کمی فکر کنید که به محافظت در برابر حملات امنیتی خاص کمک می کند.
یک منبع تأیید شده درخواست کنید
در مرحله بعد ، کاربر سعی می کند به منبعی دسترسی پیدا کند که به تأیید اعتبار نیاز داشته باشد-برای مثال ، یک صفحه یا یک نقطه پایانی API که فقط کاربران وارد شده باید آن را ببینند. سرور سپس با یک درخواست اعتبارنامه پاسخ می دهد و از کاربر می خواهد ثابت کند که واقعاً کسانی که ادعا می کنند هستند.
- معمولاً این اعتبارنامه ها عبارتند از:
- نام کاربری و رمز عبور
- ایمیل (برای ارسال لینک جادویی)
- اعتبارنامه Webauthn (کلیدهای امنیتی سخت افزار و غیره)
- یا هر اثبات منحصر به فرد دیگری از هویت.
این همه در مراحل 2 تا 4 در نمودار پوشانده شده است. 🛂
کاربر جستجو کنید
هنگامی که اعتبار کاربر به سرور می رسد ، مرحله بعدی این است که آنها را در پایگاه داده (یا یک سیستم ذخیره سازی دیگر) جستجو کنید.
اگر کاربر وجود نداشته باشد ، سرور بلافاصله خطایی را برمی گرداند (مراحل 5 تا 7.1).
اگر کاربر پیدا شود ، ما به مرحله نهایی حرکت می کنیم: بررسی اعتبار اعتبار آنها.
تصدیق کردن
در این مرحله ، سرور اعتبارنامه ها را تأیید می کند. فرآیند دقیق به روش احراز هویت انتخاب شده شما بستگی دارد:
- نام کاربری و رمز عبور: سرور رمز ورود ورودی را هش می دهد و آن را با رمزعبور هشدار ذخیره شده در پایگاه داده مقایسه می کند.
- Magic Link: سرور بررسی می کند که آیا توکن پیوند معتبر است و منقضی نشده است.
- WebAuthn: سرور چالش امنیتی را با دستگاه یا مرورگر کاربر تأیید می کند.
اگر اعتبارنامه معتبر باشد ، سرور پاسخ موفقیت را به همراه برخی از شناسه ها باز می گرداند – اغلب یک کوکی جلسه یا یک نشانه (مانند JWT) – بنابراین کاربر مجبور نیست در هر درخواست دوباره وارد سیستم شود. این مربوط به مراحل 7.2 و 8 در نمودار است.
این شناسه را به عنوان نشان “من وارد سیستم شده ام” فکر کنید. 🏷
اکنون که مفاهیم اصلی را درک کردید ، خواهیم دید که چگونه امنیت بهار هر یک از این مراحل را در زیر کاپوت مدیریت می کند. دست و پنجه نرم 🚀
نقشه برداری از مراحل تأیید اعتبار به اجزای امنیتی بهار
اکنون که ما یک جریان تأیید هویت معمولی را درک می کنیم ، بیایید ببینیم که امنیت بهار در هر مرحله چگونه است. قبل از غواصی ، بیایید یک چیز را پاک کنیم: اگر قبلاً سعی کرده اید امنیت بهار را یاد بگیرید ، ممکن است در مورد مفاهیم اضافی زیادی مانند CORS ، زنجیره فیلتر امنیتی و موارد دیگر شنیده باشید. اما برای این مقاله ، ما فقط روی تأیید اعتبار تمرکز خود را حفظ می کنیم. من به شما نشان خواهم داد که چگونه امنیت بهار احراز هویت را به سبک API استراحت انجام می دهد. (من شخصاً هرگز از بهار به عنوان یک چارچوب تمام پشته استفاده نکردم ، بنابراین این رویکرد متناسب با تجربه من است-اما نگران نباشید ، اگر به یک راه حل تمام پشته نیاز دارید ، مفاهیم به راحتی ترجمه می شوند!)
در اینجا یک نمایش کلی نقشه برداری از یک جریان احراز هویت استاندارد به اجزای امنیتی بهار وجود دارد:
جریان احراز هویت | مؤلفه امنیت بهار |
---|---|
کاربر اعتبارنامه ارسال می کند |
Authentication (به عنوان مثال ، UsernamePasswordAuthenticationToken ) |
جستجوهای سرور برای کاربر | UserDetailsService |
بررسی کنید که آیا کاربر وجود دارد | UsernameNotFoundException |
اعتبارنامه ها را تأیید کنید |
AuthenticationProvider (به عنوان مثال ، DaoAuthenticationProvider ) |
موفقیت/شکست تأیید هویت |
AuthenticationManager این را ارکستر می کند |
شناسه احراز هویت را برگردانید | به طور معمول توسط یک جلسه یا مکانیسم توکن اداره می شود |
این یک مرور کلی در سطح بالایی است که چگونه احراز هویت در امنیت بهاری کار می کند. من عمداً جزئیاتی را رد کردم که در حالی که هر قسمت را بیشتر تجزیه می کنیم ، حفر می کنیم. بیایید شیرجه بزنیم!
احراز هویت
هر بار که کاربر به یک منبع محافظت شده دسترسی پیدا کند ، باید با استفاده از اعتبارنامه هویت خود را اثبات کند. اینجاست Authentication
رابط کاربری وارد می شود. با توجه به اسناد بهار ، “نشان دهنده نشانه درخواست تأیید اعتبار یا یک مدیر معتبر پس از پردازش درخواست توسط AuthenticationManager.authenticate(Authentication)
روش. “
در Authentication
شیء شامل:
- اعتبارنامه (به عنوان مثال ، رمز عبور)
- مجوزها/مقامات
- اصلی (نمایندگی کاربر معتبر)
- وضعیت احراز هویت (تأیید اعتبار یا نه)
- و سایر جزئیات در مورد درخواست تأیید اعتبار
اجرای متداول که احتمالاً در بسیاری از پروژه ها از آن استفاده خواهید کرد UsernamePasswordAuthenticationToken
بشر نمونه های دیگر عبارتند از:
OneTimeTokenAuthenticationToken
AnonymousAuthenticationToken
JwtAuthenticationToken
BearerTokenAuthenticationToken
موارد دیگری در آنجا وجود دارد ، یا می توانید خود را بچرخانید (اگرچه توصیه می کنم این کار را فقط برای اهداف یادگیری انجام دهید).
userDetailsService
اگر هر زمان را با بهار و الگوی MVC گذرانده اید ، احتمالاً با مفهوم خدمات روبرو شده اید. اینها به عنوان پلی بین منطق کسب و کار و تعامل پایگاه داده شما عمل می کنند. در امنیت بهار ، UserDetailsService
آیا شما برای بازیابی داده های کاربر است.
رابط یک روش واحد را تعریف می کند: loadUserByUsername(String username)
بشر کار این روش این است:
کاربر را بر اساس نام کاربری ارائه شده پیدا کنید.
بازگشت a UserDetails
نمایندگی شیء کاربر که بر داده های تأیید اعتبار و مجوز تمرکز دارد.
به یاد داشته باشید ، نهاد کاربر شما هنوز هم می تواند ویژگی های دیگری داشته باشد و کلاس های مختلف را گسترش دهد. در UserDetails
رابط فقط یک نمای متمرکز برای امنیت فراهم می کند. شما می توانید از نظر فنی با استفاده از UserDetailsService
یا UserDetails
اگر یک ارائه دهنده تأیید اعتبار سفارشی را پیاده سازی می کنید ، اما پس از آن می خواهید با کنوانسیون های چارچوب مبارزه کنید.
بهار حتی برخی از پیاده سازی های پیش فرض را ارائه می دهد:
-
InMemoryUserDetailsManager
(عالی برای نمونه سازی ، زیرا داده ها را فقط در حافظه ذخیره می کند) -
JdbcDaoImpl
(جزئیات کاربر را از یک پایگاه داده با استفاده از نمایش داده های JDBC بازیابی می کند) -
JdbcUserDetailsManager
(نسخه پیشرفته JDBCDAOIMPL با ویژگی های اضافی)
usernamenotfoundexception
وقتی UserDetailsService
نمی توان کاربر را پیدا کرد ، آن را پرتاب می کند UsernameNotFoundException
بشر این استثنا یک زیر کلاس از AuthenticationException
، کلاس پایه برای همه استثنائات مربوط به تلاش برای تأیید اعتبار ناکام. استثنائات دیگر در این خانواده عبارتند از:
BadCredentialsException
AuthenticationCredentialsNotFoundException
CompromisedPasswordException
این استثناء به نشانه امنیتی بهار کمک می کند که در طی فرآیند تأیید اعتبار اشتباه رخ داده است.
AuthenticationManager و تأیید اعتبار
اکنون ما در مرحله ای هستیم که تأیید می کنیم که اعتبار صحیح است. این توسط AuthenticationManager
، که پردازش می کند Authentication
شی (به یاد داشته باشید ، بازنمایی اعتبار کاربر شما ، اعم از تأیید شده یا نه).
متداول ترین اجرای AuthenticationManager
است ProviderManager
بشر این مدیر لیستی از AuthenticationProvider
نمونه ها و تکرار بر روی آنها تا زمانی که کسی بتواند با موفقیت درخواست تأیید اعتبار را انجام دهد. در اینجا یک قطعه ساده برای نشان دادن این وجود دارد:
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
}
توجه کنید که چگونه ProviderManager هر ارائه دهنده را بررسی می کند تا ببیند آیا از نوع احراز هویت که در حال انجام است پشتیبانی می کند یا خیر. این کار متوقف می شود که یکی از مواردی را پیدا کند که درخواست را با موفقیت تأیید کند.
رابط AuthenticationProvider خود فقط دو روش را تعریف می کند:
-
supports(Class> authentication)
: بررسی می کند که آیا ارائه دهنده می تواند نوع احراز هویت داده شده را اداره کند. -
authenticate(Authentication authentication)
: شامل منطق تأیید اعتبار است.
امنیت بهار چندین پیاده سازی را ارائه می دهد ، مانند DaoAuthenticationProvider
برای بررسی نام کاربری/رمز عبور یا JwtAuthenticationProvider
برای احراز هویت مبتنی بر JWT. به عنوان مثال ، یک قطعه از JWTauthenticationProvider ممکن است به این شکل باشد:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication;
Jwt jwt = getJwt(bearer);
AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt);
if (token.getDetails() == null) {
token.setDetails(bearer.getDetails());
}
this.logger.debug("Authenticated token");
return token;
}
@Override
public boolean supports(Class> authentication) {
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
در این مثال ، ارائه دهنده بازی می کند Authentication
هدف به نوع مورد انتظار ، JWT را پردازش می کند و آن را به یک نشانه تأیید شده تبدیل می کند. اگر یک ارائه دهنده تهی کند ، ProviderManager
به سمت بعدی حرکت می کند. این مکانیسم تضمین می کند که برنامه شما راه درستی برای تأیید اعتبار هر درخواست پیدا کند.
بازگشت یک پاسخ
در پایان فرآیند احراز هویت ، برنامه شما باید پاسخ را برگرداند. اگر شما در حال اجرای اجرای خود هستید ، این می تواند به این معنی باشد:
- تنظیم یک کوکی در احراز هویت مبتنی بر کوکی.
- بازگشت یک نشانه JWT برای API های بدون تابعیت.
- یا هر مکانیسم دیگری که انتخاب می کنید.
برای مثال سریع ، ممکن است یک AuthController با یک نقطه پایانی ورود به سیستم ایجاد کنید که سابقه ای مانند این را می پذیرد:
public record LoginRequest(String username, String passsword){}
این کنترلر ایجاد می کند UsernamePasswordAuthenticationToken
، روند احراز هویت را تحریک کنید ، و سپس پاسخ حاوی یک کوکی یا یک نشانه را بر اساس نتیجه برگردانید.
غواصی عمیق تر: کاوش در مکانیسم ورود به سیستم امنیتی بهار
تا کنون ، ما ملزومات را پوشانده ایم ، و شما می توانید جریان تأیید اعتبار خود را با این قطعات بسازید. اما اگر در مورد “راه بهار” انجام کارها کنجکاو هستید ، در اینجا نگاهی دزدکی به نحوه ورود بهار به طور پیش فرض می پردازد.
نکته: اگر این بخش بعدی احساس کمی سنگین می کند یا مفاهیم دیگری را که هنوز راحت نیستید ، معرفی می کند ، احساس راحتی کنید که جلو بروید و بعداً برگردید. درک این مفاهیم – حتی در سطح بالایی – به شما کمک می کند تا درک کنید که چگونه امنیت بهاری در زیر کاپوت کار می کند.
چگونه امنیت بهاری وارد سیستم می شود؟
امنیت بهار یک مکانیسم ورود به سیستم پیش فرض را فراهم می کند ، اما واقعاً چگونه کار می کند؟
هنگامی که کاربر اعتبار خود را ارسال می کند ، درخواست به نقطه انتهایی ورود به سیستم ارسال می شود.
امنیت بهار این درخواست را با استفاده از فیلتر ، به طور خاص رهگیری می کند UsernamePasswordAuthenticationFilter
بشر
فیلتر پس از جریانی که قبلاً در مورد آن صحبت کردیم ، تأیید هویت می کند. اگر احراز هویت موفق شود ، بهار امنیت را ذخیره می کند Authentication
هدف در SecurityContextHolder
بشر قبل از پاسخ دادن به مشتری ، SecurityContext
از طریق a ادامه دارد SecurityContextRepository
بشر این سکانس به امنیت بهاری اجازه می دهد تا احراز هویت را در چندین درخواست حفظ کند.
وای این اطلاعات و مفاهیم جدید زیادی در چنین زمان کوتاهی درست بود؟ بیایید یک بار دیگر ، آنچه را که ما نیاز داریم تعریف کنیم و چگونه این نیازها توسط اجزای مختلف بهار حل می شود.
اولین چیزها اول ،
F*ck فیلتر چیست؟
امنیت بهار به یک زنجیره فیلتر امنیتی ، مجموعه ای از فیلترها که درخواست ها و پاسخ ها را پردازش می کند ، متکی است. این فیلترها وظایف امنیتی مانند تأیید اعتبار ، مجوز و محافظت از CSRF را بر عهده دارند.
معماری :: امنیت بهار
هنگامی که مشتری درخواست HTTP را ارائه می دهد:
- این درخواست از فیلترهای امنیتی متعدد عبور می کند.
- درخواست تأیید و مجاز است.
- در صورت مجاز ، برای پردازش بیشتر به سرویس می رسد.
Boot Spring به طور خودکار این فیلترها را پیکربندی می کند ، اما می توانید آنها را با مثال زیر بازرسی کنید:
@Bean
public UserDetailsService inMemoryUserDetails() {
UserDetails admin = User.builder()
.username("admin")
.password("{noop}admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeHttp -> {
authorizeHttp.requestMatchers("/login").permitAll();
authorizeHttp.anyRequest().authenticated();
})
.formLogin(withDefaults());
return http.build();
}
این پیکربندی:
- یک کاربر مدیر در حافظه ایجاد می کند.
- رفتار ورود به سیستم پیش فرض را فعال می کند.
- دسترسی به /ورود به سیستم را در حین محافظت از سایر نقاط پایانی امکان پذیر می کند.
کاوش در زنجیره فیلتر امنیتی
Security Security با استفاده از پروکسی زنجیره ای فیلتر ، که شامل یک یا چند زنجیره فیلتر امنیتی است ، فیلترها را مدیریت می کند. زنجیره فیلتر امنیتی خاص بهار است و برخلاف زنجیره فیلتر استاندارد Servlet ، با چارچوب بهار عمیق است.
با استفاده از این نقطه پایانی می توانید زنجیره های فیلتر امنیتی فعال را بازرسی کنید:
@GetMapping
private Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
Map<Integer, Map<Integer, String>> filterChains = new HashMap<>();
int i = 1;
for (SecurityFilterChain securityFilterChain : this.filterChainProxy.getFilterChains()){
Map<Integer, String> filterChain = new HashMap<>();
int j = 1;
for (Filter filter : securityFilterChain.getFilters()){
filterChain.put(j, filter.getClass().getName());
j++;
}
filterChains.put(i, filterChain);
}
return filterChains;
}
آیا به یاد دارید که چگونه توضیح دادم که فیلترها کار می کنند؟ خوب ، من دروغ گفته ام ، امنیت بهار در واقع از یک پروکسی زنجیره ای فیلتر برای داشتن فیلترهای تعریف شده در زنجیره فیلتر امنیتی استفاده می کند ، که متفاوت از زنجیره فیلتر است ، در حالی که اولی یک چیز بهاری است و سپس می تواند از سایر ویژگی های بهاری مانند برنامه کاربردی استفاده کند و در واقع همان چیزی است که شما از 99.99 ٪ از زمان استفاده خواهید کرد ، بعداً یک سرویس سرویس دهنده ، شما واقعاً نگران کننده نیستید که زنجیره امنیت را در داخل فیلتر نامیدید. Deper شما می توانید مستندات امنیتی بهار را در بخش Servlet Applicatoins / Architecture بخوانید
این نقطه پایانی که من به شما نشان دادم این است که زنجیره های فیلتر امنیتی را که در پروکسی زنجیره ای فیلتر ثبت شده اند دریافت می کند (یکی که بهار در داخل زنجیره فیلتر Servlet سیم می شود) و نام کلاس فیلترها را در داخل زنجیره فیلتر امنیتی باز می گرداند ، شما پاسخی مانند این دریافت خواهید کرد
{
"1": {
"1": "org.springframework.security.web.session.DisableEncodeUrlFilter",
"2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
"3": "org.springframework.security.web.context.SecurityContextHolderFilter",
"4": "org.springframework.security.web.header.HeaderWriterFilter",
"5": "org.springframework.security.web.csrf.CsrfFilter",
"6": "org.springframework.security.web.authentication.logout.LogoutFilter",
"7": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
"8": "org.springframework.security.web.authentication.ui.DefaultResourcesFilter",
"9": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
"10": "org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter",
"11": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
"12": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
"13": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
"14": "org.springframework.security.web.access.ExceptionTranslationFilter",
"15": "org.springframework.security.web.access.intercept.AuthorizationFilter"
}
}
فیلترهای کلیدی در امنیت بهار
من نمی خواهم هر فیلتر را توضیح دهم بلکه در عوض بیایید با آنهایی که به آنها اهمیت می دهیم برویم:
-
SecurityContextHolderFilter
SecurityContext را برای درخواست ها بازیابی می کند. -
CsrfFilter
تضمین می کند که نشانه های CSRF در درخواست ها گنجانده شده است (در صورت فعال بودن). -
LogoutFilter
با فراخوانی Logouthandlers و هدایت موفقیت ، ورود به سیستم کاربر را مدیریت می کند. -
UsernamePasswordAuthenticationFilter
درخواست های ورود به سیستم ، استخراج اعتبار و انتقال آنها به مکانیسم های تأیید اعتبار. -
DefaultLoginPageGeneratingFilter
صفحه ورود به سیستم پیش فرض را تولید می کند. -
DefaultLogoutPageGeneratingFilter
صفحه ورود به سیستم پیش فرض را ایجاد می کند. -
AnonymousAuthenticationFilter
تأیید اعتبار ناشناس را در صورت تأیید اعتبار کاربر اختصاص می دهد. -
ExceptionTranslationFilter
AccessDeniedException و احراز هویت را به پاسخ های HTTP تبدیل می کند. -
AuthorizationFilter
سیاست های مجوز را اعمال می کند.
پیکربندی زنجیره فیلتر امنیتی
در زیر نمونه ای از پیکربندی فیلترهای امنیتی کلیدی وجود دارد:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.securityContext(securityContextConfigurer ->
securityContextConfigurer.securityContextRepository(new MyCustonSecurityContextRepository())
)
.csrf(csrfConfigurer -> csrfConfigurer.disable())
.logout(logoutConfigurer -> {
logoutConfigurer.logoutUrl("/logout");
logoutConfigurer.invalidateHttpSession(true);
})
.formLogin(loginConfigurer -> {
loginConfigurer.loginPage("/login");
loginConfigurer.failureForwardUrl("/login?error");
loginConfigurer.usernameParameter("username");
}
)
.anonymous(anonymousConfigurer -> {
anonymousConfigurer.principal("anonymous");
})
.exceptionHandling(exceptionHandlingConfigurer -> {
exceptionHandlingConfigurer.accessDeniedPage("/access-denied");
})
.authorizeHttpRequests(authorizeHttp -> {
authorizeHttp.requestMatchers("/login").permitAll();
authorizeHttp.anyRequest().authenticated();
});
return http.build();
}
این پیکربندی:
- یک SecurityContexTrepository سفارشی را تعریف می کند.
- CSRF (در صورت لزوم) را غیرفعال می کند.
- رفتار ورود به سیستم/ورود به سیستم را سفارشی می کند.
- کاربران ناشناس را کنترل می کند و به انکار دسترسی می یابد.
- احراز هویت را برای همه مسیرها به جز /ورود به سیستم اعمال می کند.
این همه خوب و عالی است اما بیایید کمی عمیق تر به این موضوع بپردازیم که چگونه تیم بهاری به طور معمول احراز هویت را در داخل فیلترها پیاده سازی می کند ، برای این کار ما به اجرای برنامه می پردازیم UsernamePasswordAuthenticationFilter
با LogoutFilter
با AnonymousFilter
با SecurityContextHolderFilter
و آخر SecurityContextRepository
SecurityContexTholderfilter
تضمین می کند که SecurityContext به درستی قبل از اقدام به کار با درخواست تنظیم شده است. در اوایل زنجیره فیلتر اجرا می شود و تضمین می کند که هر فیلتر پایین دست می تواند به جزئیات امنیتی دسترسی پیدا کند.
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
try {
this.securityContextHolderStrategy.setDeferredContext(deferredContext);
chain.doFilter(request, response);
}
finally {
this.securityContextHolderStrategy.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
در اجرای می توانید ببینید که آیا فیلتر قبلاً اعمال شده است ، اگر این روش را برای بارگیری زمینه به SecurityContexTholder منتقل کند و با فیلتر بعدی ادامه یابد.
امنیت امنیتی
- SecurityContext را هنگام پردازش درخواست بازیابی و بازیابی می کند.
- با احراز هویت مبتنی بر جلسه کار می کند تا از SecurityContext بین درخواست ها پایدار باشد.
- پیاده سازی های مشترک:
- httpsessionsecurityContextRepository: SecurityContext را در جلسه HTTP ذخیره می کند.
- RequestAtAtTributeSecurityContextRepository: SecurityContext را در ویژگی های درخواست ذخیره می کند.
ناشناس
هنگامی که هیچ کاربر معتبر وجود ندارد ، یک شیء احراز هویت ناشناس را اختصاص می دهد. این تضمین می کند که کاربران غیرمجاز هنوز یک متن امنیتی معتبر دارند. احراز هویت ناشناس نقش خاصی دارد (نقش_ناموس) ، و امکان کنترل دسترسی ریز دانه را فراهم می کند.
بیایید به بخش کوچکی از اجرای فیلتر نگاه کنیم
private SecurityContext defaultWithAnonymous(HttpServletRequest request, SecurityContext currentContext) {
Authentication currentAuthentication = currentContext.getAuthentication();
if (currentAuthentication == null) {
Authentication anonymous = createAuthentication(request);
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.of(() -> "Set SecurityContextHolder to " + anonymous));
}
else {
this.logger.debug("Set SecurityContextHolder to anonymous SecurityContext");
}
SecurityContext anonymousContext = this.securityContextHolderStrategy.createEmptyContext();
anonymousContext.setAuthentication(anonymous);
return anonymousContext;
}
else {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.of(() -> "Did not set SecurityContextHolder since already authenticated "
+ currentAuthentication));
}
}
return currentContext;
}
protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(this.key, this.principal,
this.authorities);
token.setDetails(this.authenticationDetailsSource.buildDetails(request));
return token;
}
این دو روش مواردی است که ما در واقع برای اهداف این مقاله به آنها اهمیت می دهیم ، روش defeaultWithAnymonous احراز هویت فعلی را دریافت می کند و بررسی می کند که آیا این تهی است ، اگر این کار را نمی کند که لازم نیست تنظیم کنید SecurityContextHolder
، اگر اینگونه باشد ، ایجاد می کند AnonymousAuthenticationToken
و سپس یک زمینه خالی و احراز هویت را در آن تنظیم می کند ، در نهایت آن را برمی گرداند SecurityContext
، توکن ناشناس از یک کلید ، یک اصلی به عنوان یک رشته استفاده می کند (که می توانید همانطور که در مثال مشاهده کردید) و جزئیات ، جزئیات درخواست HTTP را ارائه می دهد و آن آدرس IP و شناسه جلسه خواهد بود
usernamepasswordauthenticationfilter
این فیلتر کمی خاص است ، به طور معمول وقتی می خواهید یک فیلتر ایجاد کنید تا به زنجیره ای فیلتر امنیتی بهار اضافه کنید ، کلاس خود را با هر دو گسترش می دهید GenericFilterBean
یا OncePerRequestFilter
اما فیلترهایی که با تأیید هویت مبتنی بر HTTP مبتنی بر Broswer مانند این فیلتر یا Oauth2LoginAuthenticationFilter
با WebAuthnAuthenticationFilter
، و غیره … ، آنها به جای آن ، کلاس دیگری را گسترش می دهند ، AbstractAuthenticationProcessingFilter
این فیلتر به AuthenticationManager
برای پردازش درخواست احراز هویت و الف RequestMatcher
برای بررسی اینکه آیا باید برای درخواست فعلی احراز هویت تلاش کند ، سه روش مهم دارد attemptAuthentication
که هر دو درخواست درخواست و پاسخ را دریافت می کنند و در صورت عبور از تأیید اعتبار ، احراز هویت واقعی را انجام می دهند Authentication
نتیجه در SecurityContext
و آن را پیکربندی می کند AuthenticationSuccessHandler
و در صورت عدم موفقیت ، آن را به پیکربندی واگذار می کند AuthenticationFailurehandler
از اجرای واقعی UsernamePasswordAuthenticationFilter
ما می بینیم که این چقدر ساده است
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
ابتدا بررسی می کند که آیا باید یک درخواست پست باشد و پس از آن نام کاربری و رمز عبور را به دست می آورد ، با جزئیات از درخواست ، تأیید اعتبار ایجاد می کند و در آخر از این عبور می کند Authentication
به AuthenticationManager
حال بیایید ببینیم که چگونه این در AbstractAuthenticationProcessingFilter
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
try {
Authentication authenticationResult = this.attemptAuthentication(request, response);
if (authenticationResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
this.successfulAuthentication(request, response, chain, authenticationResult);
} catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
this.unsuccessfulAuthentication(request, response, failed);
} catch (AuthenticationException ex) {
this.unsuccessfulAuthentication(request, response, ex);
}
}
}
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
if (this.requiresAuthenticationRequestMatcher.matches(request)) {
return true;
} else {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Did not match request to %s", this.requiresAuthenticationRequestMatcher));
}
return false;
}
}
از پیکربندی شده استفاده می کند RequestMatcher
برای بررسی اینکه آیا باید احراز هویت را انجام دهد و سپس با آن تماس می گیرد attemptAuthentication
روش ، در صورت موفقیت آمیز ، برخی از عملیات های آشکار را انجام می دهد (برای اهداف این مقاله واقعاً مهم نیست) و در آخر از آن استفاده می کند succesfulAuthentication
روشی که به نوبه خود از کنترل کننده ای که ما گذشتیم استفاده می کند ، در صورت عدم موفقیت ، از آن استفاده می کند unsuccessfulAuthentication
روشی که از کنترل کننده شکست ما استفاده می کند
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authResult);
this.securityContextHolderStrategy.setContext(context);
this.securityContextRepository.saveContext(context, request, response);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
در اینجا می توانیم ببینیم که این منطق را مانند انتشار رویداد انجام می دهد ، به یاد می آورم خدمات من و غیره … اما بخشی که می خواهم برجسته کنم صرفه جویی در زمینه با استفاده از SecurityContextRepository
این لوبیا از پیکربندی ناشی می شود ، بنابراین بیایید کمی در مورد پیکربندی این فیلترهای ویژه صحبت کنیم.
اجرای پیش فرض این فیلترها به طور معمول با HTTPSecurance Builder بهار پیکربندی SecurityFilterChain پیکربندی شده است ، به طور خاص از کلاس های ویژه ای استفاده می کند که گسترش می یابد AbstractAuthenticationFilterConfigurer
مثل FormLoginConfigurer
و از این دست ، به من اجازه دهید بخش کوچکی از کد را در این کلاس انتزاعی نشان دهم:
@Override
public void configure(B http) throws Exception {
...
SecurityContextConfigurer securityContextConfigurer = http.getConfigurer(SecurityContextConfigurer.class);
if (securityContextConfigurer != null && securityContextConfigurer.isRequireExplicitSave()) {
SecurityContextRepository securityContextRepository = securityContextConfigurer
.getSecurityContextRepository();
this.authFilter.setSecurityContextRepository(securityContextRepository);
}
this.authFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
F filter = postProcess(this.authFilter);
http.addFilter(filter);
}
همانطور که در اینجا مشاهده می کنید ، از پیکربندی دیگری برای دریافت مخزن مورد نیاز برای ذخیره (و بازیابی) جلسات استفاده می کند ، اگر به این ترتیب پیکربندی شود ، آیا به یاد دارید که نمونه پیکربندی زنجیره ای فیلتر را نشان دادم که در آن نمونه ای از پیکربندی برای هر یک از فیلترهای مربوطه ارائه کردم؟ خوب ، در آن مثال .securityContext()
روش است SecurityContextConfigurer
، بیایید ببینیم که به طور پیش فرض از چه مخزنی استفاده می کند
SecurityContextRepository getSecurityContextRepository() {
SecurityContextRepository securityContextRepository = getBuilder()
.getSharedObject(SecurityContextRepository.class);
if (securityContextRepository == null) {
securityContextRepository = new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(), new HttpSessionSecurityContextRepository());
}
return securityContextRepository;
}
همانطور که در اینجا مشاهده می کنید ، یا از آن استفاده می کند که به پیکربندی یا a منتقل می کنید DelegatingSecurityContextRepository
یکی که از RequestAttributeSecurityContextRepository
(این متن فقط در درخواست فعلی را نشان می دهد) و HttpSessionSecurityContextRepository
(این زمینه را در جلسه HTTP نشان می دهد) اگر شما یکی از آنها را پیکربندی نکردید ، اگرچه جزئیات اضافی کمی وجود دارد که آنها دقیقاً ضروری نیستند.
کارنامه
این از یک سری از Logouthandlers استفاده می کند که به ترتیب مشخص شده آنها استفاده می شود ، پس از ورود به سیستم موفقیت آمیز ، با استفاده از هر دو تغییر مسیر را انجام می دهد LogoutSuccessHandler
یا logoutSuccessUrl
پیکربندی شده
اجرای این فیلتر تقریباً شبیه به نسخه قبلی است ، بنابراین به جای نشان دادن آن ، اجرای یک کنترل کننده ورود به سیستم را که بهار ارائه می دهد ، به شما نشان می دهم.
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Assert.notNull(request, "HttpServletRequest required");
if (this.invalidateHttpSession) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Invalidated session %s", session.getId()));
}
}
}
SecurityContext context = this.securityContextHolderStrategy.getContext();
this.securityContextHolderStrategy.clearContext();
if (this.clearAuthentication) {
context.setAuthentication(null);
}
SecurityContext emptyContext = this.securityContextHolderStrategy.createEmptyContext();
this.securityContextRepository.saveContext(emptyContext, request, response);
}
این کد از SecurityContextLogoutHandler
که یکی از موارد پیش فرض استفاده از امنیت بهار است ، ادعا می کند که یک درخواست وجود دارد ، در صورت پیکربندی جلسه ، جلسه را باطل می کند ، پس زمینه را پاک می کند و در صورت تنظیم ، تأیید اعتبار نیز از آن استفاده می کند. SecurityContextRepository
برای به روزرسانی زمینه غیرمجاز.
نتیجه گیری و چه بعدی
تاکنون ما اجزای اصلی درگیر در جریان احراز هویت بهار امنیت را مورد بررسی قرار داده ایم ، از موارد ساده مانند Authentication
رابط به قطعات سازنده بیشتر مانند SecurityContextRepository
، با درک چگونگی کار این قسمت ها می توانیم بینش عمیق تری در مورد چگونگی مدیریت امنیت بهاری در پشت صحنه کسب کنیم و به شما امکان می دهد پیاده سازی های خود را بنویسید یا تصمیم بگیرید که آیا شما باید پیش فرض کنید یا نه.
با نگاهی به آینده ، من قصد دارم مقالات پیگیری را در مورد اجرای احراز هویت سفارشی ، مانند ادغام مکانیسم های تأیید هویت جایگزین فراتر از احراز هویت کلمه کاربری ، بنویسم. من همچنین ممکن است تنظیمات امنیتی مختلف بهاری ، از جمله OAuth2 ، احراز هویت مبتنی بر JWT و استراتژی های کنترل دسترسی ریز دانه را پوشش دهم.
اگر علاقه مند به عمیق تر در این مباحث هستید ، برای پست های آینده با ما در ارتباط باشید و در آنجا نمونه های عملی و موارد استفاده در دنیای واقعی را کشف خواهیم کرد. 🚀
به خاطر داشته باشید که من به هیچ وجه یک متخصص در امنیت یا بهار نیستم ، بنابراین به مستندات زیادی نیاز دارم که به معنای ارسال هر مقاله به زمان خواهد رسید ، لطفاً هرگونه سؤال یا مطالباتی را که ممکن است داشته باشید اظهار نظر کنید ، خوشحال می شوم که افکار خود را بخوانم ، با هیچ چیز دیگری برای گفتن ، خداحافظ و بهترین احترام. 🎉🎉🎉