Spring Boot 集成Shiro的多realm實(shí)現(xiàn)以及shiro基本入門教程
情景
我的項(xiàng)目中有六個(gè)用戶角色(學(xué)校管理員,學(xué)生等),需要進(jìn)行分別登陸。如果在一個(gè)realm中,對(duì)controller封裝好的Token進(jìn)行Service驗(yàn)證,需要在此realm中注入六個(gè)數(shù)據(jù)庫(kù)操作對(duì)象,然后寫一堆if語(yǔ)句來(lái)判斷應(yīng)該使用那個(gè)Service服務(wù),然后再在驗(yàn)證方法(doGetAuthorizationInfo)中寫一堆if來(lái)進(jìn)行分別授權(quán),這樣寫不僅會(huì)讓代碼可讀性會(huì)非常低而且很難后期維護(hù)修改(剛寫完的時(shí)候只有上帝和你能看懂你寫的是什么,一個(gè)月之后你寫的是什么就只有上帝能看懂了)。
所以一定要配置多個(gè)realm來(lái)分別進(jìn)行認(rèn)證授權(quán)操作。shiro有對(duì)多個(gè)realm的處理,當(dāng)配置了多個(gè)Realm時(shí),shiro會(huì)用自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator類的doAuthenticate方法來(lái)進(jìn)行realm判斷,源碼:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { assertRealmsConfigured(); Collection<Realm> realms = getRealms(); if (realms.size() == 1) { return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken); } else { return doMultiRealmAuthentication(realms, authenticationToken); } }
assertRealmsConfigured();的作用是驗(yàn)證realm列表是否為空,如果一個(gè)realm也沒有則會(huì)拋出IllegalStateException異常(爆紅:Configuration error: No realms have been configured! One or more realms must be present to execute an authentication attempt.)
當(dāng)realm只有一個(gè)時(shí)直接返回,當(dāng)realm有多個(gè)時(shí)返回所有的realm。而我們要做的就是寫多個(gè)realm后重寫ModularRealmAuthenticator下的doAuthenticate方法,使它能滿足我們的項(xiàng)目需求。
那么改怎么重寫ModularRealmAuthenticator下的doAuthenticate方法,使它能滿足我們的項(xiàng)目需求呢?這就需要分析我們使用shiro的使用方法了。
shiro的使用
1.Controller層中,獲取當(dāng)前用戶后將用戶名和密碼封裝UsernamePasswordToken對(duì)象,然后調(diào)用Subject中的登陸方法subject.login(UsernamePasswordToken)
@RequestMapping("/user/login") @ResponseBody public String Login(String userName,String password){ //獲取當(dāng)前用戶 subject Subject subject = SecurityUtils.getSubject(); //封裝用戶的登陸數(shù)據(jù) UsernamePasswordToken token = new UsernamePasswordToken(userName, password); try{ subject.login(token);//執(zhí)行登陸方法 return "登陸成功"; }catch (UnknownAccountException e){//用戶名不存在 model.addAttribute("msg","用戶名不存在"); return "用戶名不存在"; }catch (IncorrectCredentialsException e){//密碼錯(cuò)誤 model.addAttribute("msg","密碼錯(cuò)誤"); return "密碼錯(cuò)誤"; } }
(為了測(cè)試方便,我用了@ResponseBody返回字符串)
2.完善自定義Realm類,繼承于AuthorizingRealm,主要實(shí)現(xiàn)doGetAuthorizationInfo和doGetAuthenticationInfo方法
(需要實(shí)現(xiàn)認(rèn)證和授權(quán)方法,在這里方便測(cè)試主要是認(rèn)證)
public class StudentRealm extends AuthorizingRealm { @Resource private StudentsService studentsService; //授權(quán) @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } //認(rèn)證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("Shiro=========Student認(rèn)證"); UserToken userToken = (UserToken) token; Students students = studentsService.queryByNum(userToken.getUsername()); //賬號(hào)不存在 if (students == null) { System.out.println("學(xué)生不存在"); //向上層提交UnknownAccountException異常,在controller層處理 throw new UnknownAccountException(); } //密碼認(rèn)證,shiro來(lái)做,可以自定義加密方式 return new SimpleAuthenticationInfo("", students.getPassword(), USER_LOGIN_TYPE); } }
3.配置shiro,將realm配置進(jìn)shiro(很多教程是使用xml配置或者ini配置,在這里用java代碼配置,功能都是一樣的,看個(gè)人習(xí)慣了)
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //設(shè)置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); return bean; } //DefaultWebSecurityManager 默認(rèn)web安全管理器 @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //關(guān)聯(lián)realm securityManager.setRealm(userRealm); return securityManager; } //創(chuàng)建自定義 realm @Bean public UserRealm userRealm() { return new UserRealm(); } }
記得加@Configuration注解?。。。。。?!
經(jīng)過(guò)以上三步,可以看出shiro的簡(jiǎn)略工作流程(非常簡(jiǎn)略)就是,在web 啟動(dòng)階段,讀取
@Configuration注解將自定義的ream配置進(jìn)默認(rèn)web安全管理器(DefaultWebSecurityManager)然后將DefaultWebSecurityManager與ShiroFilterFactoryBean相關(guān)聯(lián)。
當(dāng)用戶登陸時(shí),從前端拿到username和password,封裝好Token后,進(jìn)入realm進(jìn)行認(rèn)證和授權(quán),而realm就來(lái)自于剛才的shiro的DefaultWebSecurityManager配置
多realm實(shí)現(xiàn)原理
根據(jù)上面的shiro簡(jiǎn)略流程可知,shiro配置中寫入多個(gè)realm后,在controller提交token時(shí),只要多攜帶一個(gè)參數(shù),用來(lái)進(jìn)行org.apache.shiro.authc.pam.ModularRealmAuthenticator類的doAuthenticate(重寫后)的驗(yàn)證即可明確應(yīng)該用那個(gè)realm。那么,我們需要重寫org.apache.shiro.authc.UsernamePasswordToken(令其攜帶身份參數(shù)用于選擇realm)和org.apache.shiro.authc.pam.ModularRealmAuthenticator(令其根據(jù)token中的身份參數(shù)來(lái)進(jìn)行選擇realm)即可。
多realm實(shí)現(xiàn)具體操作
1.寫多個(gè)自定義的realm
public class AdminRealm extends AuthorizingRealm { @Resource private AdminService adminService; private static final String USER_LOGIN_TYPE = UserType.AdminRealm; @Override public String getName() { return UserType.AdminRealm; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("Shiro=========Admin認(rèn)證"); UserToken userToken = (UserToken) token; Admin admin = adminService.queryById(userToken.getUsername()); if(admin == null){ System.out.println("管理員不存在"); throw new UnknownAccountException(); } return new SimpleAuthenticationInfo("", admin.getAdminpassword(), USER_LOGIN_TYPE); } }
2.創(chuàng)建靜態(tài)變量類(用于realm選擇)
public class UserType { //實(shí)習(xí)學(xué)校管理員 public static final String SchoolAdminRealm = "schooladminrealm"; //學(xué)生 public static final String StudentRealm ="studentrealm"; //管理員 public static final String AdminRealm ="adminrealm_1"; //導(dǎo)員 public static final String InstructorRealm ="instructorrealm"; //實(shí)習(xí)帶隊(duì)老師 public static final String UniversityteacherRealm ="universityteacherrealm"; //實(shí)習(xí)指導(dǎo)老師 public static final String SchoolTeacherRealm ="schoolteacherrealm"; }
3.重寫UsernamePasswordToken,令其可以攜帶身份參數(shù)
@Component public class UserModularRealmAuthenticator extends ModularRealmAuthenticator { @Override protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) { // 判斷getRealms()是否返回為空,ModularRealmAuthenticator 自帶 assertRealmsConfigured(); // 強(qiáng)制轉(zhuǎn)換回自定義的UserToken UserToken token = (UserToken) authenticationToken; String loginType = token.getLoginType(); Collection<Realm> realms = getRealms(); for (Realm realm : realms) { System.out.println(realm.getName().toLowerCase()); if (realm.getName().toLowerCase().contains(loginType)){ //找到登錄類型對(duì)應(yīng)的指定Realm return doSingleRealmAuthentication(realm, token); } } //沒找到正確的realm的異常處理 String msg = "Configuration error: Didn't find the right realm"; throw new IllegalStateException(msg); } }
4.shiro的配置中寫入自定義的realm,還有其它配置
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //設(shè)置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); return bean; } //DefaultWebSecurityManager @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager( @Qualifier("schoolAdminRealm") SchoolAdminRealm schoolAdminRealm, @Qualifier("studentRealm") StudentRealm studentRealm, @Qualifier("adminRealm") AdminRealm adminRealm, @Qualifier("schoolTeacherRealm") SchoolTeacherRealm schoolTeacherRealm, @Qualifier("instructorRealm") InstructorRealm instructorRealm, @Qualifier("universityteacherRealm") UniversityteacherRealm universityteacherRealm, @Qualifier("userModularRealmAuthenticator") UserModularRealmAuthenticator userModularRealmAuthenticator ) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setAuthenticator(userModularRealmAuthenticator); /**關(guān)聯(lián)realm *securityManager.setRealm() 是配置單個(gè)realm,不可用它配置多個(gè)realm *securityManager.setRealms()配置多個(gè)realm, *List<Realm> realms可以直接被set進(jìn)去 */ List<Realm> realms = new ArrayList<Realm>(); realms.add(schoolAdminRealm); realms.add(studentRealm); realms.add(adminRealm); realms.add(schoolTeacherRealm); realms.add(instructorRealm); realms.add(universityteacherRealm); securityManager.setRealms(realms); System.out.println(securityManager.getRealms().toString()); return securityManager; } //實(shí)習(xí)學(xué)校管理員 @Bean(name = "schoolAdminRealm") public SchoolAdminRealm SchoolAdminRealm() { return new SchoolAdminRealm(); } //學(xué)生 @Bean(name = "studentRealm") public StudentRealm StudentRealm() { return new StudentRealm(); } //管理員 @Bean(name = "adminRealm") public AdminRealm AdminRealm() { return new AdminRealm(); } //導(dǎo)員 @Bean(name = "instructorRealm") public InstructorRealm InstructorRealm() { return new InstructorRealm(); } //實(shí)習(xí)帶隊(duì)老師 @Bean(name = "universityteacherRealm") public UniversityteacherRealm UniversityteacherRealm() { return new UniversityteacherRealm(); } //實(shí)習(xí)指導(dǎo)老師 @Bean(name = "schoolTeacherRealm") public SchoolTeacherRealm SchoolTeacherRealm() { return new SchoolTeacherRealm(); } }
5.在controller中使用重寫后的UsernamePasswordToken(UserToken)即可
//管理員登陸 @RequestMapping(value = "/AdminLogin", produces = "text/html;charset=UTF-8") @ResponseBody//為了測(cè)試方便,返回字符串 public String AdminLogin( @RequestParam(value = "username") String username, @RequestParam(value = "password") String password) { //獲取當(dāng)前用戶 subject Subject subject = SecurityUtils.getSubject(); //封裝用戶的登陸數(shù)據(jù) UserToken token = new UserToken(username, Md5.getMd5(password), USER_LOGIN_TYPE); try { System.out.println("AdminLogin"); subject.login(token);//執(zhí)行登陸方法 return null; } catch (UnknownAccountException e) {//用戶名不存在 System.out.println("用戶名錯(cuò)誤"); return null; } catch (IncorrectCredentialsException e) {//密碼錯(cuò)誤 System.out.println("密碼錯(cuò)誤"); return null; }
到此這篇關(guān)于Spring Boot 集成Shiro的多realm實(shí)現(xiàn)以及shiro基本入門的文章就介紹到這了,更多相關(guān)Spring Boot 集成Shiro內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot自動(dòng)重連Redis的實(shí)現(xiàn)方法
由于網(wǎng)絡(luò)或服務(wù)器問(wèn)題,Redis連接可能會(huì)斷開,導(dǎo)致應(yīng)用程序無(wú)法繼續(xù)正常工作,本文主要介紹了springboot自動(dòng)重連Redis的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02java 根據(jù)身份證號(hào)碼判斷出生日期、性別、年齡的示例
這篇文章主要介紹了java 根據(jù)身份證號(hào)碼判斷出生日期、性別、年齡的示例,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-10-10詳解SpringBoot實(shí)現(xiàn)ApplicationEvent事件的監(jiān)聽與發(fā)布
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何實(shí)現(xiàn)ApplicationEvent事件的監(jiān)聽與發(fā)布,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03java使用Jdom實(shí)現(xiàn)xml文件寫入操作實(shí)例
這篇文章主要介紹了java使用Jdom實(shí)現(xiàn)xml文件寫入操作的方法,以完整實(shí)例形式分析了Jdom針對(duì)XML文件寫入操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10MybatisPlus開啟、關(guān)閉二級(jí)緩存方法
本文主要介紹了MybatisPlus開啟、關(guān)閉二級(jí)緩存方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09