Spring Boot 集成Shiro的多realm配置過(guò)程
我在做畢設(shè)的時(shí)候采用shiro進(jìn)行登錄認(rèn)證和權(quán)限管理的實(shí)現(xiàn)。其中需求涉及使用三個(gè)角色分別是:學(xué)生、教師、管理員。現(xiàn)在要三者實(shí)現(xiàn)分開(kāi)登錄。即需要三個(gè)Realm——StudentRealm和TeacherRealm、AdminRealm,分別處理學(xué)生、教師和管理員的驗(yàn)證功能。
但是正常情況下,當(dāng)定義了多個(gè)Realm,無(wú)論是學(xué)生登錄,教師登錄,還是管理員登錄,都會(huì)由這三個(gè)Realm共同處理。這是因?yàn)椋?dāng)配置了多個(gè)Realm時(shí),我們通常使用的認(rèn)證器是shiro自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中決定使用的Realm的是doAuthenticate()方法,源代碼如下:
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); } }
上述代碼的意思就是如果有多個(gè)Realm就會(huì)使用所有配置的Realm。 只有一個(gè)的時(shí)候,就直接使用當(dāng)前的Realm。
為了實(shí)現(xiàn)需求,我會(huì)創(chuàng)建一個(gè)org.apache.shiro.authc.pam.ModularRealmAuthenticator的子類(lèi),并重寫(xiě)doAuthenticate()方法,讓特定的Realm完成特定的功能。如何區(qū)分呢?我會(huì)同時(shí)創(chuàng)建一個(gè)org.apache.shiro.authc.UsernamePasswordToken的子類(lèi),在其中添加一個(gè)字段loginType,用來(lái)標(biāo)識(shí)登錄的類(lèi)型,即是學(xué)生登錄、教師登錄,還是管理員登錄。具體步驟如下(我的代碼使用的是Groovy):
enum LoginType { STUDENT('Student'), ADMIN('Admin'), TEACHER('Teacher') private String type private LoginType(String type) { this.type = type } @Override public String toString() { return this.type.toString() }}
接下來(lái)新建org.apache.shiro.authc.UsernamePasswordToken的子類(lèi)UserToken
import org.apache.shiro.authc.UsernamePasswordTokenclass UserToken extends UsernamePasswordToken { //登錄類(lèi)型,判斷是學(xué)生登錄,教師登錄還是管理員登錄 private String loginType public UserToken(final String username, final String password,String loginType) { super(username,password) this.loginType = loginType } public String getLoginType() { return loginType } public void setLoginType(String loginType) { this.loginType = loginType }}
第三步:新建org.apache.shiro.authc.pam.ModularRealmAuthenticator的子類(lèi)UserModularRealmAuthenticator:
import org.apache.shiro.authc.AuthenticationExceptionimport org.apache.shiro.authc.AuthenticationInfoimport org.apache.shiro.authc.AuthenticationTokenimport org.apache.shiro.authc.pam.ModularRealmAuthenticatorimport org.apache.shiro.realm.Realmimport org.slf4j.Loggerimport org.slf4j.LoggerFactory/** * 當(dāng)配置了多個(gè)Realm時(shí),我們通常使用的認(rèn)證器是shiro自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中決定使用的Realm的是doAuthenticate()方法 * * 自定義Authenticator * 注意,當(dāng)需要分別定義處理學(xué)生和教師和管理員驗(yàn)證的Realm時(shí),對(duì)應(yīng)Realm的全類(lèi)名應(yīng)該包含字符串“Student”“Teacher”,或者“Admin”。 * 并且,他們不能相互包含,例如,處理學(xué)生驗(yàn)證的Realm的全類(lèi)名中不應(yīng)該包含字符串'Admin'。 */class UserModularRealmAuthenticator extends ModularRealmAuthenticator { private static final Logger logger = LoggerFactory.getLogger(UserModularRealmAuthenticator.class) @Override protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info('UserModularRealmAuthenticator:method doAuthenticate() execute ') // 判斷getRealms()是否返回為空 assertRealmsConfigured() // 強(qiáng)制轉(zhuǎn)換回自定義的CustomizedToken UserToken userToken = (UserToken) authenticationToken // 登錄類(lèi)型 String loginType = userToken?.getLoginType() // 所有Realm Collection<Realm> realms = getRealms() // 登錄類(lèi)型對(duì)應(yīng)的所有Realm Collection<Realm> typeRealms = new ArrayList<>() for (Realm realm : realms) { if (realm?.getName()?.contains(loginType)) typeRealms?.add(realm) } // 判斷是單Realm還是多Realm if (typeRealms?.size() == 1){ logger.info('doSingleRealmAuthentication() execute ') return doSingleRealmAuthentication(typeRealms?.get(0), userToken) } else{ logger.info('doMultiRealmAuthentication() execute ') return doMultiRealmAuthentication(typeRealms, userToken) } }}
第四步:創(chuàng)建分別處理學(xué)生登錄和教師登錄、管理員登錄的Realm: 我這里直接貼出了我項(xiàng)目中的代碼,你們可以根據(jù)具體的需求進(jìn)行操作。 AdminShiroRealm :
package com.ciyou.edu.config.shiro.adminimport com.ciyou.edu.config.shiro.common.UserTokenimport com.ciyou.edu.entity.Adminimport com.ciyou.edu.service.AdminServiceimport com.ciyou.edu.service.PermissionServiceimport org.apache.shiro.authc.AuthenticationExceptionimport org.apache.shiro.authc.AuthenticationInfoimport org.apache.shiro.authc.AuthenticationTokenimport org.apache.shiro.authc.SimpleAuthenticationInfoimport org.apache.shiro.authc.UnknownAccountExceptionimport org.apache.shiro.authz.AuthorizationExceptionimport org.apache.shiro.authz.AuthorizationInfoimport org.apache.shiro.authz.SimpleAuthorizationInfoimport org.apache.shiro.realm.AuthorizingRealmimport org.apache.shiro.subject.PrincipalCollectionimport org.apache.shiro.util.ByteSourceimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.context.annotation.Lazyclass AdminShiroRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(AdminShiroRealm.class) @Autowired @Lazy private AdminService adminService @Autowired @Lazy private PermissionService permissionService @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { logger.info('開(kāi)始Admin身份認(rèn)證...') UserToken userToken = (UserToken)token String adminName = userToken?.getUsername() //獲取用戶(hù)名,默認(rèn)和login.html中的adminName對(duì)應(yīng)。 Admin admin = adminService?.findByAdminName(adminName) if (admin == null) { //沒(méi)有返回登錄用戶(hù)名對(duì)應(yīng)的SimpleAuthenticationInfo對(duì)象時(shí),就會(huì)在LoginController中拋出UnknownAccountException異常 throw new UnknownAccountException('用戶(hù)不存在!') } //驗(yàn)證通過(guò)返回一個(gè)封裝了用戶(hù)信息的AuthenticationInfo實(shí)例即可。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( admin, //用戶(hù)信息 admin?.getPassword(), //密碼 getName() //realm name ) authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(admin?.getAdminName())) //設(shè)置鹽 logger.info('返回Admin認(rèn)證信息:' + authenticationInfo) return authenticationInfo }//當(dāng)訪問(wèn)到頁(yè)面的時(shí)候,鏈接配置了相應(yīng)的權(quán)限或者shiro標(biāo)簽才會(huì)執(zhí)行此方法否則不會(huì)執(zhí)行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info('開(kāi)始Admin權(quán)限授權(quán)(進(jìn)行權(quán)限驗(yàn)證!!)') if (principals == null) { throw new AuthorizationException('PrincipalCollection method argument cannot be null.') } SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo() if(principals?.getPrimaryPrincipal() instanceof Admin){ Admin admin = (Admin) principals?.getPrimaryPrincipal() logger.info('當(dāng)前Admin :' + admin ) authorizationInfo?.addRole('Admin') //每次都從數(shù)據(jù)庫(kù)重新查找,確保能及時(shí)更新權(quán)限 admin?.setPermissionList(permissionService?.findPermissionByAdmin(admin?.getAdminId())) admin?.getPermissionList()?.each {current_Permission -> authorizationInfo?.addStringPermission(current_Permission?.getPermission()) } logger.info('當(dāng)前Admin授權(quán)角色:' +authorizationInfo?.getRoles() + ',權(quán)限:' + authorizationInfo?.getStringPermissions()) return authorizationInfo } }}
TeacherShiroRealm :
package com.ciyou.edu.config.shiro.teacherimport com.ciyou.edu.config.shiro.common.UserTokenimport com.ciyou.edu.entity.Teacherimport com.ciyou.edu.service.TeacherServiceimport org.apache.shiro.authc.*import org.apache.shiro.authz.AuthorizationExceptionimport org.apache.shiro.authz.AuthorizationInfoimport org.apache.shiro.authz.SimpleAuthorizationInfoimport org.apache.shiro.realm.AuthorizingRealmimport org.apache.shiro.subject.PrincipalCollectionimport org.apache.shiro.util.ByteSourceimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.context.annotation.Lazyclass TeacherShiroRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(TeacherShiroRealm.class) //在自定義Realm中注入的Service聲明中加入@Lazy注解即可解決@cacheble注解無(wú)效問(wèn)題 //解決同時(shí)使用Redis緩存數(shù)據(jù)和緩存shiro時(shí),@cacheble無(wú)效的問(wèn)題 @Autowired @Lazy private TeacherService teacherService @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { logger.info('開(kāi)始Teacher身份認(rèn)證..') UserToken userToken = (UserToken)token String teacherId = userToken?.getUsername() Teacher teacher = teacherService?.findByTeacherId(teacherId) if (teacher == null) { //沒(méi)有返回登錄用戶(hù)名對(duì)應(yīng)的SimpleAuthenticationInfo對(duì)象時(shí),就會(huì)在LoginController中拋出UnknownAccountException異常 throw new UnknownAccountException('用戶(hù)不存在!') } //驗(yàn)證通過(guò)返回一個(gè)封裝了用戶(hù)信息的AuthenticationInfo實(shí)例即可。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( teacher, //用戶(hù)信息 teacher?.getPassword(), //密碼 getName() //realm name ) authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(teacher?.getTeacherId())) //設(shè)置鹽 return authenticationInfo }//當(dāng)訪問(wèn)到頁(yè)面的時(shí)候,鏈接配置了相應(yīng)的權(quán)限或者shiro標(biāo)簽才會(huì)執(zhí)行此方法否則不會(huì)執(zhí)行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info('開(kāi)始Teacher權(quán)限授權(quán)') if (principals == null) { throw new AuthorizationException('PrincipalCollection method argument cannot be null.') } SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo() if(principals?.getPrimaryPrincipal() instanceof Teacher){ authorizationInfo?.addRole('Teacher') return authorizationInfo } }}
StudentShiroRealm :
package com.ciyou.edu.config.shiro.studentimport com.ciyou.edu.config.shiro.common.UserTokenimport com.ciyou.edu.entity.Studentimport com.ciyou.edu.service.StudentServiceimport org.apache.shiro.authc.*import org.apache.shiro.authz.AuthorizationExceptionimport org.apache.shiro.authz.AuthorizationInfoimport org.apache.shiro.authz.SimpleAuthorizationInfoimport org.apache.shiro.realm.AuthorizingRealmimport org.apache.shiro.subject.PrincipalCollectionimport org.apache.shiro.util.ByteSourceimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.context.annotation.Lazyclass StudentShiroRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(StudentShiroRealm.class) //在自定義Realm中注入的Service聲明中加入@Lazy注解即可解決@cacheble注解無(wú)效問(wèn)題 //解決同時(shí)使用Redis緩存數(shù)據(jù)和緩存shiro時(shí),@cacheble無(wú)效的問(wèn)題 @Autowired @Lazy private StudentService studentService @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { logger.info('開(kāi)始Student身份認(rèn)證..') UserToken userToken = (UserToken)token String studentId = userToken?.getUsername() Student student = studentService?.findByStudentId(studentId) if (student == null) { //沒(méi)有返回登錄用戶(hù)名對(duì)應(yīng)的SimpleAuthenticationInfo對(duì)象時(shí),就會(huì)在LoginController中拋出UnknownAccountException異常 throw new UnknownAccountException('用戶(hù)不存在!') } //驗(yàn)證通過(guò)返回一個(gè)封裝了用戶(hù)信息的AuthenticationInfo實(shí)例即可。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( student, //用戶(hù)信息 student?.getPassword(), //密碼 getName() //realm name ) authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(student?.getStudentId())) //設(shè)置鹽 return authenticationInfo }//當(dāng)訪問(wèn)到頁(yè)面的時(shí)候,鏈接配置了相應(yīng)的權(quán)限或者shiro標(biāo)簽才會(huì)執(zhí)行此方法否則不會(huì)執(zhí)行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info('開(kāi)始Student權(quán)限授權(quán)') if (principals == null) { throw new AuthorizationException('PrincipalCollection method argument cannot be null.') } SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo() if(principals?.getPrimaryPrincipal() instanceof Student){ authorizationInfo?.addRole('Student') return authorizationInfo } }}
接下來(lái)是對(duì)Shiro進(jìn)行多realm的注解配置。 這里直接貼出我項(xiàng)目中的代碼。
上面是我進(jìn)行shiro進(jìn)行配置的類(lèi),下面是主要的一些代碼:
//SecurityManager 是 Shiro 架構(gòu)的核心,通過(guò)它來(lái)鏈接Realm和用戶(hù)(文檔中稱(chēng)之為Subject.) @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager() //設(shè)置realm. securityManager.setAuthenticator(modularRealmAuthenticator()) List<Realm> realms = new ArrayList<>() //添加多個(gè)Realm realms.add(adminShiroRealm()) realms.add(teacherShiroRealm()) realms.add(studentShiroRealm()) securityManager.setRealms(realms) // 自定義緩存實(shí)現(xiàn) 使用redis securityManager.setCacheManager(cacheManager()) // 自定義session管理 使用redis securityManager.setSessionManager(sessionManager()) //注入記住我管理器; securityManager.setRememberMeManager(rememberMeManager()) return securityManager } /** * 系統(tǒng)自帶的Realm管理,主要針對(duì)多realm * */ @Bean public ModularRealmAuthenticator modularRealmAuthenticator(){ //自己重寫(xiě)的ModularRealmAuthenticator UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator() modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()) return modularRealmAuthenticator } @Bean public AdminShiroRealm adminShiroRealm() { AdminShiroRealm adminShiroRealm = new AdminShiroRealm() adminShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設(shè)置解密規(guī)則 return adminShiroRealm } @Bean public StudentShiroRealm studentShiroRealm() { StudentShiroRealm studentShiroRealm = new StudentShiroRealm() studentShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設(shè)置解密規(guī)則 return studentShiroRealm } @Bean public TeacherShiroRealm teacherShiroRealm() { TeacherShiroRealm teacherShiroRealm = new TeacherShiroRealm() teacherShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設(shè)置解密規(guī)則 return teacherShiroRealm } //因?yàn)槲覀兊拿艽a是加過(guò)密的,所以,如果要Shiro驗(yàn)證用戶(hù)身份的話(huà),需要告訴它我們用的是md5加密的,并且是加密了兩次。同時(shí)我們?cè)谧约旱腞ealm中也通過(guò)SimpleAuthenticationInfo返回了加密時(shí)使用的鹽。這樣Shiro就能順利的解密密碼并驗(yàn)證用戶(hù)名和密碼是否正確了。 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher() hashedCredentialsMatcher.setHashAlgorithmName('md5')//散列算法:這里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2)//散列的次數(shù),比如散列兩次,相當(dāng)于 md5(md5('')); return hashedCredentialsMatcher; }
接下來(lái)就是Controller中實(shí)現(xiàn)登錄的功能了,這里我只貼出我項(xiàng)目中Admin登錄的代碼:
package com.ciyou.edu.controller.adminimport com.ciyou.edu.config.shiro.common.LoginTypeimport com.ciyou.edu.config.shiro.common.UserTokenimport com.ciyou.edu.entity.Adminimport com.ciyou.edu.utils.JSONUtilimport org.apache.shiro.SecurityUtilsimport org.apache.shiro.authc.AuthenticationExceptionimport org.apache.shiro.subject.Subjectimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.stereotype.Controllerimport org.springframework.web.bind.annotation.RequestMappingimport org.springframework.web.bind.annotation.RequestMethodimport org.springframework.web.bind.annotation.ResponseBody/** * @Author C. * @Date 2018-02-02 20:46 * admin登錄Controller */@Controllerclass AdminLoginController { private static final Logger logger = LoggerFactory.getLogger(AdminLoginController.class) private static final String ADMIN_LOGIN_TYPE = LoginType.ADMIN.toString() /** * admin登錄 * @param admin * @return 登錄結(jié)果 */ @RequestMapping(value='/adminLogin',method=RequestMethod.POST, produces='application/json;charset=UTF-8') @ResponseBody public String loginAdmin(Admin admin){ logger.info('登錄Admin: ' + admin) //后臺(tái)校驗(yàn)提交的用戶(hù)名和密碼 if(!admin?.getAdminName() || admin?.adminName?.trim() == ''){ return JSONUtil.returnFailReuslt('賬號(hào)不能為空') }else if(!admin?.getPassword() || admin?.getPassword()?.trim() == ''){ return JSONUtil.returnFailReuslt('密碼不能為空') }else if(admin?.getAdminName()?.length() < 3 || admin?.getAdminName()?.length() >15){ return JSONUtil.returnFailReuslt('賬號(hào)長(zhǎng)度必須在3~15之間') }else if(admin?.getPassword()?.length() < 3 || admin?.getPassword()?.length() >15){ return JSONUtil.returnFailReuslt('密碼長(zhǎng)度必須在3~15之間') } //獲取Subject實(shí)例對(duì)象 //在shiro里面所有的用戶(hù)的會(huì)話(huà)信息都會(huì)由Shiro來(lái)進(jìn)行控制,那么也就是說(shuō)只要是與用戶(hù)有關(guān)的一切的處理信息操作都可以通過(guò)Shiro取得, // 實(shí)際上可以取得的信息可以有用戶(hù)名、主機(jī)名稱(chēng)等等,這所有的信息都可以通過(guò)Subject接口取得 Subject subject = SecurityUtils.getSubject() //將用戶(hù)名和密碼封裝到繼承了UsernamePasswordToken的userToken UserToken userToken = new UserToken(admin?.getAdminName(), admin?.getPassword(), ADMIN_LOGIN_TYPE) userToken.setRememberMe(false) try { //認(rèn)證 // 傳到ModularRealmAuthenticator類(lèi)中,然后根據(jù)ADMIN_LOGIN_TYPE傳到AdminShiroRealm的方法進(jìn)行認(rèn)證 subject?.login(userToken) //Admin存入session SecurityUtils.getSubject()?.getSession()?.setAttribute('admin',(Admin)subject?.getPrincipal()) return JSONUtil.returnSuccessResult('登錄成功') } catch (AuthenticationException e) { //認(rèn)證失敗就會(huì)拋出AuthenticationException這個(gè)異常,就對(duì)異常進(jìn)行相應(yīng)的操作,這里的處理是拋出一個(gè)自定義異常ResultException //到時(shí)候我們拋出自定義異常ResultException,用戶(hù)名或者密碼錯(cuò)誤 logger.info('認(rèn)證錯(cuò)誤:' + e.getMessage()) return JSONUtil.returnFailReuslt('賬號(hào)或者密碼錯(cuò)誤') } } @RequestMapping(value='/admin/adminLogout') public String logoutAdmin(){ SecurityUtils.getSubject()?.logout() return 'redirect:/adminLogin' }}
現(xiàn)在Spring Boot中集成Shiro實(shí)現(xiàn)多realm配置就完成了。
感謝以下博文,在我學(xué)習(xí)的過(guò)程中給了我很多幫助,我的博文也有一些內(nèi)容參考他們的,還不夠清楚的讀者可以參考: shiro實(shí)現(xiàn)不同身份使用不同Realm進(jìn)行驗(yàn)證 SpringBoot+Shiro學(xué)習(xí)之?dāng)?shù)據(jù)庫(kù)動(dòng)態(tài)權(quán)限管理和Redis緩存 Springboot多realm集成,無(wú)ini文件,無(wú)xml配置
想看項(xiàng)目具體源碼,或者對(duì)我項(xiàng)目感興趣的可以查看:CIYOU
到此這篇關(guān)于Spring Boot 集成Shiro的多realm配置過(guò)程的文章就介紹到這了,更多相關(guān)Spring Boot多realm配置內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. ASP常用日期格式化函數(shù) FormatDate()2. Python 操作 MySQL數(shù)據(jù)庫(kù)3. Python數(shù)據(jù)相關(guān)系數(shù)矩陣和熱力圖輕松實(shí)現(xiàn)教程4. 開(kāi)發(fā)效率翻倍的Web API使用技巧5. bootstrap select2 動(dòng)態(tài)從后臺(tái)Ajax動(dòng)態(tài)獲取數(shù)據(jù)的代碼6. CSS3中Transition屬性詳解以及示例分享7. js select支持手動(dòng)輸入功能實(shí)現(xiàn)代碼8. 什么是Python變量作用域9. vue使用moment如何將時(shí)間戳轉(zhuǎn)為標(biāo)準(zhǔn)日期時(shí)間格式10. python 如何在 Matplotlib 中繪制垂直線
