Spring Boot 集成Shiro的多realm配置過程
我在做畢設的時候采用shiro進行登錄認證和權限管理的實現。其中需求涉及使用三個角色分別是:學生、教師、管理員?,F在要三者實現分開登錄。即需要三個Realm——StudentRealm和TeacherRealm、AdminRealm,分別處理學生、教師和管理員的驗證功能。
但是正常情況下,當定義了多個Realm,無論是學生登錄,教師登錄,還是管理員登錄,都會由這三個Realm共同處理。這是因為,當配置了多個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。 只有一個的時候,就直接使用當前的Realm。
為了實現需求,我會創建一個org.apache.shiro.authc.pam.ModularRealmAuthenticator的子類,并重寫doAuthenticate()方法,讓特定的Realm完成特定的功能。如何區分呢?我會同時創建一個org.apache.shiro.authc.UsernamePasswordToken的子類,在其中添加一個字段loginType,用來標識登錄的類型,即是學生登錄、教師登錄,還是管理員登錄。具體步驟如下(我的代碼使用的是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.UsernamePasswordTokenclass UserToken extends UsernamePasswordToken { //登錄類型,判斷是學生登錄,教師登錄還是管理員登錄 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.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/** * 當配置了多個Realm時,我們通常使用的認證器是shiro自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中決定使用的Realm的是doAuthenticate()方法 * * 自定義Authenticator * 注意,當需要分別定義處理學生和教師和管理員驗證的Realm時,對應Realm的全類名應該包含字符串“Student”“Teacher”,或者“Admin”。 * 并且,他們不能相互包含,例如,處理學生驗證的Realm的全類名中不應該包含字符串'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() // 強制轉換回自定義的CustomizedToken UserToken userToken = (UserToken) authenticationToken // 登錄類型 String loginType = userToken?.getLoginType() // 所有Realm Collection<Realm> realms = getRealms() // 登錄類型對應的所有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) } }}
第四步:創建分別處理學生登錄和教師登錄、管理員登錄的Realm: 我這里直接貼出了我項目中的代碼,你們可以根據具體的需求進行操作。 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('開始Admin身份認證...') UserToken userToken = (UserToken)token String adminName = userToken?.getUsername() //獲取用戶名,默認和login.html中的adminName對應。 Admin admin = adminService?.findByAdminName(adminName) if (admin == null) { //沒有返回登錄用戶名對應的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常 throw new UnknownAccountException('用戶不存在!') } //驗證通過返回一個封裝了用戶信息的AuthenticationInfo實例即可。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( admin, //用戶信息 admin?.getPassword(), //密碼 getName() //realm name ) authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(admin?.getAdminName())) //設置鹽 logger.info('返回Admin認證信息:' + authenticationInfo) return authenticationInfo }//當訪問到頁面的時候,鏈接配置了相應的權限或者shiro標簽才會執行此方法否則不會執行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info('開始Admin權限授權(進行權限驗證!!)') 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('當前Admin :' + admin ) authorizationInfo?.addRole('Admin') //每次都從數據庫重新查找,確保能及時更新權限 admin?.setPermissionList(permissionService?.findPermissionByAdmin(admin?.getAdminId())) admin?.getPermissionList()?.each {current_Permission -> authorizationInfo?.addStringPermission(current_Permission?.getPermission()) } logger.info('當前Admin授權角色:' +authorizationInfo?.getRoles() + ',權限:' + 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注解無效問題 //解決同時使用Redis緩存數據和緩存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) { //沒有返回登錄用戶名對應的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常 throw new UnknownAccountException('用戶不存在!') } //驗證通過返回一個封裝了用戶信息的AuthenticationInfo實例即可。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( teacher, //用戶信息 teacher?.getPassword(), //密碼 getName() //realm name ) authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(teacher?.getTeacherId())) //設置鹽 return authenticationInfo }//當訪問到頁面的時候,鏈接配置了相應的權限或者shiro標簽才會執行此方法否則不會執行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info('開始Teacher權限授權') 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注解無效問題 //解決同時使用Redis緩存數據和緩存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) { //沒有返回登錄用戶名對應的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常 throw new UnknownAccountException('用戶不存在!') } //驗證通過返回一個封裝了用戶信息的AuthenticationInfo實例即可。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( student, //用戶信息 student?.getPassword(), //密碼 getName() //realm name ) authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(student?.getStudentId())) //設置鹽 return authenticationInfo }//當訪問到頁面的時候,鏈接配置了相應的權限或者shiro標簽才會執行此方法否則不會執行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info('開始Student權限授權') 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 架構的核心,通過它來鏈接Realm和用戶(文檔中稱之為Subject.) @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager() //設置realm. securityManager.setAuthenticator(modularRealmAuthenticator()) List<Realm> realms = new ArrayList<>() //添加多個Realm realms.add(adminShiroRealm()) realms.add(teacherShiroRealm()) realms.add(studentShiroRealm()) securityManager.setRealms(realms) // 自定義緩存實現 使用redis securityManager.setCacheManager(cacheManager()) // 自定義session管理 使用redis securityManager.setSessionManager(sessionManager()) //注入記住我管理器; securityManager.setRememberMeManager(rememberMeManager()) return securityManager } /** * 系統自帶的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())//設置解密規則 return adminShiroRealm } @Bean public StudentShiroRealm studentShiroRealm() { StudentShiroRealm studentShiroRealm = new StudentShiroRealm() studentShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設置解密規則 return studentShiroRealm } @Bean public TeacherShiroRealm teacherShiroRealm() { TeacherShiroRealm teacherShiroRealm = new TeacherShiroRealm() teacherShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設置解密規則 return teacherShiroRealm } //因為我們的密碼是加過密的,所以,如果要Shiro驗證用戶身份的話,需要告訴它我們用的是md5加密的,并且是加密了兩次。同時我們在自己的Realm中也通過SimpleAuthenticationInfo返回了加密時使用的鹽。這樣Shiro就能順利的解密密碼并驗證用戶名和密碼是否正確了。 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher() hashedCredentialsMatcher.setHashAlgorithmName('md5')//散列算法:這里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2)//散列的次數,比如散列兩次,相當于 md5(md5('')); return hashedCredentialsMatcher; }
接下來就是Controller中實現登錄的功能了,這里我只貼出我項目中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 登錄結果 */ @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來進行控制,那么也就是說只要是與用戶有關的一切的處理信息操作都可以通過Shiro取得, // 實際上可以取得的信息可以有用戶名、主機名稱等等,這所有的信息都可以通過Subject接口取得 Subject subject = SecurityUtils.getSubject() //將用戶名和密碼封裝到繼承了UsernamePasswordToken的userToken UserToken userToken = new UserToken(admin?.getAdminName(), admin?.getPassword(), ADMIN_LOGIN_TYPE) userToken.setRememberMe(false) try { //認證 // 傳到ModularRealmAuthenticator類中,然后根據ADMIN_LOGIN_TYPE傳到AdminShiroRealm的方法進行認證 subject?.login(userToken) //Admin存入session SecurityUtils.getSubject()?.getSession()?.setAttribute('admin',(Admin)subject?.getPrincipal()) return JSONUtil.returnSuccessResult('登錄成功') } catch (AuthenticationException e) { //認證失敗就會拋出AuthenticationException這個異常,就對異常進行相應的操作,這里的處理是拋出一個自定義異常ResultException //到時候我們拋出自定義異常ResultException,用戶名或者密碼錯誤 logger.info('認證錯誤:' + e.getMessage()) return JSONUtil.returnFailReuslt('賬號或者密碼錯誤') } } @RequestMapping(value='/admin/adminLogout') public String logoutAdmin(){ SecurityUtils.getSubject()?.logout() return 'redirect:/adminLogin' }}
現在Spring Boot中集成Shiro實現多realm配置就完成了。
感謝以下博文,在我學習的過程中給了我很多幫助,我的博文也有一些內容參考他們的,還不夠清楚的讀者可以參考: shiro實現不同身份使用不同Realm進行驗證 SpringBoot+Shiro學習之數據庫動態權限管理和Redis緩存 Springboot多realm集成,無ini文件,無xml配置
想看項目具體源碼,或者對我項目感興趣的可以查看:CIYOU
到此這篇關于Spring Boot 集成Shiro的多realm配置過程的文章就介紹到這了,更多相關Spring Boot多realm配置內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!
相關文章:
