Spring Boot 集成Shiro的多realm配置過程
我在做畢設(shè)的時候采用shiro進行登錄認證和權(quán)限管理的實現(xiàn)。其中需求涉及使用三個角色分別是:學(xué)生、教師、管理員。現(xiàn)在要三者實現(xiàn)分開登錄。即需要三個Realm——StudentRealm和TeacherRealm、AdminRealm,分別處理學(xué)生、教師和管理員的驗證功能。
但是正常情況下,當(dāng)定義了多個Realm,無論是學(xué)生登錄,教師登錄,還是管理員登錄,都會由這三個Realm共同處理。這是因為,當(dāng)配置了多個Realm時,我們通常使用的認證器是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); } }
上述代碼的意思就是如果有多個Realm就會使用所有配置的Realm。 只有一個的時候,就直接使用當(dāng)前的Realm。
為了實現(xiàn)需求,我會創(chuàng)建一個org.apache.shiro.authc.pam.ModularRealmAuthenticator的子類,并重寫doAuthenticate()方法,讓特定的Realm完成特定的功能。如何區(qū)分呢?我會同時創(chuàng)建一個org.apache.shiro.authc.UsernamePasswordToken的子類,在其中添加一個字段loginType,用來標(biāo)識登錄的類型,即是學(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() } }
接下來新建org.apache.shiro.authc.UsernamePasswordToken的子類UserToken
import org.apache.shiro.authc.UsernamePasswordToken class UserToken extends UsernamePasswordToken { //登錄類型,判斷是學(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的子類UserModularRealmAuthenticator:
import org.apache.shiro.authc.AuthenticationException import org.apache.shiro.authc.AuthenticationInfo import org.apache.shiro.authc.AuthenticationToken import org.apache.shiro.authc.pam.ModularRealmAuthenticator import org.apache.shiro.realm.Realm import org.slf4j.Logger import org.slf4j.LoggerFactory /** * 當(dāng)配置了多個Realm時,我們通常使用的認證器是shiro自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中決定使用的Realm的是doAuthenticate()方法 * * 自定義Authenticator * 注意,當(dāng)需要分別定義處理學(xué)生和教師和管理員驗證的Realm時,對應(yīng)Realm的全類名應(yīng)該包含字符串“Student”“Teacher”,或者“Admin”。 * 并且,他們不能相互包含,例如,處理學(xué)生驗證的Realm的全類名中不應(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() // 強制轉(zhuǎn)換回自定義的CustomizedToken UserToken userToken = (UserToken) authenticationToken // 登錄類型 String loginType = userToken?.getLoginType() // 所有Realm Collection<Realm> realms = getRealms() // 登錄類型對應(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:
我這里直接貼出了我項目中的代碼,你們可以根據(jù)具體的需求進行操作。
AdminShiroRealm :
package com.ciyou.edu.config.shiro.admin import com.ciyou.edu.config.shiro.common.UserToken import com.ciyou.edu.entity.Admin import com.ciyou.edu.service.AdminService import com.ciyou.edu.service.PermissionService import org.apache.shiro.authc.AuthenticationException import org.apache.shiro.authc.AuthenticationInfo import org.apache.shiro.authc.AuthenticationToken import org.apache.shiro.authc.SimpleAuthenticationInfo import org.apache.shiro.authc.UnknownAccountException import org.apache.shiro.authz.AuthorizationException import org.apache.shiro.authz.AuthorizationInfo import org.apache.shiro.authz.SimpleAuthorizationInfo import org.apache.shiro.realm.AuthorizingRealm import org.apache.shiro.subject.PrincipalCollection import org.apache.shiro.util.ByteSource import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Lazy class 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("開始Admin身份認證...") UserToken userToken = (UserToken)token String adminName = userToken?.getUsername() //獲取用戶名,默認和login.html中的adminName對應(yīng)。 Admin admin = adminService?.findByAdminName(adminName) if (admin == null) { //沒有返回登錄用戶名對應(yīng)的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常 throw new UnknownAccountException("用戶不存在!") } //驗證通過返回一個封裝了用戶信息的AuthenticationInfo實例即可。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( admin, //用戶信息 admin?.getPassword(), //密碼 getName() //realm name ) authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(admin?.getAdminName())) //設(shè)置鹽 logger.info("返回Admin認證信息:" + authenticationInfo) return authenticationInfo } //當(dāng)訪問到頁面的時候,鏈接配置了相應(yīng)的權(quán)限或者shiro標(biāo)簽才會執(zhí)行此方法否則不會執(zhí)行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info("開始Admin權(quán)限授權(quán)(進行權(quá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ù)庫重新查找,確保能及時更新權(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.teacher import com.ciyou.edu.config.shiro.common.UserToken import com.ciyou.edu.entity.Teacher import com.ciyou.edu.service.TeacherService import org.apache.shiro.authc.* import org.apache.shiro.authz.AuthorizationException import org.apache.shiro.authz.AuthorizationInfo import org.apache.shiro.authz.SimpleAuthorizationInfo import org.apache.shiro.realm.AuthorizingRealm import org.apache.shiro.subject.PrincipalCollection import org.apache.shiro.util.ByteSource import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Lazy class TeacherShiroRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(TeacherShiroRealm.class) //在自定義Realm中注入的Service聲明中加入@Lazy注解即可解決@cacheble注解無效問題 //解決同時使用Redis緩存數(shù)據(jù)和緩存shiro時,@cacheble無效的問題 @Autowired @Lazy private TeacherService teacherService @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { logger.info("開始Teacher身份認證..") UserToken userToken = (UserToken)token String teacherId = userToken?.getUsername() Teacher teacher = teacherService?.findByTeacherId(teacherId) if (teacher == null) { //沒有返回登錄用戶名對應(yīng)的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常 throw new UnknownAccountException("用戶不存在!") } //驗證通過返回一個封裝了用戶信息的AuthenticationInfo實例即可。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( teacher, //用戶信息 teacher?.getPassword(), //密碼 getName() //realm name ) authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(teacher?.getTeacherId())) //設(shè)置鹽 return authenticationInfo } //當(dāng)訪問到頁面的時候,鏈接配置了相應(yīng)的權(quán)限或者shiro標(biāo)簽才會執(zhí)行此方法否則不會執(zhí)行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info("開始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.student import com.ciyou.edu.config.shiro.common.UserToken import com.ciyou.edu.entity.Student import com.ciyou.edu.service.StudentService import org.apache.shiro.authc.* import org.apache.shiro.authz.AuthorizationException import org.apache.shiro.authz.AuthorizationInfo import org.apache.shiro.authz.SimpleAuthorizationInfo import org.apache.shiro.realm.AuthorizingRealm import org.apache.shiro.subject.PrincipalCollection import org.apache.shiro.util.ByteSource import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Lazy class StudentShiroRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(StudentShiroRealm.class) //在自定義Realm中注入的Service聲明中加入@Lazy注解即可解決@cacheble注解無效問題 //解決同時使用Redis緩存數(shù)據(jù)和緩存shiro時,@cacheble無效的問題 @Autowired @Lazy private StudentService studentService @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { logger.info("開始Student身份認證..") UserToken userToken = (UserToken)token String studentId = userToken?.getUsername() Student student = studentService?.findByStudentId(studentId) if (student == null) { //沒有返回登錄用戶名對應(yīng)的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常 throw new UnknownAccountException("用戶不存在!") } //驗證通過返回一個封裝了用戶信息的AuthenticationInfo實例即可。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( student, //用戶信息 student?.getPassword(), //密碼 getName() //realm name ) authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(student?.getStudentId())) //設(shè)置鹽 return authenticationInfo } //當(dāng)訪問到頁面的時候,鏈接配置了相應(yīng)的權(quán)限或者shiro標(biāo)簽才會執(zhí)行此方法否則不會執(zhí)行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info("開始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 } } }
接下來是對Shiro進行多realm的注解配置。
這里直接貼出我項目中的代碼。
上面是我進行shiro進行配置的類,下面是主要的一些代碼:
//SecurityManager 是 Shiro 架構(gòu)的核心,通過它來鏈接Realm和用戶(文檔中稱之為Subject.) @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager() //設(shè)置realm. securityManager.setAuthenticator(modularRealmAuthenticator()) List<Realm> realms = new ArrayList<>() //添加多個Realm realms.add(adminShiroRealm()) realms.add(teacherShiroRealm()) realms.add(studentShiroRealm()) securityManager.setRealms(realms) // 自定義緩存實現(xiàn) 使用redis securityManager.setCacheManager(cacheManager()) // 自定義session管理 使用redis securityManager.setSessionManager(sessionManager()) //注入記住我管理器; securityManager.setRememberMeManager(rememberMeManager()) return securityManager } /** * 系統(tǒng)自帶的Realm管理,主要針對多realm * */ @Bean public ModularRealmAuthenticator modularRealmAuthenticator(){ //自己重寫的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 } //因為我們的密碼是加過密的,所以,如果要Shiro驗證用戶身份的話,需要告訴它我們用的是md5加密的,并且是加密了兩次。同時我們在自己的Realm中也通過SimpleAuthenticationInfo返回了加密時使用的鹽。這樣Shiro就能順利的解密密碼并驗證用戶名和密碼是否正確了。 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher() hashedCredentialsMatcher.setHashAlgorithmName("md5")//散列算法:這里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2)//散列的次數(shù),比如散列兩次,相當(dāng)于 md5(md5("")); return hashedCredentialsMatcher; }
接下來就是Controller中實現(xiàn)登錄的功能了,這里我只貼出我項目中Admin登錄的代碼:
package com.ciyou.edu.controller.admin import com.ciyou.edu.config.shiro.common.LoginType import com.ciyou.edu.config.shiro.common.UserToken import com.ciyou.edu.entity.Admin import com.ciyou.edu.utils.JSONUtil import org.apache.shiro.SecurityUtils import org.apache.shiro.authc.AuthenticationException import org.apache.shiro.subject.Subject import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMethod import org.springframework.web.bind.annotation.ResponseBody /** * @Author C. * @Date 2018-02-02 20:46 * admin登錄Controller */ @Controller class 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) //后臺校驗提交的用戶名和密碼 if(!admin?.getAdminName() || admin?.adminName?.trim() == ""){ return JSONUtil.returnFailReuslt("賬號不能為空") }else if(!admin?.getPassword() || admin?.getPassword()?.trim() == ""){ return JSONUtil.returnFailReuslt("密碼不能為空") }else if(admin?.getAdminName()?.length() < 3 || admin?.getAdminName()?.length() >15){ return JSONUtil.returnFailReuslt("賬號長度必須在3~15之間") }else if(admin?.getPassword()?.length() < 3 || admin?.getPassword()?.length() >15){ return JSONUtil.returnFailReuslt("密碼長度必須在3~15之間") } //獲取Subject實例對象 //在shiro里面所有的用戶的會話信息都會由Shiro來進行控制,那么也就是說只要是與用戶有關(guān)的一切的處理信息操作都可以通過Shiro取得, // 實際上可以取得的信息可以有用戶名、主機名稱等等,這所有的信息都可以通過Subject接口取得 Subject subject = SecurityUtils.getSubject() //將用戶名和密碼封裝到繼承了UsernamePasswordToken的userToken UserToken userToken = new UserToken(admin?.getAdminName(), admin?.getPassword(), ADMIN_LOGIN_TYPE) userToken.setRememberMe(false) try { //認證 // 傳到ModularRealmAuthenticator類中,然后根據(jù)ADMIN_LOGIN_TYPE傳到AdminShiroRealm的方法進行認證 subject?.login(userToken) //Admin存入session SecurityUtils.getSubject()?.getSession()?.setAttribute("admin",(Admin)subject?.getPrincipal()) return JSONUtil.returnSuccessResult("登錄成功") } catch (AuthenticationException e) { //認證失敗就會拋出AuthenticationException這個異常,就對異常進行相應(yīng)的操作,這里的處理是拋出一個自定義異常ResultException //到時候我們拋出自定義異常ResultException,用戶名或者密碼錯誤 logger.info("認證錯誤:" + e.getMessage()) return JSONUtil.returnFailReuslt("賬號或者密碼錯誤") } } @RequestMapping(value="/admin/adminLogout") public String logoutAdmin(){ SecurityUtils.getSubject()?.logout() return "redirect:/adminLogin" } }
現(xiàn)在Spring Boot中集成Shiro實現(xiàn)多realm配置就完成了。
感謝以下博文,在我學(xué)習(xí)的過程中給了我很多幫助,我的博文也有一些內(nèi)容參考他們的,還不夠清楚的讀者可以參考:
shiro實現(xiàn)不同身份使用不同Realm進行驗證
SpringBoot+Shiro學(xué)習(xí)之?dāng)?shù)據(jù)庫動態(tài)權(quán)限管理和Redis緩存
Springboot多realm集成,無ini文件,無xml配置
想看項目具體源碼,或者對我項目感興趣的可以查看:CIYOU
到此這篇關(guān)于Spring Boot 集成Shiro的多realm配置過程的文章就介紹到這了,更多相關(guān)Spring Boot多realm配置內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot測試配置屬性與web啟動環(huán)境超詳細圖解
Web開發(fā)的核心內(nèi)容主要包括內(nèi)嵌的Servlet容器和SpringMVCSpringBoot使用起來非常簡潔,大部分配置都有SpringBoot自動裝配,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-10-10Java 多線程Synchronized和Lock的區(qū)別
這篇文章主要介紹了Java 多線程Synchronized和Lock的區(qū)別,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2021-01-01JAVA實戰(zhàn)項目實現(xiàn)客戶選購系統(tǒng)詳細流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java實現(xiàn)一個簡單的客戶選購系統(tǒng),大家可以在過程中查缺補漏,提升水平2021-10-10mybatis返回map結(jié)果集@MapKey使用的場景分析
這篇文章主要介紹了mybatis返回map結(jié)果集@MapKey使用的場景分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01關(guān)于Spring Boot和Kotlin的聯(lián)合開發(fā)
這篇文章主要介紹了關(guān)于Spring Boot和Kotlin的聯(lián)合開發(fā),需要的朋友可以參考下2017-06-06