مقدمة
تشمل أطر التصريح العام الشهيرة الآن شيرو من أباتشي وأمن عائلة سبرينج عائلة سبرينج. عندما يتعلق الأمر بمصادقة الخدمات المجهرية اليوم ، نحتاج إلى استخدام إطار التفويض الخاص بنا لبناء خدمات المصادقة الخاصة بنا. اليوم ، كان رئيس الوزراء رئيس الوزراء.
يمنع Spring Security المصادقة بشكل أساسي (المصادقة ، الحل من أنت؟) والتحكم في الوصول (التحكم في الوصول ، أي ما المسموح لك به؟ ، المعروف أيضًا باسم التفويض). Spring Security يفصل المصادقة عن بنية الترخيص ويوفر نقاط تمديد.
الأشياء الأساسية
الكود الرئيسي هو ضمن حزمة نورس الربيع. لفهم أمان الربيع ، تحتاج إلى الانتباه إلى الأشياء الأساسية في الداخل.
SecurityContextholder ، SecurityContext والمصادقة
SecurityContextholder هي حاوية تخزين لـ SecurityContext. يتم استخدام تخزين ThreadLocal افتراضيًا ، مما يعني أن طرق SecurityContext متوفرة في نفس الموضوع.
يقوم SecurityContext بشكل رئيسي بتخزين المعلومات الرئيسية للتطبيق ويتم تمثيله بالمصادقة في Spring Security.
احصل على المدير:
الكائن الرئيسي = securityContexTholder.getContext (). getAuthentication ().
في أمن الربيع ، يمكنك إلقاء نظرة على تعريف المصادقة:
مصادقة الواجهة العامة تمتد الرئيسية ، القابلة للتسلسل {collection <؟ يمتد GrantedAuthority> getAuthorities () ؛ / *** عادةً كلمة المرور*/ الكائن getCredentials () ؛ /*** يخزن تفاصيل إضافية حول طلب المصادقة. قد يكون هذا عنوان IP * ، الرقم التسلسلي للشهادة ، إلخ. */ الكائن getDetails () ؛ /*** تستخدم لتحديد ما إذا كانت مصادقة. إذا قمت بتسجيل الدخول باستخدام اسم مستخدم وكلمة مرور ، فعادة ما يكون اسم المستخدم*/ Object GetPrincipal () ؛ / *** ما إذا كانت مصادقة*/ boolean isauthenticated () ؛ باطلة setauthenticated (المنطقية isauthenticated) يلقي غير unalfalargumentException ؛}في التطبيقات العملية ، عادة ما يتم استخدام UsernamePasswordauthenticationToken:
مجردة عامة التجريدية upplyticenticationtoken مصادقة ، credentialscontainer {} الفئة العامة usernamePasswordauthenticationToken تمتد AbstractAuthenticationToken {}عادةً ما تكون عملية المصادقة الشائعة مثل هذا: إنشاء usernamepasswordauthenticationToken ثم تسليمها إلى anitfingmanager للمصادقة (الموصوفة بالتفصيل لاحقًا). إذا تم تمرير المصادقة ، فسيتم تخزين معلومات المصادقة من خلال جهاز SecurityContextholder.
usernamepasswordauthenticationToken antainicationToken = جديد usernamePasswordauthenticationToken (loginvm.getUserName () ، loginvm.getPassword ()) ؛ مصادقة المصادقة = this.AuthenticationManager.Authenticate (AuthenticationToken) ؛
userDetails و userDetailsService
userDetails هي واجهة رئيسية في Spring Security ، والتي تستخدم لتمثيل مدير.
الواجهة العامة userDetails تمدد Serializable { / *** يمكن فهم معلومات ترخيص المستخدم على أنها دور* / collection <؟ يمتد GrantedAuthority> getAuthorities () ؛ / ** * كلمة مرور المستخدم * * @إعادة كلمة المرور */ string getPassword () ؛ / *** اسم المستخدم**/ string getUserName () ؛ Boolean isAccUncountNonexpired () ؛ Boolean isAccountnonlocked () ؛ boolean iscredentialsnonexpired () ؛ منطقية isenabled () ؛}يوفر userDetails المعلومات اللازمة للمصادقة. في الاستخدام الفعلي ، يمكنك تطبيق userDetails بنفسك وإضافة معلومات إضافية ، مثل البريد الإلكتروني والجوال وغيرها من المعلومات.
في المصادقة ، عادة ما يكون المدير هو اسم المستخدم. يمكننا الحصول على userDetails من خلال المدير من خلال userDetailsService:
الواجهة العامة userDetailsService {userDetails loadUserByUserName (اسم مستخدم السلسلة) يلقي usernamenotfoundException ؛}Grantedauthority
كما هو مذكور في userDetails ، يمكن فهم GrantEdauthority كدور ، مثل ROLE_ADMINISTRATAR أو ROLE_HR_SUPRESISOR.
ملخص
شهادة المصادقة
المصادقة
يتم تحقيق المصادقة بشكل أساسي من خلال واجهة المصادقة ، والتي تحتوي فقط على طريقة واحدة:
واجهة عامة مصادقة {مصادقة المصادقة (مصادقة المصادقة) يلقي المصادقة ؛}تقوم طريقة المصادقة () بشكل أساسي بثلاثة أشياء:
AuthenticationException هو استثناء وقت التشغيل ، والذي عادة ما يتم التعامل معه من قبل التطبيق بطريقة مشتركة. لا تحتاج رمز المستخدم عادة إلى الوقوع ومعالجتها على وجه التحديد.
التنفيذ الافتراضي لـ AuthenticationManager هو Providermanager ، الذي ينفي مجموعة من مثيلات AuthenticationProvider لتنفيذ المصادقة.
تشبه AuthenticationProvider و AuthenticationManager المصادقة ، وكلاهما يحتوي على مصادقة ، لكن لديه دعمًا إضافيًا للسماح بالاستعلام عما إذا كان المتصل يدعم نوع مصادقة معين:
الواجهة العامة AuthenticationProvider {مصادقة المصادقة (مصادقة المصادقة) يلقي المصادقة ؛ الدعم المنطقي (الفئة <؟> المصادقة) ؛}يحتوي Providermanager على مجموعة من المصادقة. عند تنفيذ المصادقة ، فإنه يعبر مقدمي الخدمات ثم يستدعي الدعم. إذا كان مدعومًا ، فإنه ينفذ طريقة المصادقة التي تعبر المزود الحالي. إذا تم مصادقة مزود بنجاح ، فاكسي.
مصادقة المصادقة العامة (مصادقة المصادقة) يلقي المصادقة {class <؟ يمتد المصادقة> totest = Authentication.getClass () ؛ AuthenticationException lastException = null ؛ نتيجة المصادقة = فارغة ؛ Boolean Debug = logger.isdebugenabled () ؛ لـ (مصادقة Provider: getProviders ()) {if (! provider.supports (totest)) {contern ؛ } if (debug) {logger.debug ("محاولة المصادقة باستخدام" + provider.getClass (). getName ()) ؛ } حاول {result = provider.authenticate (المصادقة) ؛ if (result! = null) {copyDetails (المصادقة ، النتيجة) ؛ استراحة؛ }} catch (accountStatusexception e) {prepareException (e ، المصادقة) ؛ // SEC-546: تجنب استطلاع مقدمي الخدمات الإضافية إذا كان فشل المصادقة بسبب // حالة حساب غير صالحة } catch (InternalAuthenticationServiceException e) {prepareException (e ، المصادقة) ؛ رمي ه ؛ } catch (AuthenticationException e) {lastException = e ؛ }} if (result == null && parent! = null) {// السماح للوالد أن يحاول. حاول {result = parent.authenticate (المصادقة) ؛ } catch (providernotfoundException e) {// تجاهل لأننا سنرمي أدناه إذا لم يحدث استثناء آخر قبل // الاتصال بالأصل وقد يرمي الوالد // }} if (result! = null) {if (erasEcRredSafterAuthentication && (result estructionof crediventscontainer)) {// المصادقة كاملة. قم بإزالة بيانات الاعتماد والبيانات السرية الأخرى // من المصادقة ((CredistsScontainer). } EventPublisher.PublishAuthenticationsuccess (النتيجة) ؛ نتيجة العودة } // كان الوالد فارغًا ، أو لم يصادق (أو رمي استثناء). if (lastException == null) {lastException = new providernotfoundException (messages.getMessage ("providermanager.providernotfound" ، كائن جديد [] {totest.getName ()} ، "لم يتم العثور على مصادقة لـ {0}")) ؛ } prepareException (LastException ، المصادقة) ؛ رمي lastException. }كما يتضح من الكود أعلاه ، لدى Providermanager أحد الوالدين الاختياري. إذا لم يكن الوالد فارغًا ، فسيتم استدعاء الوالدين.
المصادقة
المصادقة لديها العديد من التطبيقات. الشخص الذي تشعر بالقلق أكثر من ذلك هو DaoAuthenticationProvider ، الموروث من AbstractUserDetailsauthenticationProvider. الأساسية هو تنفيذ المصادقة من خلال userDetails. سيتم تحميل DaoAuthenticationProvider تلقائيًا بشكل افتراضي ولا يلزم تكوينه يدويًا.
دعونا أولاً نلقي نظرة على agrectuserDetailsauthenticationProvider وننظر إلى أكثر المصادقة الأساسية:
مصادقة المصادقة العامة (مصادقة المصادقة) يلقي المصادقة {// يجب أن يكون usernamepasswordauthenticationtoken Assert.isinstanceof (usernamepasswordauthenticationtoken.class ، المصادقة ، messages.getMessage ("ustrultuserdetailsauthenticationprovider.onlysupports" ، " // Get username string username = (Authentication.getPrincipal () == NULL)؟ "none_provided": Authentication.getName () ؛ Boolean cachewasused = true ؛ // احصل على userDetails من مستخدم Cache = this.usercache.getuserFromCache (اسم المستخدم) ؛ if (user == null) {cachewasused = false ؛ حاول {// RetrieveUser Abstract Method للحصول على مستخدم المستخدم = RetrieveUser (اسم المستخدم ، (usernamePasswordauthenticationToken) مصادقة) ؛ } catch (usernamenotfoundException notfound) {logger.debug ("user '" + username + "' 'not found") ؛ if (hideusernotfoundExceptions) {رمي badcredientialSexception (messages.getMessage ("AbstractUserDetailsAuthenticationProvider.Badcredentials" ، "بيانات الاعتماد السيئة")) ؛ } آخر {رمي notfound ؛ }} Assert.notnull (user ، "RetrieveUser Return Null - انتهاك لعقد الواجهة") ؛ } جرب {// pre-check ، defaultPreaUtenticationChecks ، تحقق مما إذا كان المستخدم مغلقًا أو ما إذا كان الحساب متاحًا لـ preauthenticationchecks.check (user) ؛ // method method ، check check check addainauthenticationcks (user ، (usernamePasswordauthenticationToken)) ؛ } catch (AuthenticationException issection) {if (cachewasused) {// كانت هناك مشكلة ، لذا حاول مرة أخرى بعد التحقق // نحن نستخدم أحدث البيانات (أي ليس من ذاكرة التخزين المؤقت) cachewasused = false ؛ user = RetrieveUser (اسم المستخدم ، (usernamePasswordauthenticationToken) مصادقة) ؛ preAuthenticationChecks.check (المستخدم) ؛ ExtrauthenticationChecks (المستخدم ، (usernamepasswordauthenticationtoken) مصادقة) ؛ } آخر {رمي استثناء ؛ }} // post-check defaultpostauthenticationchecks ، تحقق من iscredentialsnonexpired postauthenticationChecks.check (user) ؛ if (! cachewasused) {this.usercache.putuserIncache (user) ؛ } Object princialtoreturn = user ؛ if (forceprincipalasstring) {princialTorTurn = user.getUserName () ؛ } Return CreateSuccessAuthentication (princialtoreturn ، المصادقة ، المستخدم) ؛ }يعتمد الاختبار أعلاه بشكل أساسي على تطبيق userDetails ، حيث يتم تطبيق منطق اكتساب المستخدم والتحقق من قبل فئات محددة. التنفيذ الافتراضي هو daoAuthenticationProvider. جوهر هذه الفئة هو السماح للمطورين بتزويد userDetailsService بالحصول على userDetails و PasswordEncoder للتحقق مما إذا كانت كلمة المرور صالحة:
userDetailsService userDetailsService ؛ PassworderCoder PasswordEncoder ؛
للاطلاع على التنفيذ المحدد ، RetrieveUser ، اتصل مباشرة على userDetailsService للحصول على المستخدم:
userDetails النهائي المحمي RetrieveUser (اسم مستخدم السلسلة ، usernamepasswordauthenticationtoken مصادقة) يلقي المصادقة attivicationexception {userDetails loadedUser ؛ حاول {loadedUser = this.getuserDetailsService (). loadUserByUserName (اسم المستخدم) ؛ } catch (usernamenotfoundException notfound) {if (antaintication.getCredentials ()! = null) {string passpassword = antaintication.getCredentials (). toString () ؛ passwordencoder.ispasswordvalid (usernotFoundEncodedPassword ، provespassword ، null) ؛ } رمي } catch (استثناء مستودع prository) {رمي InternalAuthenticationServiceException (ropositoryproblem.getMessage () ، ropositoryproblem) ؛ } if (loadedUser == null) {رمي internalaUthenticationserviceException ("userDetailsService return null ، وهو انتهاك عقد الواجهة") ؛ } إرجاع loadedUser ؛ }دعونا نلقي نظرة على التحقق:
الفراغ المحمي الإضافي extracticationChes (userDetails userDetails ، usernamePasswordauthenticationToken مصادقة) يلقي المصادقة {كائن ملح = فارغ ؛ if (this.saltsource! = null) {salt = this.saltsource.getSalt (userDetails) ؛ } if (Authentication.getCredentials () == null) {logger.debug ("فشل المصادقة: لم يتم توفير بيانات اعتماد") ؛ رمي badcredentialSexception (messages.getMessage ("agrusserDetailsauthenticationProvider.Badcredentials" ، "بيانات الاعتماد السيئة")) ؛ } // احصل على سلسلة كلمة مرور المستخدم المقدمة PassPassword = Authentication.getCredentials (). toString () ؛ // قارن ما إذا كانت كلمة المرور بعد passwordencoder هي نفسها كلمة مرور userDetails إذا (! passwordencoder.ispasswordvalid (userDetails.getPassword () ، prodaspassword ، salt)) رمي badcredentialSexception (messages.getMessage ("agrusserDetailsauthenticationProvider.Badcredentials" ، "بيانات الاعتماد السيئة")) ؛ }}ملخص: لتخصيص المصادقة ، واستخدام DaoAuthenticationProvider ، تحتاج فقط إلى تزويدها بـ PasswordEncoder و UserDetailsService.
تخصيص مديري المصادقة
يوفر Spring Security مصادقة مصادقة فئة منشئ ، مما يتيح لك تنفيذ المصادقة المخصصة بسرعة.
انظر وصف رمز المصدر الرسمي:
SecurityBuilder المستخدمة لإنشاء anufacticationManager. يسمح بسهولة للبناء في مصادقة الذاكرة ، ومصادقة LDAP ، والمصادقة المستندة إلى JDBC ، وإضافة userDetailsService ، وإضافة AuthenticationProvider.
يمكن استخدام AuthenticationManagerBuilder لإنشاء anuffecticationmanager ، والذي يمكن أن ينشئ المصادقة المستندة إلى الذاكرة ، ومصادقة LDAP ، ومصادقة JDBC ، وإضافة userDetailsService و AuthenticationProvider.
استخدام بسيط:
@configuration@enablewebsecurity@enableglobalbalmethodsecurity (prepoStenabled = true ، securedenabled = true) يمتد تطبيقات الفئة العامة على WebSecurityConfigureRadapter {public securityconfiguration Corsfilter ، SecurityProMbleSsupport Problemupport) {this.AuthenticationManagerBuilder = antainticationManagerBuilder ؛ this.userDetailsService = userDetailsService ؛ this.tokenProvider = tokenprovider ؛ this.corsfilter = corsfilter ؛ this.problemSupport = مشاكل الدعم ؛ } postconstruct public void init () {try {antainicationManagerBuilder .userDetailsService (userDetailsService) .PasswordEncoder (passwordencoder ()) ؛ } catch (استثناء e) {رمي جديد beaninitializationException ("فشل تكوين الأمان" ، e) ؛ }} @Override contected void config (httpsecurity http) يلقي الاستثناء {http .addfilterbefore (corsfilter ، usernamepasswordauthentication. .Headers () .R.FrameOptions () .disable (). و (). .antmatchers ("/api/antensicate"). permanly () .antmatchers ("/API/account/reset-password/init"). fermanly () .antmatchers ("/api/account/reset-password/finish"). .antmatchers ("/management/health"). permanly () .antmatchers ("/management/**"). hasauthority (exectoriesconstants.admin) .antmatchers ("/v2/api-docs/**"). .antmatchers ("/swagger-ui/index.html"). hasauthority (exectortsconstants.admin). و () .apply (SecurityConfigureRadapter ()) ؛ }}التصريح والتحكم في الوصول
بمجرد نجاح المصادقة ، يمكننا الاستمرار في التفويض ، والذي يتم تنفيذه من خلال AccessDecisionManager. هناك ثلاثة تطبيقات للإطار ، ويتم تعليم الافتراضي الذي يتم إجراؤه من خلال AccessDecisionVoter ، والذي يشبه إلى حد ما providermanager إلى المصادقة للمصادقة.
قرار الفراغ العام (مصادقة المصادقة ، كائن الكائن ، المجموعة <NigipAttribute> configattributes) يلقي AccessDeniEdException {int deny = 0 ؛ // traversal decisionvoter لـ (AccessDecisionVoter Voter: getDecisionVoters ()) {// potting int result = pott.vote (المصادقة ، الكائن ، configattributes) ؛ if (logger.isdebugenabled ()) {logger.debug ("الناخب:" + ناخب + "، عاد:" + نتيجة) ؛ } switch (result) {case AccessDecisionVoter.access_granted: return ؛ Case AccessDecisionVoter.access_denied: deny ++ ؛ استراحة؛ الافتراضي: استراحة ؛ }} // Veto if (deny> 0) {رمي AccessDenieDexception (messages.getMessage ("AbstractAccessDecisionManager.Accessdenied" ، "يتم رفض الوصول")) ؛ } // للوصول إلى هذا الحد ، كل AccessDecisionVoter امتنعت عن checkallowifallabstaindecisions () ؛ }لنلقي نظرة على AccessDecisionVoter:
الدعم المنطقي (سمة configattribute) ؛ الدعم المنطقي (الفئة <؟> clazz) ؛ int التصويت (مصادقة المصادقة ، كائن s ، المجموعة <configattribute> سمات) ؛
الكائن هو المورد الذي يريد المستخدم الوصول إليه ، والتكوين هو الشرط الذي يحتاجه الكائن. عادةً ما تكون الحمولة عبارة عن سلسلة ، مثل ROLE_ADMIN. لذلك دعونا نلقي نظرة على تنفيذ ROLEVOTER. هو جوهر استخراج الممنوحة من المصادقة ، ثم مقارنة مع التكوين ما إذا كانت الشروط قد تم استيفاءها.
الدعم المنطقي العام (السمة configattribute) {if ((attribute.getAttribute ()! = null) && attribute.getAttribute (). StartSwith (getRolePrefix ())) {return true ؛ } آخر {return false ؛ }} الدعم المنطقي العام (الفئة <؟> clazz) {return true ؛ } public int التصويت (مصادقة المصادقة ، كائن كائن ، مجموعة <NugmatTribute> السمات) {if (Authentication == null) {return access_denied ؛ } int result = access_abstain ؛ // الحصول على GrantedAuthority InformationCollection <؟ يمتد prantedauthority> السلطات = المستخلصات (المصادقة) ؛ لـ (ConfigAttribute Settribute: entributes) {if (this.supports (السمة)) {// تم رفض Access بشكل افتراضي = access_denied ؛ // محاولة العثور على سلطة ممنوحة مطابقة لـ (سلطة prantedauthority: السلطات) {// تحديد ما إذا كانت هناك سلطة مطابقة إذا (attribute.getAttribute (). equals (Authord.getAuthority ())) {// يمكنك الوصول إلى الإرجاع access_granted ؛ }}}} نتيجة الإرجاع ؛ }هنا يجب أن أسأل ، من أين جاء التكوين؟ في الواقع ، هو في تكوين ApplicationsEcurity أعلاه.
كيفية تنفيذ أمان الويب
يعتمد أمان الربيع (لخلف واجهة المستخدم و HTTP) في طبقة الويب على مرشحات servlet ، ويظهر الشكل التالي التسلسل الهرمي النموذجي للمعالجات لطلب HTTP واحد.
يتم تسجيل أمان الربيع على طبقة الويب من خلال FilterChainproxy كمرشح واحد ، مرشح داخل الوكيل.
FilterChainproxy يعادل حاوية مرشح. من خلال VirtualFilterchain ، يتم استدعاء كل مرشح داخلي بالتسلسل.
Dofilter public void (طلب ServletRequest ، استجابة servletResponse ، سلسلة filterchain) يلقي ioException ، servleTexception {boolean clearContext = request.getAttribute (filter_applied) == null ؛ if (clearContext) {try {request.setAttribute (filter_applied ، boolean.true) ؛ dofilterinternal (طلب ، استجابة ، سلسلة) ؛ } أخيرًا {securityContextholder.clearContext () ؛ request.RemoVeatTribute (filter_applied) ؛ }} آخر {dofilterinternal (طلب ، استجابة ، سلسلة) ؛ }} private void dofilterinternal (servletRequest request ، servletResponse ، سلسلة filterchain) يلقي ioException ، servleTexception {firewalledRequest fwrequest = requare all all allget. httpservletresponse fwresponse = جدار الحماية .getFireWalledResponse ((httpservletresponse)) ؛ قائمة <Filter> filters = getFilters (fwRequest) ؛ if (filters == null || filters.size () == 0) {if (logger.isdebugenabled ()) {logger.debug (urlutils.buildRequesturl (fwrequest) + (filters == null؟ " } fwrequest.reset () ؛ Chain.dofilter (fwrequest ، fwresponse) ؛ يعود؛ } VirtualFilterChain vfc = new VirtualFilterChain (fwrequest ، سلسلة ، مرشحات) ؛ vfc.dofilter (fwrequest ، fwresponse) ؛ } فئة ثابتة خاصة VirtualFilterChain تنفذ filterchain {private Final Filterchain OriginalChain ؛ القائمة النهائية الخاصة <Filter> مرشحات إضافية ؛ خاص نهائي FirewalledRequest FirewalledRequest ؛ حجم int النهائي الخاص ؛ private int currentposition = 0 ؛ Private VirtualFilterChain (FirewalledRequest FirewalledRequest ، سلسلة Filterchain ، قائمة <Filter> إضافية) {this.originalChain = Chain ؛ this.additionalFilters = extraftists ؛ this.size = extraffilters.size () ؛ هذا. } public void dofilter (servletRequest request ، servletResponse) يلقي ioException ، servletexception {if (currentPosition == size) {if (logger.isdeBugenabled ()) }. OriginalChain.Dofilter (طلب ، استجابة) ؛ } آخر {currentPosition ++ ؛ Filter nextFilter = extrafilters.get (CurrentPosition - 1) ؛ if (logger.isdebugenabled ()) {logger.debug (urlutils.buildRequesturl (FirewalledRequest) + "في الموضع" + currentposition + "من" + size + "في سلسلة مرشح إضافية ؛ مرشح إطلاق النار: ' + nextFilter.getClass () getimplename () } nextfilter.dofilter (طلب ، استجابة ، هذا) ؛ }}}}الرجوع إلى
https://spring.io/guides/topicals/spring-security-architecture/
https://docs.spring.io/spring-security/site/docs/5.0.5
لخص
ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون لمحتوى هذه المقالة قيمة مرجعية معينة لدراسة أو عمل الجميع. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل. شكرا لك على دعمك إلى wulin.com.