Shiro默認使用自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分情況下需要從系統的數據庫中讀取用戶信息,所以需要自定義realm。
1,Realm接口
最基礎的是Realm接口,CachingRealm負責緩存處理,AuthenticationRealm負責認證,AuthorizingRealm負責授權,通常自定義的realm繼承AuthorizingRealm
2,數據表設計
permission 菜單和權限表
role 角色表
role_permission 角色和權限的關系表
user 用戶表
user_role用戶和角色之間的關系表
3,實現步驟
1,創建shiro_realm的maven項目
web.xml文件過濾器配置:
<!-- Shiro filter start --> <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> <!-- 下面這個參數表示,applicationContext.xml(Shiro過濾器鏈)的名字,如果省略,配置器鏈名默認就是過濾器名字--> <init-param> <param-name>targetBeanName</param-name> <param-value>shiroFilter</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Shiro filter end -->
applicationContext.xml設置
<!-- 10. Shiro認證權限配置--> <!-- ================ Shiro start ================ --> <!-- (1).聲明憑證匹配器(密碼加密用)--> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!--注入算法--> <property name="hashAlgorithmName" value="md5"></property> <!--注入散列次數--> <property name="hashIterations" value="1"></property> </bean> <!-- (2).配置Realm--> <bean id="shiroReaml" class="com.cc8w.shiro.ShiroRealm"> <!--注入憑證匹配器--> <property name="credentialsMatcher" ref="credentialsMatcher"></property> </bean> <!-- (3). 創建安全管理器--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--注入reaml--> <property name="realm" ref="shiroReaml"></property> </bean> <!-- (4). 配置過濾器鏈--> <!-- Shiro 的Web過濾器 id必須和web.xml里面的shiroFilter的 targetBeanName的值一樣 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--Shiro的核心安全接口,這個屬性是必須的--> <property name="securityManager" ref="securityManager"></property> <!-- 要求登錄時的鏈接(登錄頁面地址),非必須屬性,屬性會自動尋找web工程根目錄下的"/login.jsp"頁面--> <property name="loginUrl" value="/login.jsp"></property> <!-- Shiro 的Web過濾器 id必須和web.xml里面的shiroFilter的 targetBeanName的值一樣 --> <!--property name="successUrl" value="success.do"></property--> <!-- 用戶訪問未對其授權的資源時,所顯示的連接 --> <property name="unauthorizedUrl" value="unauthorized.jsp"></property> <!-- 過濾器鏈的定義,從上往下順序執行,一般將/**放在最后--> <property name="filterChainDefinitions"> <value> <!-- /**=authc 所有url都必須認證通過才可以訪問 --> /index.jsp*=anon /login/toLogin*=anon /login/login*=anon <!-- 如果訪問/login/logout就是用Shiro注銷session--> /login/logout=logout <!-- /** = anon所有url都可以匿名訪問 --> <!-- /** = authc --> <!-- /*/* = authc --> <!-- /** = authc所有url都不可以匿名訪問 必須放到最后面 --> /** = authc </value> </property> </bean> <!-- ================ Shiro end ================ -->
2,創建自定義realm (這個里面重寫兩個方法一個認證回調,一個授權回調)
package com.cc8w.shiro; import com.cc8w.entity.UserActivePojo; import com.cc8w.entity.UserPojo; import com.cc8w.service.PermssionService; import com.cc8w.service.RoleService; import com.cc8w.service.UserService; 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.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * Shiro默認使用自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分情況下需要從系統的數據庫中讀取用戶信息,所以需要自定義realm。 * 最基礎的是Realm接口,CachingRealm負責緩存處理,AuthenticationRealm負責認證,AuthorizingRealm負責授權,通常自定義的realm繼承AuthorizingRealm */ @Component public class ShiroRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private PermssionService permssionService; @Override public String getName() { return this.getClass().getSimpleName(); } /* * 登錄信息和用戶驗證信息驗證(non-Javadoc) * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //1.從authenticationToken中獲取身份信息,其實就是用戶的登錄名 String username = authenticationToken.getPrincipal().toString(); String password = authenticationToken.getCredentials().toString(); //2.根據用戶名查詢用戶是否存在 UserPojo user=userService.queryUserByUserName(username); System.out.println(user); //返回null說明用戶不存在 if(null!=user) { //2.1根據用戶名去查詢用戶擁有哪些角色 List<String> roles= roleService.queryRolesByUserName(user.getUserName()); System.out.println(roles); //2.2根據用戶名查詢用戶擁有哪些權限 List<String> permissions=permssionService.queryPermissionsByUserName(user.getUserName()); UserActivePojo activeUser=new UserActivePojo(user, roles, permissions); //3.返回認證信息 /** * 參數1 用戶身份 * 參數2 用戶在數據庫里面存放的密碼 * 參數3 當前類名 */ SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(activeUser, user.getPassword(), this.getName()); //SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName) return info; } return null; } /* * 授權查詢回調函數, 進行鑒權但緩存中無用戶的授權信息時調用,負責在應用程序中決定用戶的訪問控制的方法(non-Javadoc) * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection) */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //1.獲得用戶身份信息(PrincipalCollection有認證回調傳來的第一個參數[activeUser]) UserActivePojo activeUser = (UserActivePojo) principalCollection.getPrimaryPrincipal(); System.out.println("doGetAuthorizationInfo"); //2.根據身份信息獲取權限數據 SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); //3.根據用戶查詢用戶的角色 (其實認證方法一並查詢出來了,保存在UserActivePojo) List<String> roles = activeUser.getRoles(); if(null!=roles&&roles.size()>0) { info.addRoles(roles);//添加角色 } //4.根據用戶查詢用戶的權限 List<String> permissions=activeUser.getPermissions(); if(null!=permissions&&permissions.size()>0) { info.addStringPermissions(permissions);//添加權限 } /** * 總結:本來授權->查角色和權限都在本方法寫(但是前端每查詢一次權限,就會回調本方法一次, * 所以直接查數據庫,對數據庫有壓力),所以最后, * 1.把查角色和權限方法寫在了認證,然后封裝成activeUser傳遞過來,這樣Controller每次查權限,就不用查數據庫了,直接在activeUser獲取即可. * 2.緩存應該也可以解決 */ return info; } }
4,測試 (認證和授權)
package com.cc8w.test; import com.cc8w.shiro.ShiroRealm; import org.apache.log4j.Logger; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.UnauthenticatedException; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.apache.shiro.mgt.SecurityManager; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.Arrays; /** * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class TestShiro { private static Logger logger = Logger.getLogger(TestShiro.class); @Autowired private ShiroRealm shiroRealm; public static void main(String[] args) { TestShiro ts = new TestShiro(); ts.testAuth(); } //三,登錄測試(自定義Realm) @Test public void testRealmLogin(){ //1.創建一個安全管理器的工廠 Factory<SecurityManager> factory = new IniSecurityManagerFactory(); //2.在工廠中獲取安全管理器 DefaultSecurityManager securityManager = (DefaultSecurityManager) factory.getInstance(); //2.1 創建自定義Realm注入到安全管理器 //ShiroRealm shiroRealm = new ShiroRealm();//(SpringM在bean控制,xml也可以配置散列加密相關) securityManager.setRealm(shiroRealm); //3.將securityManager綁定到運行環境 SecurityUtils.setSecurityManager(securityManager); //4.獲取Subject對象(將要登錄的用戶) Subject subject = SecurityUtils.getSubject(); //5.獲取要登錄用戶的token,客戶端傳遞過來的用戶名和密碼 String username = "zhangsan",password="123456"; UsernamePasswordToken token = new UsernamePasswordToken(username,password); try{ //6.登陸(認證) subject.login(token); logger.info("登錄了"); }catch (IncorrectCredentialsException e ){ logger.info("密碼不正確"); logger.info(e); }catch (UnknownAccountException e) { System.out.println("沒有這個帳號"); }catch (AuthenticationException e) { e.printStackTrace(); } //如果登錄成功了,可以獲取subject中各種狀態了 Boolean isAuth = subject.isAuthenticated(); System.out.println("認證狀態:" + isAuth); // 7.授權 分為:基於角色授權 基於資源的授權 //7.1 基於角色授權 boolean permited = subject.hasRole("role1"); System.out.println("這是授權單個:"+permited); boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("role1","role2","role3")); System.out.println("這個授權多個"+hasAllRoles); // 使用check方法進行授權,如果授權不通過會拋出異常 // subject.checkRole("role13"); try { subject.checkRole("roles1"); }catch (UnauthenticatedException e){ logger.info("沒有這個角色"); //e.printStackTrace(); }catch (UnauthorizedException e){ logger.info("沒有這個權限"); //e.printStackTrace(); } //7.2 基於資源的授權 //isPermitted傳入權限標識符 boolean isPermitted = subject.isPermitted("user:query"); System.out.println("單個權限判斷:"+isPermitted); boolean isPermittedAll = subject.isPermittedAll("user:query","user:adb","user:add"); System.out.println("多個權限判斷"+isPermittedAll); // 使用check方法進行授權,如果授權不通過會拋出異常 try { subject.checkPermission("user:adb"); }catch (UnauthenticatedException e){ logger.info("沒有這個角色"); //e.printStackTrace(); }catch (UnauthorizedException e){ logger.info("沒有這個權限"); //e.printStackTrace(); } } //四,授權驗證(自定義Realm) public void testRealmAuth(){ //其實授權也需要登陸(上面方法第7條之后:就是授權的驗證) } }
前提是: 已經在service和mapper里面寫了,角色和權限的查詢!! 如下: