Kata pengantar
Kerangka kerja otorisasi umum yang populer sekarang termasuk Shiro Apache dan Musim Semi Musim Semi Musim Semi. Ketika datang ke otentikasi layanan mikro hari ini, kita perlu menggunakan kerangka otorisasi kita untuk membangun layanan otentikasi kita sendiri. Hari ini, Perdana Menteri telah menjadi Perdana Menteri.
Keamanan Musim Semi terutama mengimplementasikan otentikasi (otentikasi, solusi siapa Anda?) Dan kontrol akses (kontrol akses, yaitu apa yang Anda izinkan lakukan?, Juga dikenal sebagai otorisasi). Keamanan Musim Semi memisahkan otentikasi dari arsitektur otorisasi dan memberikan poin ekstensi.
Objek inti
Kode utama berada di bawah paket Spring-Security-Core. Untuk memahami keamanan musim semi, Anda perlu memperhatikan objek inti di dalamnya.
SecurityContextholder, SecurityContext dan Authentication
SecurityContextholder adalah wadah penyimpanan untuk SecurityContext. Penyimpanan Threadlocal digunakan secara default, yang berarti bahwa metode SecurityContext tersedia di utas yang sama.
SecurityContext terutama menyimpan informasi utama aplikasi dan diwakili oleh otentikasi dalam keamanan musim semi.
Dapatkan Kepala Sekolah:
Object Principal = SecurityContExtholder.getContext (). GetAuthentication (). GetPrincipal (); if (instance utama dari userDetails) {string username = ((userDetails) principal) .getUserName ();} lain {string username = principal.tostring ();Di Spring Security, Anda dapat melihat definisi otentikasi:
Otentikasi antarmuka publik memperluas prinsipal, serializable {collection <? memperluas Grantedauthority> getAuthoritys (); / *** biasanya kata sandi*/ objek getCredentials (); /*** Menyimpan detail tambahan tentang permintaan otentikasi. Ini mungkin alamat IP *, nomor seri sertifikat dll. */ Objek getDetails (); /*** Digunakan untuk mengidentifikasi apakah itu diautentikasi. Jika Anda masuk dengan nama pengguna dan kata sandi, biasanya nama pengguna*/ objek getPrincipal (); / *** apakah itu diautentikasi*/ boolean isauthenticated (); void setauthenticated (boolean isauthenticated) melempar ilegalargumentException;}Dalam aplikasi praktis, USERNAMEPASSWORDAuthenticationToken biasanya digunakan:
Kelas Abstrak Publik AbstractAuthenticationToken mengimplementasikan otentikasi, kredensialScontainer {} kelas publik UsernamepasswordAuthenticationToken memperluas abstractAuthenticationToken {}Proses otentikasi umum biasanya seperti ini: Buat UserNamepasswordAuthenticationToken dan kemudian serahkan ke AuthenticationManager untuk otentikasi (dijelaskan secara rinci nanti). Jika otentikasi dilewati, informasi otentikasi akan disimpan melalui SecurityContextholder.
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());Authentication authentication = this.authenticationManager.authenticate(authenticationToken);SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails dan UserDetailsService
UserDetails adalah antarmuka utama dalam keamanan musim semi, yang digunakan untuk mewakili kepala sekolah.
Antarmuka Publik UserDetails memperluas informasi yang dapat diserializable { / *** otorisasi pengguna dapat dipahami sebagai peran* / pengumpulan <? memperluas Grantedauthority> getAuthoritys (); / ** * Kata sandi pengguna * * @return Kata sandi */ string getPassword (); / *** Nama pengguna**/ string getUserName (); boolean isaccountnonexpired (); boolean isaccountnonlocked (); boolean iscredentialsnonexpired (); boolean isenabled ();}UserDetails memberikan informasi yang diperlukan yang diperlukan untuk otentikasi. Dalam penggunaan aktual, Anda dapat mengimplementasikan sendiri pengguna sendiri dan menambahkan informasi tambahan, seperti email, seluler, dan informasi lainnya.
Dalam otentikasi, kepala sekolah biasanya nama pengguna. Kita bisa mendapatkan userDetails melalui kepala sekolah melalui userDetailsService:
antarmuka publik userDetailsService {userDetails loadUserByUserName (string username) melempar usernamenotfoundException;}Grantedauthority
Seperti yang disebutkan dalam UserDetails, Grantedauthority dapat dipahami sebagai peran, seperti Role_administrator atau Role_HR_SUperVisor.
ringkasan
Sertifikasi otentikasi
AuthenticationManager
Otentikasi terutama dicapai melalui antarmuka AuthenticationManager, yang hanya berisi satu metode:
Antarmuka Publik AuthenticationManager {Otentikasi Otentikasi (Otentikasi Otentikasi) Melemparkan AuthenticationException;}Metode otentikasi () terutama melakukan tiga hal:
AuthenticationException adalah pengecualian runtime, yang biasanya ditangani oleh aplikasi dengan cara yang sama. Kode pengguna biasanya tidak perlu ditangkap dan diproses secara khusus.
Implementasi default dari AuthenticationManager adalah ProviderManager, yang mendelegasikan serangkaian instance AuthenticationProvider untuk mengimplementasikan otentikasi.
AuthenticationProvider dan AuthenticationManager mirip dengan otentikasi, keduanya berisi otentikasi, tetapi memiliki dukungan metode tambahan untuk memungkinkan permintaan apakah penelepon mendukung jenis otentikasi yang diberikan:
Authentication AuthenticationProvider {Otentikasi Otentikasi (Otentikasi Otentikasi) melempar AuthenticationException; Dukungan Boolean (Kelas <?> Otentikasi);}ProviderManager berisi serangkaian penyangga otentikasi. Saat menjalankan otentikasi, ia melintasi penyedia dan kemudian memanggil dukungan. Jika didukung, itu mengeksekusi metode otentikasi yang melintasi penyedia saat ini. Jika penyedia disahkan dengan sukses, hancurkan.
Otentikasi Otentikasi Publik (Otentikasi Otentikasi) Melemparkan AuthenticationException {class <? Extends Authentication> totest = authentication.getClass (); AuthenticationException LastException = null; Hasil otentikasi = null; boolean debug = logger.isdebugeNabled (); untuk (AuthenticationProvider Provider: getProviders ()) {if (! provider.supports (totest)) {lanjutan; } if (debug) {logger.debug ("upaya otentikasi menggunakan" + provider.getClass (). getName ()); } coba {result = provider.authenticate (otentikasi); if (hasil! = null) {copyDetails (otentikasi, hasil); merusak; }} catch (AccountStatusException e) {prepareException (e, otentikasi); // SEC-546: Hindari polling penyedia tambahan jika kegagalan auth disebabkan oleh // status akun tidak valid lempar e; } catch (internalAuthenticationServiceException e) {prepareException (e, otentikasi); lempar e; } catch (AuthenticationException e) {LastException = E; }} if (result == null && parent! = null) {// Izinkan induk untuk mencoba. coba {result = parent.Authenticate (otentikasi); } catch (providerNotFoundException e) {// abaikan karena kita akan melempar di bawah jika tidak ada pengecualian lain yang terjadi sebelum // memanggil orang tua dan orang tua // dapat melempar providerNotFound meskipun penyedia pada anak sudah // menangani permintaan} (authenticationException e) {last exception = e; }} if (result! = null) {if (eraseCredentialSafterAuthentication && (hasil instanceof credentialscontainer)) {// otentikasi selesai. Hapus kredensial dan data rahasia lainnya // dari hasil otentikasi ((CredentialScontainer)) .erasecredentials (); } EventPublisher.PublishAuthenticationsuccess (Hasil); hasil pengembalian; } // Orangtua adalah nol, atau tidak mengotentikasi (atau melempar pengecualian). if (lastException == null) {lastException = new providerNotFoundException (messes.getMessage ("providerManager.providerNotFound", objek baru [] {totest.getName ()}, "tidak ada AuthenticationProvider yang ditemukan untuk {0}")); } preparatException (LastException, otentikasi); melempar LastException; }Seperti yang dapat dilihat dari kode di atas, ProviderManager memiliki induk opsional. Jika orang tua tidak kosong, Parent.Authenticated (otentikasi) disebut
AuthenticationProvider
AuthenticationProvider memiliki banyak implementasi. Yang paling Anda khawatirkan adalah DaoAuthenticationProvider, diwarisi dari AbstracationSuserdetailSAuthenticationProvider. Inti adalah mengimplementasikan otentikasi melalui userDetails. DaoAuthenticationProvider akan secara otomatis dimuat secara default dan tidak perlu dikonfigurasi secara manual.
Pertama -tama mari kita lihat AbstracationUserdetailSAuthenticationProvider dan lihat otentikasi inti yang paling:
Otentikasi Otentikasi Publik (Otentikasi Otentikasi) melempar otenticationException {// harus USERNAMEPASSWORDAuthenticationToken assert.isinstanceof (UsernamepasswordAuthenticationToken.class, Otentikasi. didukung ")); // Dapatkan UserName String username = (authentication.getPrincipal () == null)? "None_provided": authentication.getName (); Boolean CacheWasused = true; // Dapatkan userDetails dari cache user = this.usercache.getUserFromCache (nama pengguna); if (user == null) {cacheWasused = false; coba {// retrieveuser abstrak metode untuk mendapatkan pengguna pengguna = retrieveuser (nama pengguna, (usernamepasswordAuthenticationToken) otentikasi); } catch (UserNamenotFoundException notfound) {logger.debug ("user '" + nama pengguna + "' tidak ditemukan"); if (cideusernotfoundExceptions) {lempar new badcredentialsException (messes.getMessage ("abstractUserDetailSAuthenticationProvider.badcredentials", "kredensial buruk")); } else {throw notfound; }} Assert.notnull (pengguna, "retrieveuser mengembalikan null - pelanggaran kontrak antarmuka"); } coba {// pre-check, defaultPreAuthenticationChecks, periksa apakah pengguna terkunci atau apakah akun tersedia untuk preauthenticationChecks.check (pengguna); // Metode Abstrak, Periksa Kustom TambahanAuthenticationChecks (Pengguna, (UsernamepasswordAuthenticationToken)); } catch (AuthenticationException Exception) {if (CacheWasused) {// Ada masalah, jadi coba lagi setelah memeriksa // kami menggunakan data terbaru (yaitu bukan dari cache) CacheWasused = false; user = retrieveuser (nama pengguna, (usernamepasswordAuthenticationToken) otentikasi); preauthenticationChecks.check (pengguna); tambahan aThenticationChecks (pengguna, (usernamepasswordAuthenticationToken) otentikasi); } else {lempar pengecualian; }} // post-check defaultPostAuthenticationChecks, periksa isCredentialSnonexpired PostauthenticationChecks.Check (pengguna); if (! CacheWasused) {this.usercache.putuserincache (pengguna); } Objek principalToReturn = pengguna; if (forcePrincipAsstring) {principalToReturn = user.getUserName (); } return createCcessAuthentication (principalToReturn, otentikasi, pengguna); }Tes di atas terutama didasarkan pada implementasi pengguna, di mana akuisisi dan logika verifikasi pengguna diimplementasikan oleh kelas -kelas tertentu. Implementasi default adalah DaoAuthenticationProvider. Inti dari kelas ini adalah untuk memungkinkan pengembang memberikan UserDetailsService untuk mendapatkan UserDetails dan PasswordEncoder untuk memverifikasi apakah kata sandi itu valid:
UserDetailsService Private UserDetailsService; kata sandi pribadi pribadi;
Untuk melihat implementasi spesifik, RetrieveUser, secara langsung hubungi userDetailsService untuk mendapatkan pengguna:
RetrieveUser UserDetails Final yang Dilindungi (String UserName, UsernAmePasswordAuthenticationToken Authentication) melempar AuthenticationException {UserDetails LoadedUser; coba {loadedUser = this.getUserDetailsService (). LoadUserbyUserName (nama pengguna); } catch (UserNamenotFoundException notfound) {if (authentication.getCredentials ()! = null) {string presentedpassword = authentication.getCredentials (). toString (); PasswordEncoder.ispasswordValid (UserNotFoundEncodedPassword, PresentedPassword, null); } lempar notfound; } catch (Exception RepositoryProblem) {lempar internalAuthenticationsexception baru (repositoryproblem.getMessage (), repositoryproblem); } if (loadedUser == null) {lempar internalAuthenticationServiceException baru ("UserDetailsService Returned Null, yang merupakan pelanggaran kontrak antarmuka"); } return loadeduser; }Mari kita lihat verifikasi:
void tambahan yang lebih terlindungi. if (this.saltsource! = null) {Salt = this.saltsource.getSalt (userDetails); } if (authentication.getCredentials () == null) {logger.debug ("Otentikasi gagal: tidak ada kredensial yang disediakan"); Lempar BadcredentialSException baru (messes.getMessage ("AbstractUserDetailSAuthenticationProvider.Badcredentials", "kredensial buruk")); } // Dapatkan string kata sandi pengguna yang disajikanpassword = authentication.getCredentials (). ToString (); // Bandingkan apakah kata sandi setelah kata sandi adalah sama dengan kata sandi userDetails if (! PasswordEncoder.ispasswordValid (userdetails.getpassword (), presedpassword, garam)) {logger.debug ("Otentikasi gagal: kata sandi tidak cocok dengan nilai yang disimpan"); Lempar BadcredentialSException baru (messes.getMessage ("AbstractUserDetailSAuthenticationProvider.Badcredentials", "kredensial buruk")); }}Ringkasan: Untuk menyesuaikan otentikasi, menggunakan DaoAuthenticationProvider, Anda hanya perlu menyediakannya dengan kata sandi dan userDetailsService.
Kustomisasi manajer otentikasi
Spring Security menyediakan AuthenticationManagerBuilder kelas pembangun, yang memungkinkan Anda untuk dengan cepat menerapkan otentikasi khusus.
Lihat deskripsi kode sumber resmi:
SecurityBuilder digunakan untuk membuat authenticationManager. Mengizinkan untuk membangun otentikasi memori dengan mudah, otentikasi LDAP, otentikasi berbasis JDBC, menambahkan UserDetailsService, dan menambahkan AuthenticationProvider.
AuthenticationManagerBuilder dapat digunakan untuk membangun AuthenticationManager, yang dapat membuat otentikasi berbasis memori, otentikasi LDAP, otentikasi JDBC, dan menambahkan UserDetailsService dan AuthenticationProvider.
Penggunaan Sederhana:
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class ApplicationSecurity extends WebSecurityConfigurerAdapter { public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService,TokenProvider tokenProvider,CorsFilter Corsfilter, SecurityProblemsupport Problempupport) {this.authenticationManagerBuilder = authenticationManagerBuilder; this.userdetailsservice = userDetailsService; this.tokenprovider = tokenProvider; this.corsfilter = corsfilter; this.problemsupport = problemupport; } @PostConstruct public void init () {coba {authenticationManagerBuilder .UserDetailsService (userDetailsService) .passwordEncoder (passwordEncoder ()); } Catch (Exception e) {Throw New BeanInitializationException ("Konfigurasi Keamanan Gagal", E); }} @Override Protected void configure (httpsecurity http) melempar Exception {http .addfilterbefore (corsfilter, USERNAMEPASSWORDAUTHENTICEDFILTER.AccessDen () .AuthenticationEntPoint (masalah MASALAH) .Accescesshand () .Authenticationentpoint (masalah MASAL .disable () .Headers () .frameOptions () .disable () .and () .SessionManagement () .SessionCreationPolicy (sessioncreationpolicy.stateless) .and () .Authorizequests () .Antmatchers ("/API/register"). .AntMatchers ("/API/AUTENTICE"). empitAll () .Antmatchers ("/API/ACCOUND/RESET-PASSWORD/INIT"). PermitAll () .AntMatchers ("/API/Account/Reset-Password/Finish"). .AntMatchers ("/API/**"). Authenticated () .Antmatchers ("/Manajemen/Kesehatan"). Permitall () .AntMatchers ("/Management/**"). HASAUTHORITAS (OtoritiesConstants.admin) .AntMatchers ("/V2/API-DOCS/**"). .AntMatchers ("/Swagger-Resources/Configuration/UI"). empoTall () .AntMatchers ("/Swagger-ui/Index.html"). HASAUTHORITAS (OtoritiesConstants.admin) .and () .Apply (SecurityConfigurerAdapter ()); }}Otorisasi dan Kontrol Akses
Setelah otentikasi berhasil, kami dapat terus mengotorisasi, yang diimplementasikan melalui AccessDecisionManager. Ada tiga implementasi dari kerangka kerja, standarnya adalah afirmatif, yang dibuat melalui AccessDecisionVoter, yang sedikit seperti penyedia yang dipercayakan kepada penyangga otentikasi untuk otentikasi.
Keputusan public void (Otentikasi Otentikasi, Objek Objek, Koleksi <ConfigAttribute> ConfigAttributes) Melemparkan AccessDenIdException {int DENY = 0; // Traversal DecisionVoter untuk (AccessDecisionVoter Pemilih: getDecisionVoters ()) {// voting int result = vote.vote (otentikasi, objek, configAttributes); if (logger.isdebugeNabled ()) {logger.debug ("pemilih:" + pemilih + ", dikembalikan:" + hasil); } switch (hasil) {case AccessDecisionVoter.access_granted: return; case AccessDecisionVoter.Access_DENIED: DENY ++; merusak; default: break; }} // veto if (tolak> 0) {throw new AccessDENiedException (messes.getMessage ("AbstractAccessDecisionManager.accesDenied", "Access ditolak")); } // Untuk mendapatkan sejauh ini, setiap akses DECISIONVOTER HANCINTAND DOPSALLOWIFALLABSTAINDECISSIONS (); }Mari kita lihat AccessDecisionVoter:
Dukungan Boolean (Atribut ConfigAttribute); Dukungan Boolean (kelas <?> CLAZZ); int vote (otentikasi otentikasi, objek S, koleksi <ConfigAttribute> atribut);
Objek adalah sumber daya yang ingin diakses pengguna, dan ConfigAttribute adalah kondisi yang harus dipenuhi objek. Biasanya muatan adalah string, seperti Role_admin. Jadi mari kita lihat implementasi Rolevoter. Inti adalah untuk mengekstrak hambatan yang diberikan dari otentikasi, dan kemudian membandingkan dengan konfigurasi apakah kondisinya terpenuhi.
Dukungan Boolean Publik (Atribut ConfigAttribute) {if ((attribute.getAttribute ()! = null) && attribute.getAttribute (). startswith (getroleprefix ())) {return true; } else {return false; }} public boolean dukungan (class <?> clazz) {return true; } Public int vote (otentikasi otentikasi, objek objek, koleksi <ConfigAttribute> atribut) {if (authentication == null) {return access_denied; } int result = access_abstain; // Dapatkan informasi informasi <? Extends Grantedauthority> Otorities = ExtractAuthority (otentikasi); untuk (Atribut ConfigAttribute: Atribut) {if (this.supports (atribut)) {// akses yang ditolak oleh hasil default = access_denied; // Upaya untuk menemukan otoritas yang diberikan untuk (Grantedauthority Authority: Otorities) {// Tentukan apakah ada otoritas yang cocok jika (atribute.getAttribute (). Equals (Authority.getAuthority ())) {// Anda dapat mengakses pengembalian access_granted; }}}} hasil pengembalian; }Di sini saya harus bertanya, dari mana konfigurasi berasal? Faktanya, itu dalam konfigurasi keamanan aplikasi di atas.
Cara mengimplementasikan keamanan web
SPRING Security (untuk backend UI dan HTTP) di lapisan web didasarkan pada filter servlet, dan gambar berikut menunjukkan hierarki penangan khas untuk permintaan HTTP tunggal.
Spring Security terdaftar ke lapisan web melalui filterchainproxy sebagai filter tunggal, filter di dalam proxy.
FilterChainProxy setara dengan wadah filter. Melalui VirtualFilterChain, setiap filter internal dipanggil secara berurutan.
public void dofilter (permintaan servletRequest, respons servletResponse, rantai filterchain) melempar ioException, servletException {boolean clearcontext = request.getAttribute (filter_applied) == null; if (clearcontext) {coba {request.setAttribute (filter_applied, boolean.true); dofilterinternal (permintaan, respons, rantai); } akhirnya {SecurityContExTholder.ClearContext (); request.removeattribute (filter_applied); }} else {dofilterinternal (permintaan, respons, rantai); }} private void dofilterinternal (permintaan servletRequest, respons servletResponse, rantai filterchain) melempar ioException, servletException {firewalledRequest fwrequest = firewall .getFireWallEdrequest ((httpservletreest) permintaan); HttpservletResponse fwResponse = firewall .getFireWalledResponse ((httpservletResponse) respons); Daftar <TERFERTER> filter = getFilters (fwrequest); if (filter == null || filter.size () == 0) {if (logger.isdebugeNabled ()) {logger.debug (urlutils.buildrequesturl (fwrequest) + (filter == null? "Tidak memiliki filter yang cocok": "memiliki filter kosong") (filter == null? } fwrequest.reset (); rantai.dofilter (fwrequest, fwresponse); kembali; } VirtualFilterchain vfc = virtualfilterchain baru (fwrequest, rantai, filter); vfc.dofilter (fwrequest, fwResponse); } private static class virtualFilterChain mengimplementasikan filterchain {private final filterChain originalChain; Daftar Akhir Pribadi <TERFERTER> Tambahan; firewalledrequest firewalledRequest private final; Ukuran int final pribadi; private int currentposition = 0; Private VirtualFilterChain (FirewalledRequest FirewalledRequest, FilterChain Chain, List <TILER> TambahanFilters) {this.originalchain = rantai; this.additionalfilters = tambahanFilters; this.size = tambahanFilters.size (); this.fireWalledRequest = firewalledRequest; } public void dofilter (permintaan servletRequest, respons servletResponse) melempar ioException, servletException {if (currentPosition == ukuran) {if (logger.isdebugenabled () {logger.debug (urlutils.buildrequesturl (firewall rantai (firger) dari firger.debug (Urlutils.buildrequesturl (firger.deball (Urlutils.buildrequesturl () {logger. } // Nonaktifkan striping jalur saat kami keluar dari rantai filter keamanan this.fireWalledRequest.reset (); originalchain.dofilter (permintaan, respons); } else {currentPosition ++; Filter nextFilter = tambahanFilters.get (currentPosition - 1); if (logger.isdebugeNabled ()) {logger.debug (urlutils.buildrequesturl (firewalledRequest) + "pada posisi" + arus lancar + "dari" + ukuran + "dalam rantai filter tambahan; filter penembakan: '" + nextFilter.getClass (). GetSeMplase () () () () (); + " +" ") (get getsclass (). } nextFilter.dofilter (permintaan, respons, ini); }}}}merujuk ke
https://spring.io/guides/topicals/spring-security-architecture/
https://docs.spring.io/spring-security/site/docs/5.0.5.release/reference/htmlsingle/#overall-architecture
Meringkaskan
Di atas adalah seluruh konten artikel ini. Saya berharap konten artikel ini memiliki nilai referensi tertentu untuk studi atau pekerjaan semua orang. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi. Terima kasih atas dukungan Anda ke wulin.com.