Shiro adalah kerangka kontrol izin ringan dengan berbagai aplikasi. Fokus artikel ini adalah untuk memperkenalkan integrasi Shiro Spring dan untuk mengaktifkan parameter dinamis seperti @RequiresRoles yang akan didukung dengan memperluas ekspresi EL Spring. Pengantar Shiro tidak berada dalam ruang lingkup artikel ini. Jika pembaca tidak tahu banyak tentang Shiro, mereka dapat mempelajari informasi yang sesuai melalui situs web resminya. Ada juga artikel tentang InfoQ yang menyediakan pengantar komprehensif untuk Shiro, dan juga direkomendasikan secara resmi. Alamatnya adalah https://www.infoq.com/articles/apache-shiro.
Shiro mengintegrasikan musim semi
Pertama, Anda perlu menambahkan shiro-spring-xxx.jar ke proyek Anda. Jika Anda menggunakan Maven untuk mengelola proyek Anda, Anda dapat menambahkan dependensi berikut ke dependensi Anda. Berikut adalah versi 1.4.0 terbaru yang telah saya pilih.
<dependency> <GroupId> org.apache.shiro </groupid> <ArTifactId> Shiro-SPRING </artifactid> <version> 1.4.0 </version> </dependency>
Selanjutnya, Anda perlu mendefinisikan shirofilter di web.xml Anda dan menerapkannya untuk mencegat semua permintaan yang memerlukan kontrol izin, biasanya dikonfigurasi sebagai /*. Selain itu, filter perlu ditambahkan ke depan untuk memastikan bahwa permintaan pertama kali dikendalikan melalui izin Shiro setelah masuk. Kelas filter yang sesuai di sini dikonfigurasi dengan DelegatingFilterProxy, yang merupakan proxy filter yang disediakan oleh Spring. Anda dapat menggunakan kacang di wadah kacang pegas sebagai instance filter saat ini, dan kacang yang sesuai akan mengambil kacang yang sesuai dengan nama filter. Jadi konfigurasi berikut akan mencari kacang bernama shirofilter di wadah kacang.
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param></filter><filter-mapping> <filter-name> shirofilter </filter-name> <rerl-pattern>/*</rerl-pattern> </filter-mapping>
Saat menggunakan Shiro secara mandiri, Anda biasanya mendefinisikan org.apache.shiro.web.servlet.shirofilter untuk melakukan sesuatu yang serupa.
Berikutnya adalah mendefinisikan shirofilter kami di wadah kacang. Sebagai berikut, kami mendefinisikan shirofilterfactorybean, yang akan menghasilkan jenis kacang abstrak. Melalui ShirofilterFactoryBean kita dapat menentukan SecurityManager. DefaultWebSecurityManager yang digunakan di sini perlu menentukan ranah. Jika beberapa ranah perlu ditentukan, itu ditentukan melalui ranah. Untuk kesederhanaan, kami menggunakan TextConfigurationRealm berdasarkan definisi teks secara langsung. Gunakan LoginUrl untuk menentukan alamat login, SUCCESURL untuk menentukan alamat yang perlu dialihkan setelah login berhasil, dan tidak sah untuk menentukan halaman prompt ketika izin tidak cukup. FilterChainDefinitions mendefinisikan hubungan antara URL dan filter yang akan digunakan. Alias filter di sisi kanan tanda yang sama adalah alias filter. Alias default didefinisikan dalam kelas enumerasi org.apache.shiro.web.filter.mgt.defaultfilter.
<bean id = "shirofilter"> <name properti = "SecurityManager" ref = "SecurityManager"/> <name properti = "loginurl" value = "/login.jsp"/> <name properti = "value" value = "/home.jsp"/> <nama properti = "tidak disukai" nexhorizizedUrl "value ="/home.jsp "/> <Property name =" unrorityurl "value =" tidak ada " nilai = "/tidak sah Ref = "REALM"/> </ bean> <bean id = "lifecyclebeanpostprocessor"/>
<!-Untuk kesederhanaan, kami akan menggunakan implementasi ranah berbasis teks di sini-> <bean id = "realm"> <name properti = "UserDefinitions"> <value> user1 = pass1, role1, role2 user2 = pass2, role2, role3 admin = admin, admin </value> </prively> </ bean>
Jika Anda perlu menggunakan filter khusus dalam definisi filterChainDefinitions, Anda dapat menentukan filter khusus dan hubungan pemetaan aliasnya melalui filter shirofilterfactorybean. Misalnya, seperti yang ditunjukkan di bawah ini, kami telah menambahkan filter dengan logger alias, dan filter yang ditentukan /** dengan alias logger di filterchainDefinitions.
<bean id = "shirofilter"> <name properti = "SecurityManager" ref = "SecurityManager"/> <name properti = "loginurl" value = "/login.jsp"/> <name properti = "value" value = "/home.jsp"/> <nama properti = "tidak disukai" nexhorizizedUrl "value ="/home.jsp "/> <Property name =" unrorityurl "value =" tidak ada " value = "/unafhorized.jsp"/> <properti name = "filter"> <util: peta> <entry tey = "logger"> <bean/> </dert> </tuat: peta> </prively> <properti name = "filterchainDefinitions"> <value>/admin/** have # authc, authc, role/logout = log. AUTHC, LOGGER </ value> </pruptent> </tean>
Faktanya, definisi filter alias yang perlu kita terapkan juga dapat didefinisikan secara langsung oleh setFilters () ShirofilterFactoryBean (), tetapi secara langsung menentukan filter yang sesuai dengan kacang yang sesuai dalam wadah kacang yang sesuai. Karena secara default, ShirofilterFactoryBean akan mendaftarkan semua jenis kacang filter dalam wadah kacang dengan alias ID mereka dalam filter. Oleh karena itu, definisi di atas setara dengan yang berikut.
<bean id = "shirofilter"> <name properti = "SecurityManager" ref = "SecurityManager"/> <name properti = "loginurl" value = "/login.jsp"/> <name properti = "value" value = "/home.jsp"/> <nama properti = "tidak disukai" nexhorizizedUrl "value ="/home.jsp "/> <Property name =" unrorityurl "value =" tidak ada " value = "/tidak resmi.jsp"/> <name properti = "filterChainDefinitions"> <value>/admin/** = authc, peran [admin]/logout = logout # alamat lain mengharuskan pengguna telah masuk/** = authc, logger </value> </properti> </bean> <bean id id = "
Setelah langkah -langkah di atas, integrasi Shiro dan Spring selesai. Pada saat ini, jalur apa pun yang kami minta untuk proyek akan mengharuskan kami untuk masuk, dan secara otomatis akan melompat ke jalur yang ditentukan oleh loginurl dan membiarkan kami memasukkan nama pengguna/kata sandi untuk masuk. Pada saat ini, kami harus memberikan formulir untuk mendapatkan nama pengguna melalui nama pengguna, kata sandi, dan kemudian ketika mengirimkan permintaan login, permintaan yang diperlukan untuk disampaikan ke pidato. Nama pengguna/kata sandi yang digunakan saat masuk adalah nama pengguna/kata sandi yang kami tentukan dalam TextConfigurationRealm. Berdasarkan konfigurasi kami di atas, Anda dapat menggunakan User1/Pass1, Admin/Admin, dll. Setelah masuk dengan berhasil, itu akan melompat ke alamat yang ditentukan oleh parameter SuccucURL. Jika kami masuk menggunakan USER1/PASS1, kami juga dapat mencoba untuk mengakses/admin/indeks, dan pada saat ini kami akan melompat ke tidak sah.jsp karena izin yang tidak memadai.
Aktifkan dukungan berbasis anotasi
Integrasi dasar mengharuskan kita untuk mendefinisikan semua kontrol izin yang perlu diterapkan URL dalam definisi filterchaind dari shirofilterfactorybean. Terkadang ini tidak begitu fleksibel. Shiro memberi kita anotasi yang dapat digunakan setelah mengintegrasikan musim semi. Ini memungkinkan kami untuk menambahkan anotasi yang sesuai ke kelas atau metode yang memerlukan kontrol izin untuk menentukan izin yang diperlukan untuk mengakses kelas atau metode. Jika ada di kelas dalam definisi, itu berarti bahwa memanggil semua metode di kelas memerlukan izin yang sesuai (perhatikan bahwa itu perlu panggilan eksternal, yang merupakan batasan proxy dinamis). Untuk menggunakan anotasi ini, kita perlu menambahkan dua definisi kacang berikut ke wadah kacang Spring, sehingga kita dapat menentukan apakah pengguna memiliki izin yang sesuai berdasarkan definisi anotasi saat runtime. Ini dicapai melalui mekanisme AOP Spring. Jika Anda tidak tahu apa -apa tentang Spring AOP, Anda dapat merujuk ke "Spring AOP Pendahuluan Kolom" penulis yang ditulis oleh penulis. Dua definisi kacang berikut, OtorisasiTributeSourceadVisor mendefinisikan penasihat, yang akan mencegat dan memverifikasi izin berdasarkan metode konfigurasi anotasi yang disediakan oleh Shiro. DefaultAdvisorAutOproxyCreator menyediakan fungsi membuat objek proxy untuk kelas yang ditandai dengan anotasi kontrol izin yang disediakan oleh Shiro, dan menerapkan otorisasi yang sedang beralasan sumber daya saat mencegat panggilan metode target. Ketika permintaan dari pengguna dicegat dan pengguna tidak memiliki izin yang ditandai pada metode atau kelas yang sesuai, org.apache.shiro.authz.AuthorizationException Exception akan dilemparkan.
<bean depends-on = "lifecycleBeanPostProcessor"/> <bean> <name properti = "SecurityManager" ref = "SecurityManager" // </bean>
Jika <aop:config/>或<aop:aspectj-autoproxy/> sudah didefinisikan dalam wadah kacang kami, maka defaultAdvisorAutoproxycreator tidak dapat lagi didefinisikan. Karena dua kasus sebelumnya akan secara otomatis menambahkan kacang yang mirip dengan defaultAdvisorAutoproxycreator. Untuk informasi lebih lanjut tentang DefaultAdvisorAutoproxyCreator, Anda juga dapat merujuk pada prinsip penulis secara otomatis membuat objek proxy di Spring AOP.
Anotasi kontrol izin yang disediakan oleh Shiro adalah sebagai berikut:
Perlu aNtionTentication: Pengguna perlu diautentikasi dalam sesi saat ini, yaitu, dia perlu masuk dengan nama pengguna/kata sandi, dan tidak termasuk mengingat login otomatis.
Persyaratan: Pengguna harus diautentikasi. Ini dapat diautentikasi dengan masuk dengan nama pengguna/kata sandi di sesi ini, atau dapat secara otomatis masuk dengan RememberMe.
Persyaratan Guest: Pengguna tidak masuk.
Persyaratan: Pengguna mengharuskan peran yang ditentukan dimiliki.
Membutuhkan PMS: Pengguna membutuhkan izin yang ditentukan.
Tiga yang pertama mudah dimengerti, sedangkan dua yang terakhir serupa. Di sini saya menggunakan @Requirespermissions sebagai contoh. Pertama, mari kita ubah ranah yang ditentukan di atas dan tambahkan izin ke peran. Dengan cara ini, User1 kami akan memiliki izin ke Perm1, Perm2 dan Perm3, dan User2 akan memiliki izin ke Perm1, Perm3 dan Perm4.
<bean id = "ranah"> <name properti = "UserDefinitions"> <value> user1 = pass1, role1, role2 user2 = pass2, role2, role3 admin = admin, admin </value> </preate> <property name = "roledefinitions"> <value> role1 = Perm1, Perm2 Role2 = "Perm2 Rolean> <value </value/Perm1, Perm2 Role/Perm2 PERM2 ROMP3 = </PERM2 ROMP3, PERM3, PERM3, PERM3, PERM3, PERM2 ROLP3, PERM2, PERP3, PERP3, PERBER4, PERP3, PERBER4/PERM2 PERPAN.
@Requirespermissions dapat ditambahkan pada metode untuk menentukan izin yang perlu diklaim saat memanggil metode tersebut. Dalam kode berikut, kami menentukan bahwa izin Perm1 harus dimiliki saat mengakses /Perm1. Saat ini, baik User1 dan User2 dapat diakses.
@RequestMapping ("/Perm1")@membutuhkanmissions ("Perm1") Izin Objek Publik1 () {return "Perm1";}Jika Anda perlu menentukan bahwa Anda harus memiliki beberapa izin pada saat yang sama untuk mengakses metode, Anda dapat menentukan izin yang perlu Anda tentukan dalam bentuk array (ketika menentukan satu atribut array pada anotasi, Anda tidak dapat menambahkan kawat gigi, tetapi ketika Anda perlu menentukan beberapa izin, diperlukan kawat gigi). Misalnya, sebagai berikut, kami menentukan bahwa ketika mengakses /Perm1Andperm4, pengguna harus memiliki izin Perm1 dan Perm4. Pada saat ini, hanya User2 yang dapat mengaksesnya, karena hanya yang memiliki Perm1 dan Perm4 pada saat yang sama.
@RequestMapping ("/perm1Andperm4")@membutuhkanmissions ({"perm1", "perm4"}) objek publik perm1andperm4 () {return "perm1Andperm4";}Ketika beberapa izin ditentukan pada saat yang sama, hubungan antara beberapa izin adalah hubungan, yaitu, semua izin yang ditentukan pada saat yang sama diperlukan. Jika Anda hanya perlu memiliki salah satu izin yang ditentukan untuk dapat diakses, kami dapat menentukan hubungan antara atau antara beberapa izin melalui logis = logis.atau. Misalnya, sebagai berikut, kami menentukan bahwa ketika mengakses /perm1orperm4, Anda hanya perlu memiliki izin Perm1 atau Perm4, sehingga User1 dan User2 dapat mengakses metode ini.
@RequestMapping ("/Perm1orperm4")@membutuhkanmissions (value = {"perm1", "perm4"}, logical = logical.or) objek publik perm1orperm4 () {return "perm1orperm4";}@Requirespermissions juga dapat ditandai di kelas, menunjukkan bahwa ketika mengakses metode di kelas secara eksternal, Anda harus memiliki izin yang sesuai. Misalnya, sebagai berikut, kami menentukan bahwa kami perlu memiliki izin Perm2 di tingkat kelas, sedangkan metode index () tidak menentukan bahwa kami memerlukan izin apa pun, tetapi kami masih perlu memiliki izin yang ditentukan pada tingkat kelas ketika mengakses metode ini. Saat ini, hanya User1 yang dapat mengaksesnya.
@Restcontroller@requestMapping ("/foo")@membutuhkanpermissions ("perm2") kelas publik foocontroller {@RequestMapping (method = requestMethod.get) indeks objek publik () {peta <string, object> peta = new HashMap <> (); peta.put ("ABC", 123); peta mengembalikan; }}Ketika tingkat kelas dan metode memiliki @Requirespermissions, level metode memiliki prioritas yang lebih tinggi, dan hanya izin yang diperlukan oleh level metode yang akan diverifikasi saat ini. Sebagai berikut, kami menentukan bahwa izin Perm2 diperlukan di tingkat kelas dan izin Perm3 diperlukan pada tingkat metode. Kemudian saat mengakses /foo, Anda hanya perlu memiliki izin Perm3 untuk mengakses metode index (). Jadi saat ini pengguna1 dan user2 dapat mengakses /foo.
@Restcontroller @requestMapping ("/foo") @membutuhkanpermissions ("perm2") kelas publik foocontroller {@RequestMapping (Method = requestMethod.get) @Requirespermissions ("Perm3") Public Object Index () {MAP <String, Object> MAP = HASHMAPAP <> (); peta.put ("ABC", 123); peta mengembalikan; }}Namun, jika kita menambahkan @RequiresRoles ("Role1") ke kelas pada saat ini untuk menentukan bahwa kita perlu memiliki peran peran1, maka ketika mengakses /foo, kita perlu memiliki peran1 yang ditentukan oleh @Requirespermissions ("Perm3") pada peran1 pada metode indeks () pada kelas. Karena memerlukan penjelasan dan wajib militer termasuk dalam definisi izin dari dimensi yang berbeda, Shiro akan memeriksanya sekali selama verifikasi, tetapi jika kedua kelas dan metode memiliki anotasi dari jenis definisi kontrol izin yang sama, definisi dalam metode ini hanya akan didasarkan pada definisi.
@Restcontroller@requestMapping ("/foo")@membutuhkanpermissions ("perm2")@wajibRoles ("role1") kelas publik foocontroller {@RequestMapping (Method = requestMethod.get) @Requirespermissions ("Perm3") Public Object Index () {String, objek> MAP = MAP = "Perm3") (PEMP3); peta.put ("ABC", 123); peta mengembalikan; }}Meskipun contoh hanya menggunakan membutuhkan informasi, penggunaan anotasi kontrol izin lainnya juga serupa. Harap gunakan anotasi lain dengan teman yang tertarik.
Prinsip Izin Kontrol Anotasi
Izin yang kami tentukan di atas statis menggunakan @Requirespermissions. Salah satu tujuan utama artikel ini adalah untuk memperkenalkan metode untuk membuat izin yang ditentukan dinamis dengan memperluas implementasi. Tetapi sebelum kita berkembang, kita harus tahu cara kerjanya, yaitu, prinsip implementasi, sebelum kita dapat berkembang. Jadi mari kita lihat bagaimana Shiro mengintegrasikan musim semi dengan @Requirespermissions. Saat mengaktifkan dukungan untuk @Requirespermissions, kami mendefinisikan kacang berikut, yang merupakan penasihat, yang diwarisi dari StaticMethodMatcherPointCutAdvisor. Logika pencocokan metode adalah bahwa selama kelas atau metode memiliki beberapa anotasi kontrol izin Shiro, logika pemrosesan setelah intersepsi ditentukan oleh saran yang sesuai.
<Bean> <name properti = "SecurityManager" ref = "SecurityManager"/> </ bean>
Berikut ini adalah kode sumber OtorisasiTributeSourceadVisor. Kita dapat melihat bahwa dalam metode konstruktornya, aopallianceanNotationsAuthorizingMethodInterceptor, ditentukan oleh setadvice (), yang didasarkan pada implementasi MethodInterceptor.
Public Class AuthorizationTributSourCeadVisor memperluas StaticMethoDMatcherPointCutAdvisor {private static final Logger Log = loggerFactory.getLogger (otorizationAttributSourCeadVisor.class); Kelas Akhir Statis Pribadi <? Extends Annotation> [] authz_annotation_classses = kelas baru [] {membutuhkanpermissions.class, needroles.class, neededUser.class, nestedguest.class, wajib diperlukan.class}; SecurityManager SecurityManager = null; Otorisasi PublikTributsourceadVisor () {setAdvice (aopallianceanNotationsAuthorizingMethodInterCeptor ()) baru; } Public SecurityManager GetSecurityManager () {return SecurityManager; } public void setSecurityManager (org.apache.shiro.mgt.securityManager SecurityManager) {this.securityManager = SecurityManager; } kecocokan boolean publik (metode metode, kelas target class) {metode m = metode; if (isAuthzannotationPresent (m)) {return true; } // Parameter 'Metode' bisa dari antarmuka yang tidak memiliki anotasi. // Periksa untuk melihat apakah implementasinya memilikinya. if (targetClass! = null) {coba {m = targetclass.getMethod (m.getName (), m.getParametertypes ()); return isauthzannotationpresent (m) || IsauthzannotationPresent (TargetClass); } catch (NosuchMethodeException diabaikan) {// Nilai pengembalian default salah. Jika kita tidak dapat menemukan metode ini, maka jelas // tidak ada anotasi, jadi cukup gunakan nilai pengembalian default. }} return false; } private boolean isauthzannotationpresent (class <?> targetclazz) {for (class <? extends annotation> annclass: authz_annotation_classes) {annotation a = annotationutils.findannotation (targetclasz, annclass); if (a! = null) {return true; }} return false; } private boolean isauthzannotationpresent (metode metode) {for (class <? extends annotation> annclass: authz_annotation_classes) {annotation a = annotationutils.findannotation (metode, annclass); if (a! = null) {return true; }} return false; }}Kode sumber AopallianceanNotationsAuthorizingMethodInterceptor adalah sebagai berikut. Metode Invoke dari antarmuka MethodInterceptor yang diimplementasikan di dalamnya memanggil metode Invoke dari kelas induk. Pada saat yang sama, kita perlu melihat bahwa beberapa implementasi OtoringAnnotationMethodInterceptor telah dibuat dalam metode konstruktornya. Implementasi ini adalah inti dari mengimplementasikan kontrol izin. Kemudian, kami akan memilih kelas implementasi IzinAnnotationMethOdinterceptor untuk melihat logika implementasinya yang spesifik.
kelas publik aopallianceannotationsAuthorizingMethodInterceptor memperluas annotationshorizingMethodInterceptor mengimplementasikan metode interceptor {public aopallianceanNotationsAuthorizingMethodInterceptor () {list <otorizingAnnoThodInterceptor> Ininterceptors = new Arraylist <norizingAnnotationMethodInceptor> Interceptors = new Arraylist <ororingAnnoNeNeNoTationSeptor> Interceptorsceptors = new Arraylist <ororizingAnnoNeNoNTECTORCECTORTECTOR> INTERNOTECTECORTECORTECTOR = ARRAYLISTREnEnnOncorTECTORTECORTECORTERCORTECTOR () // Gunakan resolver anotasi spesifik -pegas - Annotationutils Spring lebih bagus daripada // proses resolusi JDK mentah. AnnotationResolver resolver = springannotationResolver baru (); // kita dapat menggunakan kembali instance resolver yang sama - tidak mempertahankan status: interceptors.add (roleannotationMethodInterceptor (resolver)); Interceptors.Add (izin baruNotationMethodInterceptor (resolver)); interceptors.add (UserAnnotationMethodInterceptor baru (resolver)); Interceptors.Add (GuestAnnotationMethodInterceptor baru (resolver)); SetMethodInterceptors (Interceptors); } lindung org.apache.shiro.aop.methodinVocation createMethodinVocation (objek ImplSificMethodinVocation) {MethodInVocation mi = (MethodInvocation) ImplSificMethodinVocation; return org.apache.shiro.aop.methodinVocation () {Metode publik getMethod () {return mi.getMethod (); } objek publik [] getArguments () {return mi.getArguments (); } public string toString () {return "Method Invocation [" + mi.getMethod () + "]"; } Objek Public Proceed () melempar lempar {return mi.proed (); } objek publik getThis () {return mi.getThis (); }}; } Objek yang Diprotes Lanjutkaninvokasi (objek aopalliancemethodinVocation) melempar lempar {MethodInvocation mi = (MethodInvocation) aopalliancemethodinVocation; return mi.proed (); } Invoke Objek Publik (MethodInvocation MethodInVocation) melempar Throwable {org.apache.shiro.aop.methodinVocation mi = createMethodinVocation (MethodInvocation); return super.invoke (mi); }}Dengan melihat implementasi metode Invoke dari kelas induk, kami akhirnya akan melihat bahwa logika inti adalah untuk memanggil metode yang diasingkan, dan implementasi metode ini (kode sumber adalah sebagai berikut) adalah untuk menentukan apakah otorisasi yang dikonfigurasi yang dikonfigurasi dengan metode yang didukung pada kelas atau verifikasi izin dari metode saat ini). Ketika didukung, metode assertauthorized -nya akan dipanggil untuk verifikasi izin, dan otorisasiNoNotationMethodInterceptor pada gilirannya akan memanggil metode assertauthorized dari otorisasiNotationHandler.
void yang dilindungi assertAuthorized (MethodInvocation MethodInvocation) melempar otorisasiException {// implementasi default hanya memastikan tidak dapat disangkal suara dilemparkan: koleksi <otorisasiNotationMethodInterceptor> aamis = getMethodInterceptors (); if (aamis! = null &&! aamis.isempty ()) {for (otorisasiNotationMethodInterceptor aAMI: aamis) {if (aami.supports (MethodInvocation)) {aami.assertaTorized (MethodInvocation); }}}}Selanjutnya, mari kita lihat kembali pada izinNoTationMethodInterceptor yang ditentukan oleh aopallianceannotationsAuthorizingMethodInterceptor, kode sumbernya adalah sebagai berikut. Menggabungkan kode sumber AopallianceannotationsAuthorizingMethodInterceptor dan kode sumber izinnotationMethodInterceptor, kita dapat melihat bahwa izinnotationHandler dan springannotationResolver ditentukan dalam izinNotationMethodInterceptor. IzinnotationHandler adalah subclass dari OtorisingAnnotationHandler. Jadi kontrol izin akhir kami ditentukan oleh implementasi yang tidak sah dari izinNotationHandler.
Public Class IzinAnnotationMethodInterceptor memperluas otorisasiNoNotationMethodInterceptor {Public IzinAnnotationMethodInterceptor () {super (new jikaNanNotationHandler ()); } Public IzinAnnotationMethodInterceptor (AnnotationResolver resolver) {super (Izin baruNoNotationHandler (), resolver); }}Selanjutnya, mari kita lihat metode assertAuthorized Implementasi izinnotationHandler, dan kode lengkapnya adalah sebagai berikut. Dari implementasi kita dapat melihat bahwa itu akan memperoleh nilai izin yang dikonfigurasi dari anotasi, dan anotasi di sini adalah anotasi yang memerlukan informasi. Selain itu, saat melakukan verifikasi izin, kami langsung menggunakan nilai teks yang ditentukan saat mendefinisikan anotasi. Kami akan mulai dari sini ketika kami mengembangkannya nanti.
Public Class IzinAnnotationHandler memperluas otorisasiNotationHandler {Public ImpissionAnnotationHandler () {super (membutuhkanpermissions.class); } string yang dilindungi [] getAnnotationValue (Annotation a) {membutuhkanpermisi rpannotation = (memerlukan misi) a; return rpannotation.Value (); } public void assertAuthorized (annotation a) melempar otorizationException {if (! (Contohnya membutuhkan informasi)) kembali; Membutuhkan informasi rpannotation = (membutuhkan informasi) a; String [] perms = getAnnotationValue (a); Subjek subjek = getSubject (); if (perms.length == 1) {subjek.CheckPerMission (perms [0]); kembali; } if (logical.and.equals (rpannotation.logical ())) {getSubject (). CheckPerMissions (perms); kembali; } if (logical.or.equals (rpannotation.Logical ())) {// hindari pemrosesan pengecualian tidak perlu - "tunda" melempar pengecualian dengan memanggil hasrole pertama boolean hasatleastonePermission = false; untuk (izin string: perms) if (getSubject (). ispermitted (izin)) hasatleastonepermission = true; // Menyebabkan pengecualian jika tidak ada peran yang cocok, perhatikan bahwa pesan pengecualian akan sedikit menyesatkan jika (! HasatleastOnePermission) getSubject (). CheckPerMission (perms [0]); }}}Melalui pengantar sebelumnya, kita tahu bahwa anotasi parameter metode assertAuthorized dari izinnotationHandler disahkan oleh otorisasiNotationMethodInterceptor ketika menyebut metode yang diasingkan dari otorisasiNotationHandler. Kode sumber adalah sebagai berikut. Dari kode sumber, kita dapat melihat bahwa anotasi diperoleh melalui metode getNotation.
public void assertAuthorized (MethodInvocation mi) melempar otorisasiException {try {((otorisasiNotationHandler) getHandler ()). AssertAuthorized (getAnnotation (MI)); } catch (otorizationException ae) {if (ae.getCause () == null) ae.initcause (Otorisasi baru ("tidak diizinkan untuk meminta metode:" + mi.getMethod ())); lempar ae; }}Berjalan di sepanjang arah ini, kami pada akhirnya akan menemukan implementasi metode GetAnnotation dari SpringAnnotationResolver, yang diimplementasikan sebagai berikut. Seperti yang dapat dilihat dari kode berikut, lebih disukai untuk mencari metode saat mencari anotasi. Jika tidak ditemukan pada metode ini, ia akan mencari anotasi yang sesuai dari kelas panggilan metode saat ini. Dari sini, kita juga dapat melihat mengapa salah satu yang mulai berlaku pada metode ketika kita mendefinisikan jenis anotasi kontrol izin yang sama pada kelas dan metode sebelumnya, dan ketika ada sendiri, salah satu yang didefinisikan berlaku.
Kelas Publik SpringAnnotationResolver mengimplementasikan anotationResolver {annotasi publik getannotation (MethodInvocation MI, kelas <? Extends Annotation> clazz) {Method m = mi.getMethod (); Anotasi a = annotationutils.findannotation (m, clazz); if (a! = null) mengembalikan a; // Objek Metode MethodInvocation bisa menjadi metode yang didefinisikan dalam antarmuka. // Namun, jika anotasi ada dalam implementasi antarmuka (dan bukan // antarmuka itu sendiri), itu tidak akan berada pada objek metode di atas. Sebaliknya, kita perlu // memperoleh representasi metode dari TargetClass dan memeriksa langsung pada // implementasi itu sendiri: kelas <?> TargetClass = mi.getThis (). GetClass (); m = classutils.getspecificmethod (M, TargetClass); a = annotationutils.findannotation (m, clazz); if (a! = null) mengembalikan a; // Lihat apakah kelas memiliki anotasi yang sama mengembalikan annotationutils.findannotation (mi.getThis (). GetClass (), clazz); }} Melalui pembacaan kode sumber di atas, saya percaya bahwa pembaca memiliki pemahaman yang lebih dalam tentang prinsip anotasi kontrol izin yang didukung oleh Shiro setelah mengintegrasikan musim semi. Kode sumber yang diposting di atas hanyalah beberapa inti yang menurut penulis relatif inti. Jika Anda ingin mengetahui konten lengkap secara detail, silakan baca kode lengkap sendiri di sepanjang ide yang disebutkan oleh penulis.
Setelah memahami prinsip pengendalian izin ini berdasarkan anotasi, pembaca juga dapat berkembang sesuai dengan kebutuhan bisnis yang sebenarnya.
Diperpanjang menggunakan ekspresi spring el
Misalkan sekarang ada antarmuka seperti berikut ini, yang memiliki metode kueri yang menerima jenis parameter. Mari kita sederhanakan di sini, dengan asumsi bahwa selama parameter seperti itu diterima dan nilai yang berbeda akan dikembalikan.
antarmuka publik realService {objek kueri (tipe int); }Antarmuka ini terbuka untuk dunia luar. Metode ini dapat diminta melalui URL yang sesuai. Kami mendefinisikan metode pengontrol yang sesuai sebagai berikut:
@RequestMapping ("/service/{type}") kueri objek publik (@pathvariable ("type") int type) {return this.realservice.query (type);}Layanan antarmuka di atas memiliki izin untuk jenis saat melakukan kueri. Tidak setiap pengguna dapat menggunakan masing -masing jenis untuk meminta, dan memerlukan izin yang sesuai. Oleh karena itu, untuk metode prosesor di atas, kita perlu menambahkan kontrol izin, dan izin yang diperlukan selama kontrol perubahan secara dinamis dengan jenis parameter. Misalkan definisi dari setiap izin tipe adalah bentuk kueri: ketik. Misalnya, izin yang diperlukan saat tipe = 1 adalah kueri: 1, dan izin yang diperlukan saat tipe = 2 adalah kueri: 2. Ketika tidak terintegrasi dengan musim semi, kami melakukan ini sebagai berikut:
@RequestMapping ("/service/{type}") kueri objek publik (@pathvariable ("type") int type) {SecurityUtils.getSubject (). CheckPerMission ("Query:" + type); kembalikan this.realservice.query (type);}Namun, setelah integrasi dengan pegas, praktik -praktik di atas sangat digabungkan, dan kami lebih suka menggunakan anotasi terintegrasi untuk mengendalikan izin. Untuk skenario di atas, kami lebih suka menentukan izin yang diperlukan melalui @Requirespermissions, tetapi izin yang ditentukan dalam @Requirespermissions adalah teks statis dan diperbaiki. Itu tidak dapat memenuhi kebutuhan dinamis kita. Pada saat ini, Anda mungkin berpikir bahwa kami dapat membagi metode pemrosesan pengontrol menjadi banyak dan mengontrol izin secara terpisah. Misalnya, berikut ini adalah:
@RequestMapping ("/service/1")@membutuhkan misi ("kueri: 1") layanan objek publik1 () {return this.realservice.query (1);}@wajib diperlukanmisi ("kueri: 2")@requestMapping ("/service/2") Layanan Objek Publik2 () {Query: 2 ")@ this.realservice.query (2);} //...@ requestMapping ("/service/200")@membutuhkan misi ("kueri: 200") layanan objek publik200 () {return this.realservice.query (200);}Ini OK ketika kisaran nilai jenis relatif kecil, tetapi jika ada 200 nilai yang mungkin seperti di atas, itu akan sedikit merepotkan untuk menyebutkannya secara mendalam untuk menentukan metode prosesor yang terpisah dan melakukan kontrol izin. Selain itu, jika nilai perubahan jenis di masa depan, kita harus menambahkan metode prosesor baru. Jadi cara terbaik adalah membuat @Requirespermissions mendukung definisi izin dinamis, sambil mempertahankan dukungan definisi statis. Melalui analisis sebelumnya, kita tahu bahwa titik masuknya adalah izinnotationHandler, dan itu tidak memberikan ekstensi untuk verifikasi izin. Jika kita ingin memperluasnya, cara sederhana adalah menggantinya secara keseluruhan. Namun, izin yang kita butuhkan untuk memproses secara dinamis terkait dengan parameter metode, dan parameter metode tidak dapat diperoleh dalam Handller Izin. Untuk alasan ini, kami tidak dapat secara langsung mengganti izinnotationHandler. IzinnotationHandler dipanggil dengan izinnotationMethodInterceptor. Parameter metode dapat diperoleh ketika izinnotationHandler dipanggil dalam metode assertauthorized dari kelas induknya otorisasiNoNotationMethodInterceptor. Untuk alasan ini, titik ekstensi kami dipilih pada kelas yang diizinkan. Ekspresi EL Spring dapat mendukung nilai parameter metode parsing. Di sini kami memilih untuk memperkenalkan Spring's EL Expressions. Ketika @Requirespermissions menentukan izin, Anda dapat menggunakan ekspresi spring el untuk memperkenalkan parameter metode. Pada saat yang sama, untuk memperhitungkan teks statis. Ini adalah templat ekspresi spring el. Untuk template ekspresi EL Spring, silakan merujuk ke posting blog ini. Kami mendefinisikan izin kami sendiri MethodInterceptor, mewarisinya dari IzinAnnotationMethodInterceptor, dan mengesampingkan metode assertauthoried. Logika implementasi dari metode ini mengacu pada logika di IzinAnnotationHandler, tetapi definisi izin dalam @Requirespermissions yang digunakan adalah hasil dari penggunaan ekspresi Spring El berdasarkan metode yang saat ini disebut sebagai hasil parsing evaluasi konteks. Berikut ini adalah definisi kami sendiri tentang implementasi IzinAnnotationMethodInterceptor.
kelas publik selfpermissionnotationMethodInterceptor memperluas izinAnnotationMethodInterceptor {private final spelexpressionParser parser = new spelexpressionParser (); ParameterNamedIscoverer Private ParamNamedIscoverer = New DefaultParameterNamedIscoverer (); private final templateParsercontext templateParserContext = new TemplateParserContext (); public SelfPermissionAnnotationMethodInterceptor(AnnotationResolver resolver) { super(resolver); } @Override public void assertAuthorized(MethodInvocation mi) throws AuthorizationException { Annotation annotation = super.getAnnotation(mi); RequiresPermissions permAnnotation = (RequiresPermissions) annotation; String[] perms = permAnnotation.value(); EvaluationContext evaluationContext = new MethodBasedEvaluationContext(null, mi.getMethod(), mi.getArguments(), paramNameDiscoverer); for (int i=0; i<perms.length; i++) { Expression expression = this.parser.parseExpression(perms[i], templateParserContext); //Replace the original permission definition with the permission definition parsed by Spring EL expression perms[i] = expression.getValue(evaluationContext, String.class); } Subject subject = getSubject(); if (perms.length == 1) { subject.checkPermission(perms[0]); kembali; } if (Logical.AND.equals(permAnnotation.logical())) { getSubject().checkPermissions(perms); kembali; } if (Logical.OR.equals(permAnnotation.logical())) { // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first boolean hasAtLeastOnePermission = false; for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true; // Cause the exception if none of the role match, note that the exception message will be a bit misleading if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]); }}}定义了自己的PermissionAnnotationMethodInterceptor后,我们需要替换原来的PermissionAnnotationMethodInterceptor为我们自己的PermissionAnnotationMethodInterceptor。根据前面介绍的Shiro整合Spring后使用@RequiresPermissions等注解的原理我们知道PermissionAnnotationMethodInterceptor是由AopAllianceAnnotationsAuthorizingMethodInterceptor指定的,而后者又是由AuthorizationAttributeSourceAdvisor指定的。为此我们需要在定义AuthorizationAttributeSourceAdvisor时通过显示定义AopAllianceAnnotationsAuthorizingMethodInterceptor的方式显示的定义其中的AuthorizingAnnotationMethodInterceptor,然后把自带的PermissionAnnotationMethodInterceptor替换为我们自定义的SelfAuthorizingAnnotationMethodInterceptor。替换后的定义如下:
<bean> <property name="securityManager" ref="securityManager"/> <property name="advice"> <bean> <property name="methodInterceptors"> <util:list> <bean c:resolver-ref="springAnnotationResolver"/> <!-- 使用自定义的PermissionAnnotationMethodInterceptor --> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> </util:list> </property> </bean> </property></bean><bean id="springAnnotationResolver"/>
为了演示前面示例的动态的权限,我们把角色与权限的关系调整如下,让role1、role2和role3分别拥有query:1、query:2和query:3的权限。此时user1将拥有query:1和query:2的权限。
<bean id="realm"> <property name="userDefinitions"> <value> user1=pass1,role1,role2 user2=pass2,role2,role3 admin=admin,admin </value> </property> <property name="roleDefinitions"> <value> role1=perm1,perm2,query:1 role2=perm1,perm3,query:2 role3=perm3,perm4,query:3 </value> </property></bean>
此时@RequiresPermissions中指定权限时就可以使用Spring EL表达式支持的语法了。因为我们在定义SelfPermissionAnnotationMethodInterceptor时已经指定了应用基于模板的表达式解析,此时权限中定义的文本都将作为文本解析,动态的部分默认需要使用#{前缀和}后缀包起来(这个前缀和后缀是可以指定的,但是默认就好)。在动态部分中可以使用#前缀引用变量,基于方法的表达式解析中可以使用参数名或p参数索引的形式引用方法参数。所以上面我们需要动态的权限的query方法的@RequiresPermissions定义如下。
@RequestMapping("/service/{type}")@RequiresPermissions("query:#{#type}")public Object query(@PathVariable("type") int type) { return this.realService.query(type);}这样user1在访问/service/1和/service/2是OK的,但是在访问/service/3和/service/300时会提示没有权限,因为user1没有query:3和query:300的权限。
Meringkaskan
The above is a detailed explanation of Spring integrating Shiro and expanding the use of EL expressions introduced by the editor. Saya harap ini akan membantu semua orang. Jika Anda memiliki pertanyaan, silakan tinggalkan saya pesan dan editor akan membalas Anda tepat waktu. Terima kasih banyak atas dukungan Anda ke situs web Wulin.com!