一、Shiro介紹
1、可從本教程中明白以下幾個知識點:
- 認識Shiro的整體架構和各組件的概念;
- Shiro認證、授權過程;
- Shiro自定義Realm.
2、Shiro簡介
Shiro是Apache強大靈活的開源的安全框架,有認證、授權、企業會話管理、安全加密、緩存管理等功能。
Shiro和Spring Security相比較;Shiro更加簡單方便、並且可脫離Spring,Spring Security比較笨重復雜,不可脫離Spring。
3、Shiro架構圖
我主要解釋下幾個對象:
Subject :主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是 Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有 Subject 都綁定到 SecurityManager,與 Subject 的所有交互都會委托給 SecurityManager;可以把 Subject 認為是一個門面;SecurityManager 才是實際的執行者;
SecurityManager :安全管理器;即所有與安全有關的操作都會與 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它負責與后邊介紹的其他組件進行交互,如果學習過 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
Realm: 域,Shiro 從從 Realm 獲取安全數據(如用戶、角色、權限),就是說 SecurityManager要驗證用戶身份,那么它需要從 Realm 獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從 Realm 得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把 Realm 看成 DataSource,即安全數據源。
也就是說對於我們而言,最簡單的一個 Shiro 應用:
1、 應用代碼通過 Subject 來進行認證和授權,而 Subject 又委托給 SecurityManager;
2、 我們需要給 Shiro 的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法
的用戶及其權限進行判斷。
建議看不懂的可以先直接看完下面的4個案例,再回頭看看就很明白了。^_^
詳細圖如上面所示:在這里就不介紹具體每個組件了,我會在以下4個實例代碼中詳細說明;
4、項目中用到的依賴:
添加完依賴,以下所有實例都可以直接運行。
二、第一個實例(初識Shiro認證/授權)
1、Shiro認證/授權過程:代碼有詳解
認證原理:創建SecurityManager---->主體提交認證請求----->提交到SecurityManager認證---->SecurityManager認證是由Authenticator進行認證---->Authenticator認證需要通過Realm驗證用戶數據。
授權原理:創建SecurityManager---->主體授權---->提交到SecurityManager授權---->SecurityManager授權是有Authorizer進行授權---->Realm獲取角色權限數據
public class AuthenticationTest { SimpleAccountRealm simpleAccountRealm=new SimpleAccountRealm(); @Before public void getAccount() { //方便測試 新增用戶和角色權限 simpleAccountRealm.addAccount("quentin","123456","admin"); } @Test public void AuthenticationTest() { //1、創建SecurtyManager DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager(); defaultSecurityManager.setRealm(simpleAccountRealm); //2、主體認證/授權 SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject=SecurityUtils.getSubject();//獲得當前正在執行程序的用戶 UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456"); //3、login()主體認證請求,里面封裝好的不用管,它是由SecurityManager認證->Authenticate認證->Realm驗證組成,這樣就可以實現認證。 subject.login(usernamePasswordToken); System.out.println("isAuthen:"+subject.isAuthenticated()); //3、checkRole()主體授權請求,里面封裝好的不用管,它是由SecurityManager授權->Authenticate授權->Realm獲取角色權限組成,這樣就可以實現授權功能。 subject.checkRole("admin"); } }
運行結果:
當驗證不通過會拋出異常,沒有權限也會拋出異常!!
所以,總結一下整個流程,以認證為例:就是在我們調用Subject的login()方法之后,可以看到我傳入的是用戶的token(里面的實際原理不用管,其實就是第一第二步它經歷過一系列的步驟,它調用了Realm中的doGetAuthenticationInfo(token)方法)。
三、第二個實例(IniRealm實例講解)
介紹:通過加載.ini
文件生成realm
對象來驗證
1、在resourses中建立user.ini文件,內容如下:
[users] quentin=123456,admin [roles] admin=user:delete,user:update
設置用戶名"quentin",密碼是"123456",“admin”角色,"admin"擁有“user:delete、user:update”權限。
2、實例文件代碼如下:
public class IniRealmTest { @Test public void IniRealmTest() { IniRealm iniRealm=new IniRealm("classpath:user.ini"); //配置user.ini文件(賬號和權限等信息) //1、創建SecurtyManager DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager(); defaultSecurityManager.setRealm(iniRealm);//設置Realm //2、主體認證/授權請求 SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject=SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456"); subject.login(usernamePasswordToken); System.out.println("isAuth:"+subject.isAuthenticated());//是否認證 //判斷是否授權等 subject.checkRole("admin");//是否有角色權限 subject.checkPermission("user:delete");//是否有刪除權限 } }
運行結果:
說明認證通過(用戶名密碼正確),但是跑出沒有權限異常(因為admin沒有user:deleteall全選)。
四、第三個實例(JdbcRealm實例講解)
介紹:通過獲取數據庫用戶數據來驗證
1、實例代碼如下:
public class JdbcRealmTest { DruidDataSource dataSource=new DruidDataSource(); { dataSource.setUrl("jdbc:mysql://10.0.92.23:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("123"); } @Test public void JdbcRealmTest() { JdbcRealm jdbcRealm=new JdbcRealm(); jdbcRealm.setDataSource(dataSource); String sql="select password from user where name=?"; jdbcRealm.setAuthenticationQuery(sql); //查詢認證語句 String sqlrole="select rolename from role where name=?"; jdbcRealm.setUserRolesQuery(sqlrole); //查詢角色權限語句 //1、創建SecurtyManager DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager(); defaultSecurityManager.setRealm(jdbcRealm); //2、主體認證/授權請求 SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject=SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("aaa","dfdf"); subject.login(usernamePasswordToken); System.out.println("isAuth:"+subject.isAuthenticated());//判斷是有認證 subject.checkRole("admin");//是否有權限 } }
運行結果也一樣就不演示了。其實JdbcRealm里面也提供了默認的sql語句,但是考慮到查詢不一樣,所以需要單獨編寫。
五、第四個實例(自定義Realm及Shiro加密)
1、自定義Realm,分為doGetAuthenticationInfo()認證 和 doGetAuthorizationInfo()授權信息兩部分,注意代碼注釋:
代碼如下:
//自定義Realm,代碼如下: public class CustomerRealm extends AuthorizingRealm { //繼承父類AuthorizingRealm將獲取Subject相關信息分成兩步:獲取身份驗證信息(doGetAuthenticationInfo)及授權信息(doGetAuthorizationInfo); // 原理!!!!!!!!! /* doGetAuthenticationInfo獲取身份驗證相關信息:首先根據傳入的用戶名獲取User信息; * 然后如果user為空,那么拋出沒找到帳號異常UnknownAccountException;如果user找到但鎖定了拋出鎖定異常LockedAccountException; * 最后生成AuthenticationInfo信息,交給間接父類AuthenticatingRealm使用CredentialsMatcher進行判斷密碼是否匹配,如果不匹配將拋出密碼錯誤異常IncorrectCredentialsException; * 另外如果密碼重試此處太多將拋出超出重試次數異常ExcessiveAttemptsException; * 在組裝SimpleAuthenticationInfo信息時,需要傳入:身份信息(用戶名)、憑據(密文密碼)、鹽(username+salt),CredentialsMatcher使用鹽加密傳入的明文密碼和此處的密文密碼進行匹配。 * */ @Override //用於當前用戶驗證。認證login()方法執行會自動調用 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //1、從主體傳過來的認證信息中,獲取用戶名 String UserName=(String)token.getPrincipal(); // getPrincipal();身份 | getCredentials();憑據 //2、通過用戶名從數據庫中獲取密碼憑證 String Password=getPasswordByUsername(UserName); if(Password==null) { return null; } //組裝SimpleAuthenticationInfo信息,(用戶名、密文密碼、鹽) SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo("quentin",Password,"customerReal"); simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("salt")); //將鹽注冊到信息中去 return simpleAuthenticationInfo; } //方便測試 設置用戶數據 Map<String,String> userMap=new HashMap<String, String>(16); { //將帶鹽"salt"也一起加密 Md5Hash passMD5=new Md5Hash("123456","salt"); userMap.put("quentin",passMD5.toString()); super.setName("customerReal"); } private String getPasswordByUsername(String userName) { return userMap.get(userName); } //----------------------------------------------------------------以上是認證,以下是授權------------------------------------------------------------------------------------------------------ //doGetAuthorizationInfo獲取授權信息:PrincipalCollection是一個身份集合,因為我們現在就一個Realm,所以直接調用getPrimaryPrincipal得到之前傳入的用戶名即可; // 然后根據用戶名調用UserService接口獲取角色及權限信息。 @Override //用於當前登錄用戶授權,用戶的角色與權限初始化,授權會自動調用 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //獲取用戶名 String UserName=(String)principals.getPrimaryPrincipal(); //getPrimaryPrincipal()得到主要的身份 Set<String> getRealmNames(); //獲取所有身份驗證通過的Realm名字 //獲取角色和權限信息(一般從數據庫或緩存中獲取) Set<String> setRoles=getRolesByUserName(); Set<String> setPression=getPressionByUserName(); //設置SimpleAuthorizationInfo信息,(角色和權限) SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setRoles(setRoles); simpleAuthorizationInfo.setStringPermissions(setPression); return simpleAuthorizationInfo; } //方便測試 設置用戶權限數據 private Set<String> getPressionByUserName() { Set<String> sets=new HashSet<String>(); sets.add("user:add"); sets.add("user:delete"); sets.add("user:select"); sets.add("admin:select"); return sets; } //方便測試 設置用戶角色數據 private Set<String> getRolesByUserName() { Set<String> sets=new HashSet<String>(); sets.add("admin"); sets.add("user"); return sets; } }
2、實例運行代碼如下:
public class CustomerRealmTest { @Test public void CustomerRealmTest() { //引入自定義Realm CustomerRealm customerRealm=new CustomerRealm(); //創建DefaultSecurityManager DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager(); defaultSecurityManager.setRealm(customerRealm); //設置算法名稱和加密次數 HashedCredentialsMatcher matcher=new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("md5"); matcher.setHashIterations(1); customerRealm.setCredentialsMatcher(matcher); //主體認證,得到SecurityManager實例 並綁定給SecurityUtils SecurityUtils.setSecurityManager(defaultSecurityManager); //得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject=SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456"); //登錄,即身份驗證 subject.login(usernamePasswordToken); System.out.println("isAuth:"+subject.isAuthenticated()); subject.checkRole("admin"); subject.checkPermission("admin:select"); } }
運行結果:
單獨解釋下Shiro加密:
在自定義Realm文件中
Md5Hash passMD5=new Md5Hash("123456","salt");
userMap.put("quentin",passMD5.toString());
//由上面代碼也能看到解釋,其實就是將帶鹽"salt"也一起加密,作為身份驗證的密碼信息
+
simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("salt")); //將鹽注冊到信息中去
//將鹽"salt"注冊到信息中去驗證
=
這樣就更加安全。
通過上面4個實例,動手敲一敲,相信你們都能快速了解Shiro認證授權原理了。