一、什么是shiro
它是一個功能強大且易於使用的Java安全框架,可以執行身份驗證、授權、加密和會話管理。使用Shiro易於理解的API,您可以快速且輕松地保護任何應用程序——從最小的移動應用程序到最大的web和企業應用程序。
二、shiro的主要功能
1、Authentication:身份認證
2、Authorization:權限校驗
3、SessionManager:會話管理,用戶從登錄到退出是一次會話,所有的信息都保存在會話中。普通的java se環境中也支持這種會話。
4、cryptography:數據加密,如對用戶密碼進行加密,避免將密碼明文存入數據庫中。
5、Web support:非常容易集成到web環境中。
6、Caching:緩存,將用戶信息和角色權限等緩存起來,不必每次去查
7、Concurrency:支持多線程,在一個線程中開啟新的線程,能把權限傳過去。
8、Testing:提供測試功能
9、Run As:允許一個用戶假裝為另一個用戶的身份進行訪問。
10、Remember me: 記住用戶,一次登錄后下次不用登錄。
三、核心的概念以及架構圖
1、subject(主題):代表了正在操作的當前“用戶”,這個用戶並不一定是人也也可以指第三方進程、守護進程帳戶或任何類似的東西。一旦您獲得了主題,您就可以立即訪問當前用戶使用Shiro想要做的90%的事情,比如登錄、注銷、訪問他們的會話、執行授權檢查等。
2、securityManager(安全管理器):它是shiro的核心,所有與安全有關的操作都會與securityManager進行交互。
3、realm(數據域):充當Shiro和應用程序安全數據之間的“橋梁”,當實際需要與與安全相關的數據(如用戶帳戶)進行交互以執行身份驗證(登錄)和授權(訪問控制)時,Shiro會從一個或多個為應用程序配置的領域中查找這些內容。realm可以有多個,但是至少得有一個。Shiro提供了開箱即用的realm,可以連接到許多安全數據源(即目錄),如LDAP、關系數據庫(JDBC)、文本配置源(如INI)和屬性文件,等等。如果默認的realm不滿足您的需求,可以添加您自己的領域實現來表示自定義數據源。
4、Authenticator:認證器,負責主體認證的,這是一個擴展點,如果用戶覺得 Shiro 默認的 不好,可以自定義實現;其需要認證策略(Authentication Strategy),即什么情況下算用戶 認證通過了
5、Authrizer:授權器,或者訪問控制器,用來決定主體是否有權限進行相應的操作;即控制 着用戶能訪問應用中的哪些功能;
6、SessionManager:如果寫過 Servlet 就應該知道 Session 的概念,Session 呢需要有人去管理 它的生命周期,這個組件就是 SessionManager;而 Shiro 並不僅僅可以用在 Web 環境,也 可以用在如普通的 JavaSE 環境、EJB 等環境;所有呢,Shiro 就抽象了一個自己的 Session 來管理主體與應用之間交互的數據;這樣的話,比如我們在 Web 環境用,剛開始是一台 Web 服務器;接着又上了台 EJB 服務器;這時想把兩台服務器的會話數據放到一個地方, 這個時候就可以實現自己的分布式會話(如把數據放到 Memcached 服務器);
7、SessionDAO:DAO 大家都用過,數據訪問對象,用 於 會話的 CRUD,比如我們想把 Session 保存到數據庫,那么可以實現自己的 SessionDAO,通過如 JDBC 寫到數據庫;比如想把 Session 放到 Memcached 中,可以實現自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 進行緩存,以提高性能;
8、CacheManager:緩存控制器,來管理如用戶、角色、權限等的緩存的;因為這些數據基本 上很少去改變,放到緩存中后可以提高訪問的性能
9、Cryptography:密碼模塊,Shiro 提高了一些常見的加密組件用於如密碼加密/解密的
四、身份認證
使用principals(身份,也就是賬號)和credentials(證明,密碼或者證書)來對用戶進行認證。
1、身份認證的流程圖
2、身份認證的具體流程
2.1、首先調用 Subject.login(token)進行登錄,其會自動委托給 Security Manager,調用之前必 須通過 SecurityUtils. setSecurityManager()設置。
2.2、SecurityManager 負責真正的身份驗證邏輯;它會委托給 Authenticator 進行身份驗證;
2.3、Authenticator 才是真正的身份驗證者,Shiro API 中核心的身份認證入口點,開發者可以自 定義插入自己的實現;
2.4、Authenticator 可能會委托給相應的 AuthenticationStrategy 進行多 Realm 身份驗證,默認 ModularRealmAuthenticator 會調用 AuthenticationStrategy 進行多 Realm 身份驗證;
2.5、Authenticator 會把相應的 token 傳入 Realm,從 Realm 獲取身份驗證信息,如果沒有返 回/拋出異常表示身份驗證失敗了。此處可以配置多個 Realm,將按照相應的順序及策略進 行訪問。
五、授權
1、授權相關的幾個關鍵對象:
1.1、主體(Subject):在 Shiro 中使用 Subject 代表該用戶。用戶只有授權后才允許訪 問相應的資源
1.2、資源(Resource):系統中所有可以訪問的東西,如頁面、按鈕、鏈接、數據等
1.3、權限(Permission):安全策略中的原子授權單位,通過權限我們可以表示在應用中用戶有沒有操作某個資源的 權力。即權限表示在應用中用戶能不能訪問某個資源
1.4、角色(Role):可以理解為權限集合,一般情況下我們會賦予用戶角色而不是權 限,即這樣用戶可以擁有一組權限,賦予權限時比較方便
2、授權流程圖
3、授權流程詳細
3.1、首先調用 Subject.isPermitted*/hasRole*接口,其會委托給 SecurityManager,而 SecurityManager 接着會委托給 Authorizer
3.2、Authorizer 是真正的授權者,如果我們調用如 isPermitted(“user:view”),其首先會通過 PermissionResolver 把字符串轉換成相應的 Permission 實例
3.3、在進行授權之前,其會調用相應的 Realm 獲取 Subject 相應的角色/權限用於匹配傳入的 角色/權限
3.4、Authorizer 會判斷 Realm 的角色/權限是否和傳入的匹配,如果有多個 Realm,會委托給 ModularRealmAuthorizer 進行循環判斷,如果匹配如 isPermitted*/hasRole*會返回 true,否 則返回 false 表示授權失敗
4、ModularRealmAuthorizer 進行多 Realm 匹配流程:
4.1、首先檢查相應的 Realm 是否實現了實現了 Authorizer;
4.2、如果實現了 Authorizer,那么接着調用其相應的 isPermitted*/hasRole*接口進行匹配;
4.3、如果有一個 Realm 匹配那么將返回 true,否則返回 false;
5、如果 Realm 進行授權的話,應該繼承 AuthorizingRealm,其流程是:
5.1、如果調用 hasRole(),則直接獲取 AuthorizationInfo.getRoles()與傳入的角色比較即可;
5.2、首先如果調用如 isPermitted(“user:view”),首先通過 PermissionResolver 將權限字符串 轉換成相應的 Permission 實例,默認使用 WildcardPermissionResolver,即轉換為通配符的 WildcardPermission
5.3、 通 過 AuthorizationInfo.getObjectPermissions() 得 到 Permission 實 例 集 合 ;通過 AuthorizationInfo. getStringPermissions()得到字符串集合並通過 PermissionResolver 解析為 Permission 實例;然后獲取用戶的角色,並通過 RolePermissionResolver 解析角色對應的權 限集合(默認沒有實現,可以自己提供);通過上面的三種情況獲取了所有的權限集合。
5.4、接着調用 Permission. implies(Permission p)逐個與傳入的權限比較,如果有匹配的則返回 true,否則 false;
六、編碼與加密
Shiro 提供了 base64 和 16 進制字符串編碼/解碼的 API 支持Shiro,同時也提供了多種散列算法加密:MD5,sha等和對稱加密算法
下面是一些具體的操作
public class DecodeAndEncode { public static void main(String[] args) { String source = "hello world!"; String salt = "salt"; //base64編碼和解碼 String base64Str = Base64.encodeToString(source.getBytes()); String str1 = Base64.decodeToString(base64Str.getBytes()); System.out.println(base64Str); System.out.println(str1); //16進制編解碼 String hexEncoded = Hex.encodeToString(source.getBytes()); String str2 = new String(Hex.decode(hexEncoded.getBytes())); System.out.println(hexEncoded); System.out.println(str2); //使用sha1散列算法對源字符串加鹽並散列兩次,得到長度為40的字符串 String sha1Hash = new Sha1Hash(source, salt, 2).toString(); System.out.println(sha1Hash); //sha256散列算法對源字符串加鹽並散列兩次,得到長度為64的字段 String sha256Hash = new Sha256Hash(source, salt,2).toString(); System.out.println(sha256Hash); //sha384散列算法對源字符串加鹽並散列兩次,得到長度為96的字段 String sha384Hash = new Sha384Hash(source, salt,2).toString(); System.out.println(sha384Hash); //sha512散列算法對源字符串加鹽並散列兩次,得到長度為128的字符串 String sha512Hash = new Sha512Hash(source, salt, 2).toString(); System.out.println(sha512Hash); //使用MD5來對源字符串進行加鹽加密,並得到長度為32的字符串 String md5Hash = new Md5Hash(source, salt,2).toString(); System.out.println(md5Hash); //使用通用的加密方法來對字符串進行加密 String sha1 = new SimpleHash("SHA-1", source, salt, 2).toString(); String sha256 = new SimpleHash("SHA-256", source, salt, 2).toString(); String sha512 = new SimpleHash("SHA-512", source, salt, 2).toString(); String md5 = new SimpleHash("MD5", source, salt, 2).toString(); System.out.println(sha256); System.out.println(md5); System.out.println(sha1); System.out.println(sha512); //自動生成隨機數 SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator(); randomNumberGenerator.setSeed("abc".getBytes()); String hex = randomNumberGenerator.nextBytes().toHex(); String string = randomNumberGenerator.nextBytes().toString(); System.out.println(hex); System.out.println(string); String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex(); System.out.println(salt2); //shiro對稱加密 AesCipherService aesCipherService = new AesCipherService(); //設置 生成的key長度 aesCipherService.setKeySize(128); //生成 key Key key = aesCipherService.generateNewKey(); String text = "hello"; //加密 String encrptText = aesCipherService.encrypt(text.getBytes(),key.getEncoded()).toHex(); //解密 String text2 = new String(aesCipherService.decrypt(Hex.decode(encrptText), key.getEncoded()).getBytes()); System.out.println(encrptText); System.out.println(text2); } }
七、Realm及相關對象
1、AuthenticationToken :用於收集用戶提交的身份及憑據。
2、AuthenticationInfo :存儲賬號信息用於登錄身份驗證
3、PrincipalCollection :存儲用戶的身份信息
4、AuthorizationInfo :用來存儲用戶的授權信息,如角色、權限等。用於授權驗證。
5、Subject :Subject 是 Shiro 的核心對象,基本所有身份驗證、授權都是通過 Subject 完成
subject.getPrincipal() //獲取用戶賬號信息 subject.isAuthenticated() //是否已經通過身份認證也就是login成功。 isAuthenticated/isRemembered 是互斥 的,即如果其中一個返回 true,另一個返回 false。 subject.isRemembered() //如果返回true表示是通過記住我這個功能登錄的而不是通過login登錄的 subject.hasRole("aaa") //是否有該角色 subject.isPermitted("bbb") //是否具有該權限 subject.getSession() //獲取session對象
八、非web情況下shiro簡單使用代碼認證和授權代碼
1、配置文件版
1.1、配置文件
[main] #authenticator authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy authenticator.authenticationStrategy=$authenticationStrategy securityManager.authenticator=$authenticator #authorizer authorizer=org.apache.shiro.authz.ModularRealmAuthorizer permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver authorizer.permissionResolver=$permissionResolver securityManager.authorizer=$authorizer #CredentialsMatcher credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher credentialsMatcher.hashAlgorithmName=md5 credentialsMatcher.hashIterations=2 credentialsMatcher.storedCredentialsHexEncoded=true #設置realm myRealm=com.it.shiro.MyRealm2 myRealm.credentialsMatcher=$credentialsMatcher #同時使用多個realm,inirealm是shiro自定義的配置文件realm securityManager.realms=$myRealm,$iniRealm [users] #配置用戶名/密碼及其角色,格式:“用戶名=密碼,角色 1,角色 2”,角色部分可省略。 kyle=19912,admin,employee,employee11 [roles] #配置角色及權限之間的關系,格式:“角色=權限 1,權限 2” admin=system:*:*,user employee=role:delete [urls] #用於 web,提供了對 web url 攔截相關的配置,url=攔截器[參數],攔截器
1.2、自定義realm
public class MyRealm2 extends AuthorizingRealm { @Override public String getName() { return "myRealm2"; } @Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //賬號 //String account = SecurityUtils.getSubject().getPrincipal().toString(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //模擬從數據庫中獲取角色,並設置到AuthorizationInfo中 List<String> roles = new ArrayList<>(); roles.add("admin"); roles.add("manager"); roles.add("employee"); info.addRoles(roles); //模擬從數據庫中獲取權限 List<String> permissions = new ArrayList<>(); permissions.add("aaa"); permissions.add("bbb"); info.addStringPermissions(permissions); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //用戶輸入的賬號 String account = (String)token.getPrincipal(); //用戶輸入的密碼 //String password = new String((char[])token.getCredentials()); //這是模擬從數據庫中查詢出來的密碼 String pwd ="a467b0ccf40fb5d371b5e6f2d6d7fe44"; //模擬數據庫中保存的鹽 String salt = "1991"; //對用戶身份進行校驗 return new SimpleAuthenticationInfo(account,pwd,ByteSource.Util.bytes(account+salt),getName()); } }
1.3、測試使用
//使用配置文件來進行初始化 public class TestShiro2 { public static void main(String[] args) { //先通過ini對象創建securityManager工廠 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro2.ini"); //通過工廠獲取securityManager SecurityManager securityManager = factory.getInstance(); //將securityManager綁定到SecurityUtils SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("kyle","1991"); //設置記住我 token.setRememberMe(true); try{ //登錄,進行身份驗證 subject.login(token); System.out.println("登錄成功"); }catch(AuthenticationException e){ System.out.println("登錄失敗"); } System.out.println(subject.getPrincipal());//身份信息的獲取 //是否已經通過身份認證也就是login成功。 isAuthenticated/isRemembered 是互斥 的,即如果其中一個返回 true,另一個返回 false。 System.out.println(subject.isAuthenticated()); System.out.println(subject.isRemembered());//如果返回true表示是通過記住我這個功能登錄的而不是通過login登錄的 System.out.println(subject.hasRole("aaa"));//是否有該角色 System.out.println(subject.isPermitted("bbb"));//是否具有該權限 System.out.println(subject.getSession());//獲取session對象 System.out.println(subject.hasRole("admin")); System.out.println(subject.hasRole("manager")); System.out.println(subject.hasRole("employee")); System.out.println(subject.hasRole("employee11")); System.out.println(subject.isPermitted("system:add:*")); System.out.println(subject.isPermitted("user:add:1")); System.out.println(subject.isPermitted("role:delete:1")); System.out.println(subject.isPermitted("role:add")); //退出登錄 subject.logout(); } }
2、不使用配置文件
2.1、自定義realm
public class MyRealm extends AuthorizingRealm { public MyRealm(){ this.setCredentialsMatcher(credentialsMatcher()); } @Override public String getName() { return "myRealm"; } @Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //賬號 //String account = SecurityUtils.getSubject().getPrincipal().toString(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //模擬從數據庫中獲取角色,並設置到AuthorizationInfo中 List<String> roles = new ArrayList<>(); roles.add("admin"); roles.add("manager"); roles.add("employee"); info.addRoles(roles); //模擬從數據庫中獲取權限 List<String> permissions = new ArrayList<>(); permissions.add("aaa"); permissions.add("bbb"); info.addStringPermissions(permissions); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //用戶輸入的賬號 String account = (String)token.getPrincipal(); //用戶輸入的密碼 //String password = new String((char[])token.getCredentials()); //這是模擬從數據庫中查詢出來的密碼 String pwd ="a467b0ccf40fb5d371b5e6f2d6d7fe44"; //模擬數據庫中保存的鹽 String salt = "1991"; //對用戶身份進行校驗 return new SimpleAuthenticationInfo(account,pwd,ByteSource.Util.bytes(account+salt),getName()); } private HashedCredentialsMatcher credentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("md5"); credentialsMatcher.setHashIterations(2); credentialsMatcher.setStoredCredentialsHexEncoded(true); return credentialsMatcher; } }
2.2、測試使用
public class TestShiro { public static void main(String[] args) { //創建SecurityManager DefaultSecurityManager securityManager = new DefaultSecurityManager(); /**設置身份認證器和驗證策略,ModularRealmAuthenticator為shiro默認認證器 * ModularRealmAuthenticator 默認使用 AtLeastOneSuccessfulStrategy 策略 * FirstSuccessfulStrategy:只要有一個 Realm 驗證成功即可,只返回第一個 Realm 身份驗證 成功的認證信息,其他的忽略; * AtLeastOneSuccessfulStrategy:只要有一個 Realm 驗證成功即可,和 FirstSuccessfulStrategy 不同,返回所有 Realm 身份驗證成功的認證信息; * AllSuccessfulStrategy:所有 Realm 驗證成功才算成功,且返回所有 Realm 身份驗證成功的 認證信息,如果有一個失敗就失敗了 */ ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); securityManager.setAuthenticator(authenticator); //設置 authorizer授權 ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer(); authorizer.setPermissionResolver(new WildcardPermissionResolver()); securityManager.setAuthorizer(authorizer); //設置自己定義的realm securityManager.setRealm(new MyRealm()); //將securityManager綁定到SecurityUtils SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("kyle","1991"); //設置記住我 token.setRememberMe(true); try{ //登錄,進行身份驗證 subject.login(token); System.out.println("登錄成功"); }catch(AuthenticationException e){ System.out.println("登錄失敗"); } System.out.println(subject.hasRole("admin")); System.out.println(subject.hasRole("manager")); System.out.println(subject.hasRole("employee")); System.out.println(subject.hasRole("employee11")); System.out.println(subject.isPermitted("aaa")); System.out.println(subject.isPermitted("bbb")); //退出登錄 subject.logout(); } }
八、shiro和web集成
1、先引入jar包
dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency>
2、配置web.xml
<listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener> <filter> <filter-name>iniShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class> <init-param> <param-name>configPath</param-name> <param-value>classpath:shiro.ini</param-value> </init-param> </filter> <filter-mapping> <filter-name>iniShiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3.編寫shiro.ini配置文件
[main] #沒有登錄就跳轉到index.html頁面 authc.loginUrl=/index.html #角色校驗沒有通過就跳轉到rf.html頁面 roles.unauthorizedUrl=/rf.html #權限校驗沒有通過跳轉到pf.html頁面 perms.unauthorizedUrl=/pf.html [users] #配置用戶名/密碼及其角色,格式:“用戶名=密碼,角色 1,角色 2”,角色部分可省略。 kyle=19912,admin,employee,employee11 [roles] #配置角色及權限之間的關系,格式:“角色=權限 1,權限 2” admin=system:*:*,user employee=role:delete [urls] #用於 web,提供了對 web url 攔截相關的配置,url=攔截器[參數],攔截器 ,攔截器按照順序攔截,從上到下匹配。 /login=anon /logout=logout /getUserData=authc,roles[abcde] /getDeptData=authc,perms["dept:get"] /**=authc
4、編寫java代碼
@WebServlet("/login") public class LoginController extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String account = request.getParameter("account"); String pwd = request.getParameter("pwd"); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(account,pwd); String result = null; try{ subject.login(token); result = "登錄成功"; }catch(UnknownAccountException e){ result = "賬號或者密碼錯誤"; }catch(IncorrectCredentialsException e){ result = "賬號或者密碼錯誤"; } //response.getWriter().append("Served at: ").append(request.getContextPath()); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); response.getWriter().append(result); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
九、shiro攔截器的機制
1、Shiro 對 Servlet 容器的 FilterChain 進行了代理,即 ShiroFilter 在繼續 Servlet 容器的 Filter 鏈的執行之前,通過 ProxiedFilterChain 對 Servlet 容器的 FilterChain 進行了代理。;Shiro 的 ProxiedFilterChain 執行流程:先執行 Shiro 自己的 Filter 鏈; 再執行 Servlet 容器的 Filter 鏈(即原始的 Filter)。
2、shiro中攔截器類型介紹:
2.1、OncePerRequestFilter :OncePerRequestFilter 用於防止多次執行 Filter 的;也就是說一次請求只會走一次攔截器鏈; 另外提供 enabled 屬性,表示是否開啟該攔截器實例,默認 enabled=true 表示開啟,如果不 想讓某個攔截器工作,可以設置為 false 即可。
2.2、AdviceFilter :AdviceFilter 提供了 AOP 風格的支持,類似於 SpringMVC 中的 Interceptor
2.3、PathMatchingFilter :提供了基於 Ant 風格的請求路徑匹配功能及攔截器參數解析的功能,如 “roles[admin,user]”自動根據“,”分割解析到一個路徑參數配置並綁定到相應的路徑
2.4、AccessControlFilter :提供了訪問控制的基礎功能;比如是否允許訪問/當訪問拒絕時如何處理 等
3、自定義攔截器:通過自定義自己的攔截器可以擴展一些功能,諸如動態 url-角色/權限訪問控制的實現、根 據 Subject 身份信息獲取用戶信息綁定到 Request(即設置通用數據)、驗證碼驗證、在線 用戶信息的保存等等,因為其本質就是一個 Filter;所以 Filter 能做的它就能做。如果要自定義攔截器只需要繼承上面第二點提到的攔截器並實現其中的方法並在配置文件中使用該攔截器即可。
[filters] myfilter=com.kyle.MyFilter [urls] /test=myfilter
10 、shiro的緩存機制
10.1、先引入jar包
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.11</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency>
10.2、編寫配置文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 溢出到磁盤的目錄 --> <diskStore path="java.io.tmpdir"/> <!-- name:緩存名稱。 maxElementsInMemory:緩存最大個數。 eternal:對象是否永久有效,一但設置了,timeout將不起作用。 timeToIdleSeconds:設置對象在失效前的允許閑置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閑置時間無窮大。 timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)。最大時間介於創建時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。 overflowToDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。 diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩沖區。 maxElementsOnDisk:硬盤最大緩存個數。 diskPersistent:是否緩存虛擬機重啟期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。 memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置為FIFO(先進先出)或是LFU(較少使用)。 clearOnFlush:內存數量最大時是否清除。 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> </ehcache>
10.3、編寫CacheManager
public class MyShiroCacheManager extends EhCacheManager { public MyShiroCacheManager() { setCacheManager(net.sf.ehcache.CacheManager.create()); } }
10.4、在shiro.ini中進行配置
[main] authc.loginUrl=/index.html roles.unauthorizedUrl=/rf.html perms.unauthorizedUrl=/pf.html myRealm=testShiro.MyRealm #realm啟用緩存 myRealm.cachingEnabled=true #啟用身份驗證緩存,即緩存 AuthenticationInfo 信 息,默認 false myRealm.authenticationCachingEnabled=true #緩存 AuthenticationInfo 信息的緩存名稱 myRealm.authenticationCacheName=authenticationCache #啟用授權緩存,即緩存 AuthorizationInfo 信息 myRealm.authorizationCachingEnabled=true #緩存 AuthorizationInfo 信息的緩存名稱 myRealm.authorizationCacheName=authorizationCache securityManager.realms=$myRealm,$iniRealm myShiroCacheManager=testShiro.MyShiroCacheManager myShiroCacheManager.cacheManagerConfigFile=classpath:ehcache.xml securityManager.cacheManager=$myShiroCacheManager [users] #配置用戶名/密碼及其角色,格式:“用戶名=密碼,角色 1,角色 2”,角色部分可省略。 kyle=1991,admin,employee,employee11 [roles] #配置角色及權限之間的關系,格式:“角色=權限 1,權限 2” admin=system:*:*,user employee=role:delete [urls] #用於 web,提供了對 web url 攔截相關的配置,url=攔截器[參數],攔截器 /login=anon /logout=logout /getUserData=authc,roles[employee11] /getUserData2=authc,roles[abcde] /**=authc
11、shiro與spring整合
10.1、如果不需要整合到spring的框架中,直接就和普通的web項目一樣組合在一起就行。
10.2、如果需要spring來幫忙進行對象的管理
10.2.1、引入jar包
<!-- 引入spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring context的擴展支持,用於MVC方面 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <!-- 引入springaop相關的jar包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectjweaver.version}</version> </dependency> <!-- shiro相關的jar包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.11</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
10.2.2、spring-mvc.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <!-- 開啟注解掃描 --> <mvc:annotation-driven /> <!-- 設置注解掃描的包 --> <context:component-scan base-package="com.kyle.controller"> <!-- 可以設置掃描哪些包或者注解 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 激活代理功能,實現全局日志, 之所以在這個配置文件中配置是因為springmvc的子容器能夠獲取父容器中的 對象,反過來卻不行。 --> <aop:aspectj-autoproxy/> </beans>
10.2.3、spring-shiro.xml 配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <!-- 定義緩存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <!-- 定義realm --> <bean id="myRealm" class="com.kyle.shiro.MyRealm"> <property name="cachingEnabled" value="true"/> <property name="authenticationCachingEnabled" value="true"/> <property name="authenticationCacheName" value="authenticationCache"/> <property name="authorizationCachingEnabled" value="true"/> <property name="authorizationCacheName" value="authorizationCache"/> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realms"> <list><ref bean="myRealm"></ref></list> </property> <property name="cacheManager" ref="cacheManager"></property> </bean> <!-- 將 securityManager設置到SecurityUtils--> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> <property name="arguments" ref="securityManager"/> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 配置過濾器,可以不需要 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login/index"/> <property name="unauthorizedUrl" value="/login/index2"/> <property name="filterChainDefinitions"> <value> /login/login = anon /login/logout = logout /user/get = authc,roles[manager1] /user/list = authc,roles[admin] /** = anon </value> </property> </bean> <!-- 開啟注解權限需要有aop的支持 --> <aop:config proxy-target-class="true"></aop:config> <bean class=" org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> </beans>
10.2.4、web.xml配置
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <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> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-shiro.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
10.2.5、權限注解
@RequiresAuthentication :表示當前 Subject 已經通過 login 進行了身份驗證;即 Subject. isAuthenticated()返回 true
@RequiresUser :表示當前 Subject 已經身份驗證或者通過記住我登錄的
@RequiresGuest :表示當前 Subject 沒有身份驗證或通過記住我登錄過,即是游客身份
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示當前 Subject 需要角色 admin 和 user
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR) :表示當前 Subject 需要權限 user:a 或 user:b