[shiro] - 怎样使用shiro?
?shiro是什么?
Shiro是apache旗下的一個(gè)開(kāi)源框架, 它將軟件系統(tǒng)的安全認(rèn)證相關(guān)的功能抽取出來(lái), 實(shí)現(xiàn)用戶(hù)身份認(rèn)證, 權(quán)限授權(quán), 加密, 會(huì)話管理等功能, 組成一個(gè)通用的安全認(rèn)證框架.
為什么用它?
使用shiro就可以非常快速地完成認(rèn)證,授權(quán)等功能的開(kāi)發(fā),降低系統(tǒng)成本時(shí)間.
shiro使用廣泛,shiro可以運(yùn)行在web應(yīng)用,非web應(yīng)用,集群分布式應(yīng)用中越來(lái)越多的用戶(hù)開(kāi)始使用shiro。
模塊組成
Subject
Subject即主體,外部應(yīng)用與subject進(jìn)行交互,subject記錄了當(dāng)前操作用戶(hù),將用戶(hù)的概念理解為當(dāng)前操作的主體,可能是一個(gè)通過(guò)瀏覽器請(qǐng)求的用戶(hù),也可能是一個(gè)運(yùn)行的程序.
Subject在shiro中是一個(gè)接口,接口中定義了很多認(rèn)證授權(quán)相關(guān)的方法,外部程序通過(guò)subject進(jìn)行認(rèn)證授權(quán),而subject是通過(guò)SecurityManager安全管理器進(jìn)行認(rèn)證授權(quán).
SecurityManager
SecurityManager即安全管理器,對(duì)全部的subject進(jìn)行安全管理,它是shiro的核心,負(fù)責(zé)對(duì)所有subject進(jìn)行安全管理, 通過(guò)SecurityManager可以完成subject的認(rèn)證,授權(quán)等,
實(shí)質(zhì)上SecurityManager是通過(guò)Authenticator進(jìn)行認(rèn)證,通過(guò)Authorizer進(jìn)行授權(quán),通過(guò)SessionManager進(jìn)行會(huì)話管理等.
SecurityManager是一個(gè)接口,繼承了Authenticator,Authorizer,SessionManager這三個(gè)接口.
Authenticator
Authenticator即認(rèn)證器,對(duì)用戶(hù)身份進(jìn)行認(rèn)證,Authenticator是一個(gè)接口,shiro提供ModularRealmAuthenticator實(shí)現(xiàn)類(lèi),通過(guò)ModularRealmAuthenticator基本上可以滿足
大多數(shù)需求, 也可以自定義認(rèn)證器.
Authorizer
Authorizer即授權(quán)器,用戶(hù)通過(guò)認(rèn)證器認(rèn)證通過(guò),在訪問(wèn)功能時(shí)需要通過(guò)授權(quán)器判斷用戶(hù)是否有此功能的操作權(quán)限.
Realm
Realm即領(lǐng)域,相當(dāng)于datasource數(shù)據(jù)源,securityManager進(jìn)行安全認(rèn)證需要通過(guò)Realm獲取用戶(hù)權(quán)限數(shù)據(jù),比如: 如果用戶(hù)身份數(shù)據(jù)在數(shù)據(jù)庫(kù)那么realm就需要從
數(shù)據(jù)庫(kù)獲取用戶(hù)身份信息.
注意: 不要把realm理解成只是從數(shù)據(jù)源取數(shù)據(jù), 在realm中還有認(rèn)證權(quán)限校驗(yàn)的相關(guān)的代碼.
sessionManager
sessionManager即會(huì)話管理,shiro框架定義了一套會(huì)話管理, 它不依賴(lài)web容器的session, 所以shiro可以使用在非web應(yīng)用上,也可以將分布式應(yīng)用的會(huì)話集中
在一點(diǎn)管理,此特性可使它實(shí)現(xiàn)單點(diǎn)登錄.
SessionDAO
SessionDAO即會(huì)話dao,使對(duì)session會(huì)話操作的一套接口, 比如要將session存儲(chǔ)到數(shù)據(jù)庫(kù), 可以通過(guò)jdbc將會(huì)話存儲(chǔ)到數(shù)據(jù)庫(kù).
CacheManager
CacheManager即緩存管理,將用戶(hù)權(quán)限數(shù)據(jù)存儲(chǔ)在緩存,這樣可以提高性能.
Cryptography
Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開(kāi)發(fā). 比如提供常用的散列, 加/解密等功能.
?
下面這個(gè)表是某系統(tǒng)的所有可操控菜單的數(shù)據(jù),存儲(chǔ)了對(duì)應(yīng)的訪問(wèn)路徑.
下面的是角色表:表名了哪個(gè)id干什么事,比如id為4時(shí),是只能使用采購(gòu)員的事情,而不能修改管理員密碼的.因?yàn)槟鞘窍到y(tǒng)管理員的操作.(1)
下面這個(gè)表是角色操控菜單表: 即哪些角色(id)操作哪些菜單menu(id)
?跟左側(cè)的id自增無(wú)關(guān),我們查看menu_id對(duì)應(yīng)的role_id. 這兩張表(menu,role)在上面都已經(jīng)存在了,即表示通過(guò)這個(gè)role-menu表進(jìn)行哪個(gè)角色可操作哪些菜單的設(shè)定.
菜單menu表中有對(duì)應(yīng)的url, 將可以訪問(wèn)的角色進(jìn)行放行,否則進(jìn)行攔截. (在前端頁(yè)面直接不予顯示)
?
下面是用戶(hù)表,沒(méi)有什么奇特的地方,我們?cè)跈?quán)限中將使用到它們的id
下面這個(gè)是比較關(guān)鍵的用戶(hù)角色表,該表定義了哪個(gè)用戶(hù)它屬于什么角色.可以看到有的用戶(hù)分飾多角,比如上面表的主管王大錘分飾了role(2,4,5){主管,采購(gòu)員,銷(xiāo)售經(jīng)理}
而王大錘可以操作哪些菜單?通過(guò)t-role-menu就可以查看到了.具體菜單是在哪些路徑呢?通過(guò)再查詢(xún)t_menu就可以獲取url字段了
?
?
相關(guān)博客1
相關(guān)博客2
相關(guān)博客3
?
|
|
?
新建一個(gè)springboot項(xiàng)目,引入shiro依賴(lài)
<!--shiro權(quán)限--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.4.0</version></dependency><!--shiro權(quán)限--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency>shiro相關(guān)博客
shiro相關(guān)博客
shiro論壇
github相關(guān)demo
?
簡(jiǎn)單實(shí)用shiro
新建project
需要導(dǎo)入shiro-all的jar包
創(chuàng)建的shiro.ini為shiro的配置文件
shiro.ini內(nèi)容:
#定義用戶(hù) [users] #用戶(hù)名 zhang3 密碼是 12345, 角色是 admin zhang3 = 12345, admin #用戶(hù)名 li4 密碼是 abcde, 角色是 產(chǎn)品經(jīng)理 li4 = abcde,productManager #定義角色 [roles] #管理員什么都能做 admin = * #產(chǎn)品經(jīng)理只能做產(chǎn)品管理 productManager = addProduct,deleteProduct,editProduct,updateProduct,listProduct #訂單經(jīng)理只能做訂單管理 orderManager = addOrder,deleteOrder,editOrder,updateOrder,listOrder用戶(hù)實(shí)體:
package com.how2java;public class User {private String name;private String password;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}}測(cè)試類(lèi):
package com.how2java;import java.util.ArrayList; import java.util.List;import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory;public class TestShiro {public static void main(String[] args) {//用戶(hù)們User zhang3 = new User();zhang3.setName("zhang3");zhang3.setPassword("12345");User li4 = new User();li4.setName("li4");li4.setPassword("abcde");User wang5 = new User();wang5.setName("wang5");wang5.setPassword("wrongpassword");List<User> users = new ArrayList<>();users.add(zhang3);users.add(li4);users.add(wang5); //角色們String roleAdmin = "admin";String roleProductManager ="productManager";List<String> roles = new ArrayList<>();roles.add(roleAdmin);roles.add(roleProductManager);//權(quán)限們String permitAddProduct = "addProduct";String permitAddOrder = "addOrder";List<String> permits = new ArrayList<>();permits.add(permitAddProduct);permits.add(permitAddOrder);//登陸每個(gè)用戶(hù)for (User user : users) {if(login(user)) System.out.printf("%s \t成功登陸,用的密碼是 %s\t %n",user.getName(),user.getPassword());else System.out.printf("%s \t成功失敗,用的密碼是 %s\t %n",user.getName(),user.getPassword());}System.out.println("-------how2j 分割線------");//判斷能夠登錄的用戶(hù)是否擁有某個(gè)角色for (User user : users) {for (String role : roles) {if(login(user)) {if(hasRole(user, role)) System.out.printf("%s\t 擁有角色: %s\t%n",user.getName(),role);elseSystem.out.printf("%s\t 不擁有角色: %s\t%n",user.getName(),role);}} }System.out.println("-------how2j 分割線------");//判斷能夠登錄的用戶(hù),是否擁有某種權(quán)限for (User user : users) {for (String permit : permits) {if(login(user)) {if(isPermitted(user, permit)) System.out.printf("%s\t 擁有權(quán)限: %s\t%n",user.getName(),permit);elseSystem.out.printf("%s\t 不擁有權(quán)限: %s\t%n",user.getName(),permit);}} }}private static boolean hasRole(User user, String role) {Subject subject = getSubject(user);return subject.hasRole(role);}private static boolean isPermitted(User user, String permit) {Subject subject = getSubject(user);return subject.isPermitted(permit);}private static Subject getSubject(User user) {//加載配置文件,并獲取工廠Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");//獲取安全管理者實(shí)例SecurityManager sm = factory.getInstance();//將安全管理者放入全局對(duì)象 SecurityUtils.setSecurityManager(sm);//全局對(duì)象通過(guò)安全管理者生成Subject對(duì)象Subject subject = SecurityUtils.getSubject();return subject;}private static boolean login(User user) {Subject subject= getSubject(user);//如果已經(jīng)登錄過(guò)了,退出if(subject.isAuthenticated())subject.logout();//封裝用戶(hù)的數(shù)據(jù)UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());try {//將用戶(hù)的數(shù)據(jù)token 最終傳遞到Realm中進(jìn)行對(duì)比 subject.login(token);} catch (AuthenticationException e) {//驗(yàn)證錯(cuò)誤return false;} return subject.isAuthenticated();}}測(cè)試輸出:
zhang3 成功登陸,用的密碼是 12345 li4 成功登陸,用的密碼是 abcde wang5 成功失敗,用的密碼是 wrongpassword -------how2j 分割線------ zhang3 擁有角色: admin zhang3 不擁有角色: productManager li4 不擁有角色: admin li4 擁有角色: productManager -------how2j 分割線------ zhang3 擁有權(quán)限: addProduct zhang3 擁有權(quán)限: addOrder li4 擁有權(quán)限: addProduct li4 不擁有權(quán)限: addOrder|
|
在上面的shiro簡(jiǎn)單測(cè)試后,可以通過(guò)輸出查看到shiro究竟是用來(lái)做什么的.
但是在實(shí)際工作中,我們都會(huì)把權(quán)限相關(guān)的內(nèi)容放在數(shù)據(jù)庫(kù)中,所以本知識(shí)點(diǎn)講解如何放在數(shù)據(jù)庫(kù)里來(lái)跑shiro.
[RBAC概念] : RBAC是當(dāng)下權(quán)限系統(tǒng)的設(shè)計(jì)基礎(chǔ),同時(shí)有兩種解釋:
一 : Role-Based Access Control , 基于角色的訪問(wèn)控制, 即,你要能夠刪除產(chǎn)品,那么當(dāng)前用戶(hù)就必須擁有產(chǎn)品經(jīng)理這個(gè)角色
二 : Resource-Based Access Control , 基于資源的訪問(wèn)控制, 即,你要能夠刪除產(chǎn)品,那么當(dāng)前用戶(hù)就必須擁有刪除產(chǎn)品這樣的權(quán)限
基于角色的權(quán)限訪問(wèn)控制(Role-Based Access Control) 作為傳統(tǒng)的訪問(wèn)控制(自主訪問(wèn),強(qiáng)制訪問(wèn))的有前景的代替受到廣泛的關(guān)注.
在RBAC中,權(quán)限與角色相關(guān)聯(lián), 用戶(hù)通過(guò)成為適當(dāng)角色的成員而得到這些角色的權(quán)限. 這就極大地簡(jiǎn)化了權(quán)限的管理.
在一個(gè)組織中, 角色就是為了完成各種工作而創(chuàng)造, 用戶(hù)則依據(jù)它的責(zé)任和資格來(lái)被指派相應(yīng)的角色,用戶(hù)可以很容器地從一個(gè)角色
被派到另一個(gè)角色. 角色可依新的需求和系統(tǒng)的合并而賦予新的權(quán)限, 而權(quán)限也可根據(jù)需要而從某角色中回收. 角色與角色的關(guān)系
可以建立起來(lái)以囊括更廣泛的客觀情況.
[表結(jié)構(gòu)] : 基于RBAC概念, 就會(huì)存在3張基礎(chǔ)表: 用戶(hù),角色,權(quán)限, 以及2張中間表來(lái)建立用戶(hù)與角色的多對(duì)多關(guān)系, 角色與權(quán)限的多對(duì)多關(guān)系.
用戶(hù)與權(quán)限之間也是多對(duì)多關(guān)系,但是是通過(guò)角色間接建立的.
注: 補(bǔ)充多對(duì)多概念: 用戶(hù)和角色是多對(duì)多,即表示:
一個(gè)用戶(hù)可以有多種角色,一個(gè)角色也可以賦予多個(gè)用戶(hù).
一個(gè)角色可以包含多種權(quán)限,一種權(quán)限也可以賦予多個(gè)角色.
建立shiro數(shù)據(jù)庫(kù): 內(nèi)含權(quán)限5表 (用戶(hù),角色,權(quán)限,用戶(hù)角色,角色權(quán)限)
DROP DATABASE IF EXISTS shiro; CREATE DATABASE shiro DEFAULT CHARACTER SET utf8; USE shiro;drop table if exists user; drop table if exists role; drop table if exists permission; drop table if exists user_role; drop table if exists role_permission;create table user (id bigint auto_increment,name varchar(100),password varchar(100),constraint pk_users primary key(id) ) charset=utf8 ENGINE=InnoDB;create table role (id bigint auto_increment,name varchar(100),constraint pk_roles primary key(id) ) charset=utf8 ENGINE=InnoDB;create table permission (id bigint auto_increment,name varchar(100),constraint pk_permissions primary key(id) ) charset=utf8 ENGINE=InnoDB;create table user_role (uid bigint,rid bigint,constraint pk_users_roles primary key(uid, rid) ) charset=utf8 ENGINE=InnoDB;create table role_permission (rid bigint,pid bigint,constraint pk_roles_permissions primary key(rid, pid) ) charset=utf8 ENGINE=InnoDB;之后基于shiro.ini文件,插入一樣的用戶(hù),角色和權(quán)限數(shù)據(jù).
INSERT INTO `permission` VALUES (1,'addProduct'); INSERT INTO `permission` VALUES (2,'deleteProduct'); INSERT INTO `permission` VALUES (3,'editProduct'); INSERT INTO `permission` VALUES (4,'updateProduct'); INSERT INTO `permission` VALUES (5,'listProduct'); INSERT INTO `permission` VALUES (6,'addOrder'); INSERT INTO `permission` VALUES (7,'deleteOrder'); INSERT INTO `permission` VALUES (8,'editOrder'); INSERT INTO `permission` VALUES (9,'updateOrder'); INSERT INTO `permission` VALUES (10,'listOrder'); INSERT INTO `role` VALUES (1,'admin'); INSERT INTO `role` VALUES (2,'productManager'); INSERT INTO `role` VALUES (3,'orderManager'); INSERT INTO `role_permission` VALUES (1,1); INSERT INTO `role_permission` VALUES (1,2); INSERT INTO `role_permission` VALUES (1,3); INSERT INTO `role_permission` VALUES (1,4); INSERT INTO `role_permission` VALUES (1,5); INSERT INTO `role_permission` VALUES (1,6); INSERT INTO `role_permission` VALUES (1,7); INSERT INTO `role_permission` VALUES (1,8); INSERT INTO `role_permission` VALUES (1,9); INSERT INTO `role_permission` VALUES (1,10); INSERT INTO `role_permission` VALUES (2,1); INSERT INTO `role_permission` VALUES (2,2); INSERT INTO `role_permission` VALUES (2,3); INSERT INTO `role_permission` VALUES (2,4); INSERT INTO `role_permission` VALUES (2,5); INSERT INTO `role_permission` VALUES (3,6); INSERT INTO `role_permission` VALUES (3,7); INSERT INTO `role_permission` VALUES (3,8); INSERT INTO `role_permission` VALUES (3,9); INSERT INTO `role_permission` VALUES (3,10); INSERT INTO `user` VALUES (1,'zhang3','12345'); INSERT INTO `user` VALUES (2,'li4','abcde'); INSERT INTO `user_role` VALUES (1,1); INSERT INTO `user_role` VALUES (2,2);在原有的User類(lèi)上加一個(gè)id字段,方便數(shù)據(jù)庫(kù)操作.
設(shè)置一個(gè)Dao層:
package com.how2java;import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashSet; import java.util.Set;public class DAO {public DAO() {try {Class.forName("com.mysql.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}}public Connection getConnection() throws SQLException {return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8", "root","yoursqlpassword");}public String getPassword(String userName) {String sql = "select password from user where name = ?";try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {ps.setString(1, userName);ResultSet rs = ps.executeQuery();if (rs.next())return rs.getString("password");} catch (SQLException e) {e.printStackTrace();}return null;}public Set<String> listRoles(String userName) {Set<String> roles = new HashSet<>();String sql = "select r.name from user u "+ "left join user_role ur on u.id = ur.uid "+ "left join Role r on r.id = ur.rid "+ "where u.name = ?";try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {ps.setString(1, userName);ResultSet rs = ps.executeQuery();while (rs.next()) {roles.add(rs.getString(1));}} catch (SQLException e) {e.printStackTrace();}return roles;}public Set<String> listPermissions(String userName) {Set<String> permissions = new HashSet<>();String sql = "select p.name from user u "+"left join user_role ru on u.id = ru.uid "+"left join role r on r.id = ru.rid "+"left join role_permission rp on r.id = rp.rid "+"left join permission p on p.id = rp.pid "+"where u.name =?";try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {ps.setString(1, userName);ResultSet rs = ps.executeQuery();while (rs.next()) {permissions.add(rs.getString(1));}} catch (SQLException e) {e.printStackTrace();}return permissions;}public static void main(String[] args) {System.out.println(new DAO().listRoles("zhang3"));System.out.println(new DAO().listRoles("li4"));System.out.println(new DAO().listPermissions("zhang3"));System.out.println(new DAO().listPermissions("li4"));} }Dao的主方法測(cè)試:
[admin] [productManager] [editOrder, addProduct, updateProduct, listProduct, listOrder, addOrder, updateOrder, deleteOrder, deleteProduct, editProduct] [addProduct, updateProduct, listProduct, deleteProduct, editProduct]可以看到zhang3的Roles為admin,li4的為productManager,zhang3的權(quán)限為editOrder.../ li4的權(quán)限為addProduct......
其中搜索用戶(hù)角色時(shí)進(jìn)行3表聯(lián)查:
String sql = "select r.name from user u "+ "left join user_role ur on u.id = ur.uid "+ "left join Role r on r.id = ur.rid "+ "where u.name = ?";這里都使用了左連接,曾經(jīng)有個(gè)面試官問(wèn)我inner join和outer join的區(qū)別我沒(méi)答上來(lái). 這里有篇博客整理得不錯(cuò). 注意是outer join
下面查到的編輯訂單,添加商品,更新商品等一系列操作權(quán)限集合通過(guò)5表聯(lián)查查詢(xún):
String sql = "select p.name from user u "+"left join user_role ru on u.id = ru.uid "+"left join role r on r.id = ru.rid "+"left join role_permission rp on r.id = rp.rid "+"left join permission p on p.id = rp.pid "+"where u.name =?";其實(shí)這里如果牽扯到表過(guò)多,要做個(gè)小樣測(cè)試,進(jìn)行記錄:
上圖使用了戰(zhàn)德臣老師的關(guān)系代表達(dá)式.如果多個(gè)表關(guān)系復(fù)雜時(shí)可以通過(guò)代數(shù)式清晰地展示出來(lái).
[Realm概念] : 在Shiro中存在Realm這個(gè)概念,Realm這個(gè)單詞翻譯為 域, 其實(shí)是非常難以理解的.
域 是什么? 和權(quán)限有什么關(guān)系? 這個(gè)單詞挺讓人費(fèi)解.
Realm 在 Shiro 里到底扮演什么角色呢?
當(dāng)應(yīng)用程序向Shiro提供了賬號(hào)和密碼之后,Shiro就會(huì)問(wèn)Realm這個(gè)賬號(hào)密碼是否對(duì), 如果對(duì)的話,其所對(duì)應(yīng)的用戶(hù)擁有哪些角色,哪些權(quán)限.
所以Realm是什么? 其實(shí)就是個(gè)中介, Realm得到了Shiro給的用戶(hù)和密碼后, 有可能去找ini文件,就像之前的shiro.ini,也可以去找數(shù)據(jù)庫(kù),
就如同上面Dao查詢(xún)信息.
Realm就是干這個(gè)用的,它才是真正進(jìn)行用戶(hù)認(rèn)證和授權(quán)的關(guān)鍵地方.
再看另一個(gè)類(lèi):
DatabaseRealm 它就是用來(lái)通過(guò)數(shù)據(jù)庫(kù)驗(yàn)證用戶(hù),和相關(guān)授權(quán)的類(lèi).
兩個(gè)方法分別做驗(yàn)證和授權(quán):
doGetAuthenticationInfo(), doGetAuthorizationInfo()
package com.how2java;import java.util.Set;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.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection;public class DatabaseRealm extends AuthorizingRealm {@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//能進(jìn)入到這里,表示賬號(hào)已經(jīng)通過(guò)驗(yàn)證了String userName =(String) principalCollection.getPrimaryPrincipal();//通過(guò)DAO獲取角色和權(quán)限Set<String> permissions = new DAO().listPermissions(userName);Set<String> roles = new DAO().listRoles(userName);//授權(quán)對(duì)象SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();//把通過(guò)DAO獲取到的角色和權(quán)限放進(jìn)去 s.setStringPermissions(permissions);s.setRoles(roles);return s;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//獲取賬號(hào)密碼UsernamePasswordToken t = (UsernamePasswordToken) token;String userName= token.getPrincipal().toString();String password= new String( t.getPassword());//獲取數(shù)據(jù)庫(kù)中的密碼String passwordInDB = new DAO().getPassword(userName);//如果為空就是賬號(hào)不存在,如果不相同就是密碼錯(cuò)誤,但是都拋出AuthenticationException,而不是拋出具體錯(cuò)誤原因,免得給破解者提供幫助信息if(null==passwordInDB || !passwordInDB.equals(password)) throw new AuthenticationException();//認(rèn)證信息里存放賬號(hào)密碼, getName() 是當(dāng)前Realm的繼承方法,通常返回當(dāng)前類(lèi)名 :databaseRealmSimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,password,getName());return a;}}注: DatabaseRealm這個(gè)類(lèi),用戶(hù)提供,但是不由用戶(hù)自己調(diào)用, 而是由Shiro去調(diào)用,就像Servlet的doPost方法,是被Tomcat調(diào)用一樣.
那么Shiro怎么找到這個(gè)Realm呢? 那么需要修改shiro.ini
[main] databaseRealm=com.how2java.DatabaseRealm securityManager.realms=$databaseRealm[JdbcRealm]?
Shiro提供了一個(gè)JdbcRealm, 它會(huì)默認(rèn)去尋找 users, roles, permissions 三張表做類(lèi)似于 Dao 中的查詢(xún).
但是這里沒(méi)有使用,因?yàn)閷?shí)際工作通常都會(huì)有更復(fù)雜的權(quán)限需要,以上3個(gè)表不夠用. JdbcRealm又封裝得太嚴(yán)實(shí)了.
[md5加密]
在之前,用戶(hù)密碼都是明文的,這樣有巨大的風(fēng)險(xiǎn),一旦泄露,就不好了.
所以,通常都會(huì)采用非對(duì)稱(chēng)加密,什么是非對(duì)稱(chēng)呢?就是不可逆的,而md5就是這樣的一個(gè)算法(在之前接觸到的微信平臺(tái)開(kāi)發(fā)與支付中同樣也會(huì)用到)
如TestEncryption
package com.how2java;import org.apache.shiro.crypto.hash.Md5Hash;public class TestEncryption {public static void main(String[] args) {String password = "123";String encodedPassword = new Md5Hash(password).toString();System.out.println(encodedPassword);} }123字符串通過(guò)md5加密后得到字符串202CB962AC59075B964B07152D234B70
這個(gè)字符串,卻無(wú)法通過(guò)計(jì)算,反過(guò)來(lái)得到源密碼 123
這個(gè)加密后的字符串就存在數(shù)據(jù)庫(kù)里了, 下次用戶(hù)再登錄,輸入密碼123, 同樣用md5加密后, 再和這個(gè)字符串一比較, 就知道密碼是否正確了.
如此這樣,既能保證用戶(hù)密碼校驗(yàn)的功能, 又能保證不暴露密碼.
但是md5加密又有一些缺陷:
1.如果A的密碼是123, B的也是123, 那么md5的值是一樣的, 那么通過(guò)比較加密后的字符串, 我就可以反推過(guò)來(lái), 原來(lái)你的密碼也是123.
2.與上述相同,雖然md5不可逆,但是可以進(jìn)行窮舉法暴力破解. 為了解決這個(gè)問(wèn)題,引入了鹽的概念, 鹽是什么呢? 比如炒菜,直接使用md5,
就是對(duì)食材(源密碼)進(jìn)行炒菜,因?yàn)槭巢氖且粯拥?所以炒出來(lái)的味道都一樣,可是如果加了不同分量的鹽,那么即便食材一樣,炒出來(lái)的味道就
不一樣了.
所以,雖然每次123md5之后的密文都是202CB962AC59075B964B07152D234B70, 但是我加上鹽,即123+隨機(jī)數(shù),那么md5的值就不一樣了~
這個(gè)隨機(jī)數(shù),就是鹽,而這個(gè)隨機(jī)數(shù)也會(huì)在數(shù)據(jù)庫(kù)里保存下來(lái),每個(gè)不同的用戶(hù),隨機(jī)數(shù)也是不一樣的.
再就是加密次數(shù),加密一次是202CB962AC59075B964B07152D234B70,我也可以加密兩次,就是另一個(gè)數(shù)了,而黑客即便是拿到了加密后的密碼,
如果不知道到底加密了多少次,也是很難辦的.
package com.how2java;import org.apache.shiro.crypto.SecureRandomNumberGenerator; import org.apache.shiro.crypto.hash.SimpleHash;public class TestEncryption {public static void main(String[] args) {String password = "123";String salt = new SecureRandomNumberGenerator().nextBytes().toString();int times = 2;String algorithmName = "md5";String encodedPassword = new SimpleHash(algorithmName,password,salt,times).toString();System.out.printf("原始密碼是 %s , 鹽是: %s, 運(yùn)算次數(shù)是: %d, 運(yùn)算出來(lái)的密文是:%s ",password,salt,times,encodedPassword);} }運(yùn)行這些代碼:得到輸出:
原始密碼是 123 , 鹽是: Qf7STdEccXZtFkAujUDwSA==, 運(yùn)算次數(shù)是: 2, 運(yùn)算出來(lái)的密文是:e020da0e276e9f4f30f520fb3a225935 原始密碼是 123 , 鹽是: OlYiWbzKmyccksMQLMOcPg==, 運(yùn)算次數(shù)是: 2, 運(yùn)算出來(lái)的密文是:236d91c0a2e9d2ecb77e60f0d4050fef 原始密碼是 123 , 鹽是: 75MZpCeMIgf0F/v+RnCsSA==, 運(yùn)算次數(shù)是: 2, 運(yùn)算出來(lái)的密文是:46d77dc05d2fa0e48035ceed4333079f像上面得到了鹽的加密(作者比喻比較形象),這樣的字符串基本很難推算出來(lái),從而加大了破譯密碼的難度.
而使用的生成隨機(jī)鹽的方法也是Shiro自帶的工具類(lèi).new SecureRandomNumberGenerator().nextBytes().toString();
[數(shù)據(jù)庫(kù)調(diào)整] : 有了以上基礎(chǔ),那么就可以開(kāi)始在原來(lái)的教程里加入對(duì)加密的支持了. 在開(kāi)始之前, 要修改一下user表,
加上鹽字段: salt. 因?yàn)辂}是隨機(jī)數(shù),得保留下來(lái), 如果不知道鹽是多少, 我們也就沒(méi)法判斷密碼是否正確了.
?
alert table user add (salt varchar(100));?
package com.how2java;import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory;public class TestShiro {public static void main(String[] args) {//這里要釋放注釋,先注冊(cè)一個(gè)用戶(hù)new DAO().createUser("tom", "123");User user = new User();user.setName("tom");user.setPassword("123");if(login(user)) System.out.println("登錄成功");elseSystem.out.println("登錄失敗");}private static boolean hasRole(User user, String role) {Subject subject = getSubject(user);return subject.hasRole(role);}private static boolean isPermitted(User user, String permit) {Subject subject = getSubject(user);return subject.isPermitted(permit);}private static Subject getSubject(User user) {//加載配置文件,并獲取工廠Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");//獲取安全管理者實(shí)例SecurityManager sm = factory.getInstance();//將安全管理者放入全局對(duì)象 SecurityUtils.setSecurityManager(sm);//全局對(duì)象通過(guò)安全管理者生成Subject對(duì)象Subject subject = SecurityUtils.getSubject();return subject;}private static boolean login(User user) {Subject subject= getSubject(user);//如果已經(jīng)登錄過(guò)了,退出if(subject.isAuthenticated())subject.logout();//封裝用戶(hù)的數(shù)據(jù)UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());try {//將用戶(hù)的數(shù)據(jù)token 最終傳遞到Realm中進(jìn)行對(duì)比 subject.login(token);} catch (AuthenticationException e) {//驗(yàn)證錯(cuò)誤return false;} return subject.isAuthenticated();}}通過(guò)上述代碼,先注冊(cè)了一個(gè)用戶(hù),然后進(jìn)行登錄驗(yàn)證
在Dao中,增加了兩個(gè)方法 createUser, getUser
package com.how2java;import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashSet; import java.util.Set;import org.apache.shiro.crypto.SecureRandomNumberGenerator; import org.apache.shiro.crypto.hash.SimpleHash;public class DAO {public DAO() {try {Class.forName("com.mysql.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}}public Connection getConnection() throws SQLException {return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8", "root","yourpassword");}public String createUser(String name, String password) {String sql = "insert into user values(null,?,?,?)";String salt = new SecureRandomNumberGenerator().nextBytes().toString(); //鹽量隨機(jī)String encodedPassword= new SimpleHash("md5",password,salt,2).toString();try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {ps.setString(1, name);ps.setString(2, encodedPassword);ps.setString(3, salt);ps.execute();} catch (SQLException e) {e.printStackTrace();}return null; }public String getPassword(String userName) {String sql = "select password from user where name = ?";try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {ps.setString(1, userName);ResultSet rs = ps.executeQuery();if (rs.next())return rs.getString("password");} catch (SQLException e) {e.printStackTrace();}return null;}public User getUser(String userName) {User user = null;String sql = "select * from user where name = ?";try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {ps.setString(1, userName);ResultSet rs = ps.executeQuery();if (rs.next()) {user = new User();user.setId(rs.getInt("id"));user.setName(rs.getString("name"));user.setPassword(rs.getString("password"));user.setSalt(rs.getString("salt"));}} catch (SQLException e) {e.printStackTrace();}return user;}public Set<String> listRoles(String userName) {Set<String> roles = new HashSet<>();String sql = "select r.name from user u "+ "left join user_role ur on u.id = ur.uid "+ "left join Role r on r.id = ur.rid "+ "where u.name = ?";try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {ps.setString(1, userName);ResultSet rs = ps.executeQuery();while (rs.next()) {roles.add(rs.getString(1));}} catch (SQLException e) {e.printStackTrace();}return roles;}public Set<String> listPermissions(String userName) {Set<String> permissions = new HashSet<>();String sql = "select p.name from user u "+"left join user_role ru on u.id = ru.uid "+"left join role r on r.id = ru.rid "+"left join role_permission rp on r.id = rp.rid "+"left join permission p on p.id = rp.pid "+"where u.name =?";try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {ps.setString(1, userName);ResultSet rs = ps.executeQuery();while (rs.next()) {permissions.add(rs.getString(1));}} catch (SQLException e) {e.printStackTrace();}return permissions;} }繼而修改DatabaseRealm,把用戶(hù)通過(guò)UsernamePasswordToken傳進(jìn)來(lái)的密碼,以及數(shù)據(jù)庫(kù)里取出來(lái)的salt進(jìn)行加密,加密之后再與數(shù)據(jù)庫(kù)里的
密文進(jìn)行比較,判斷用戶(hù)是否能夠通過(guò)驗(yàn)證.
package com.how2java;import java.util.Set;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.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource;public class DatabaseRealm extends AuthorizingRealm {@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//能進(jìn)入到這里,表示賬號(hào)已經(jīng)通過(guò)驗(yàn)證了String userName =(String) principalCollection.getPrimaryPrincipal();//通過(guò)DAO獲取角色和權(quán)限Set<String> permissions = new DAO().listPermissions(userName);Set<String> roles = new DAO().listRoles(userName);//授權(quán)對(duì)象SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();//把通過(guò)DAO獲取到的角色和權(quán)限放進(jìn)去 s.setStringPermissions(permissions);s.setRoles(roles);return s;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//獲取賬號(hào)密碼UsernamePasswordToken t = (UsernamePasswordToken) token;String userName= token.getPrincipal().toString();String password =new String(t.getPassword());//獲取數(shù)據(jù)庫(kù)中的密碼 User user = new DAO().getUser(userName);String passwordInDB = user.getPassword();String salt = user.getSalt();String passwordEncoded = new SimpleHash("md5",password,salt,2).toString();if(null==user || !passwordEncoded.equals(passwordInDB))throw new AuthenticationException();//認(rèn)證信息里存放賬號(hào)密碼, getName() 是當(dāng)前Realm的繼承方法,通常返回當(dāng)前類(lèi)名 :databaseRealmSimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,password,getName());return a;}}通過(guò)剛才的增加新用戶(hù):
可以看到tom的密碼加密了,它的鹽在后面
另一個(gè)做法的DatabaseRealm中只是提供了密文和鹽,具體操作:
修改shiro.ini :
為DatabaseRealm指定credentialsMatcher, 其中就指定了算法是md5, 次數(shù)為2, storedCredentialsHexEncoded這個(gè)表示計(jì)算之后以密文為16進(jìn)制.
這樣Shiro就拿著在subject.log()時(shí)傳入的UsernamePasswordToken中的源密碼,數(shù)據(jù)庫(kù)里的密文和鹽,以及配置文件里指定的算法參數(shù),
自己去進(jìn)行相關(guān)匹配了.
以下是shiro.ini
[main] credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher credentialsMatcher.hashAlgorithmName=md5 credentialsMatcher.hashIterations=2 credentialsMatcher.storedCredentialsHexEncoded=truedatabaseRealm=com.how2java.DatabaseRealm databaseRealm.credentialsMatcher=$credentialsMatcher securityManager.realms=$databaseRealm好了,除了能看到md5加密和隨機(jī)鹽,shiro.ini馬上就不會(huì)用到了,所以上面的也不用太在意,可以查看下storedCredentialsHexEncoded,不過(guò)接下來(lái)接入jsp頁(yè)面查看下shiro的再實(shí)際點(diǎn)的作用.
既然是Web項(xiàng)目,那還是配置下web.xml :
<web-app><listener><listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class></listener><context-param><param-name>shiroEnvironmentClass</param-name><param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value><!-- 默認(rèn)先從/WEB-INF/shiro.ini,如果沒(méi)有找classpath:shiro.ini --></context-param><context-param><param-name>shiroConfigLocations</param-name><param-value>classpath:shiro.ini</param-value></context-param><filter><filter-name>shiroFilter</filter-name><filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping> </web-app>這里有個(gè)Filter的DEMO,Filter說(shuō)白了就是當(dāng)調(diào)用一個(gè)url時(shí)之前要有一道關(guān)口,這個(gè)關(guān)口就是Filter.
其它的如User,DAO,DatabaseRealm都和之前的沒(méi)啥區(qū)別.
這里有一個(gè)Servlet
package com.how2java;import java.io.IOException;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject;@WebServlet(name = "loginServlet", urlPatterns = "/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name = req.getParameter("name"); String password = req.getParameter("password"); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(name, password); try { subject.login(token);Session session=subject.getSession();session.setAttribute("subject", subject);resp.sendRedirect("");} catch (AuthenticationException e) { req.setAttribute("error", "驗(yàn)證失敗"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } }LoginServlet映射路徑/login的訪問(wèn).
獲取賬號(hào)和密碼,然后組成UsernamePasswordToken對(duì)象,扔給Shiro進(jìn)行判斷, 如果判斷不報(bào)錯(cuò), 即表示成功, 客戶(hù)端跳轉(zhuǎn)到根目錄,
否則返回login.jsp,并帶上錯(cuò)誤信息
登錄成功之后還會(huì)把subject放在shiro的session對(duì)象里, shiro的這個(gè)session和httpsession是串通好了的, 所以在這里放了, 它會(huì)自動(dòng)放在
httpsession里,它們之間是同步的. 可以看到這里登錄成功后,將用戶(hù)名和密碼通過(guò)Shiro的UsernamePasswordToken包裝為一個(gè)token對(duì)象
之后通過(guò)login方法進(jìn)行用戶(hù)登錄,如果沒(méi)有異常則表示用戶(hù)名密碼正確, resp響應(yīng)到了"",(并不是index.jsp,或mian.jsp不知道為什么)
下面是shiro.ini,之后到springboot應(yīng)該不用配置了
[main] #使用數(shù)據(jù)庫(kù)進(jìn)行驗(yàn)證和授權(quán) databaseRealm=com.how2java.DatabaseRealm securityManager.realms=$databaseRealm#當(dāng)訪問(wèn)需要驗(yàn)證的頁(yè)面,但是又沒(méi)有驗(yàn)證的情況下,跳轉(zhuǎn)到login.jsp authc.loginUrl=/login.jsp #當(dāng)訪問(wèn)需要角色的頁(yè)面,但是又不擁有這個(gè)角色的情況下,跳轉(zhuǎn)到noroles.jsp roles.unauthorizedUrl=/noRoles.jsp #當(dāng)訪問(wèn)需要權(quán)限的頁(yè)面,但是又不擁有這個(gè)權(quán)限的情況下,跳轉(zhuǎn)到noperms.jsp perms.unauthorizedUrl=/noPerms.jsp#users,roles和perms都通過(guò)前面知識(shí)點(diǎn)的數(shù)據(jù)庫(kù)配置了 [users] #urls用來(lái)指定哪些資源需要什么對(duì)應(yīng)的授權(quán)才能使用 [urls] #doLogout地址就會(huì)進(jìn)行退出行為 /doLogout=logout #login.jsp,noroles.jsp,noperms.jsp 可以匿名訪問(wèn) /login.jsp=anon /noroles.jsp=anon /noperms.jsp=anon#查詢(xún)所有產(chǎn)品,需要登錄后才可以查看 /listProduct.jsp=authc #增加商品不僅需要登錄,而且要擁有 productManager 權(quán)限才可以操作 /deleteProduct.jsp=authc,roles[productManager] #刪除商品,不僅需要登錄,而且要擁有 deleteProduct 權(quán)限才可以操作 /deleteOrder.jsp=authc,perms["deleteOrder"]?
下面是index.jsp, 通過(guò)${subject.principal}來(lái)判斷用戶(hù)是否登錄,if(登錄了){ 顯示退出選項(xiàng) } else { 顯示登錄按鈕 } (與大多數(shù)的網(wǎng)站登錄一致)
index里提供了3個(gè)超鏈接,分別要登錄后才可以查看,有角色,有權(quán)限才能看,便于進(jìn)行測(cè)試.
注: subject是在LoginServlet里放進(jìn)session的
index.jsp使用了JSTL表達(dá)式,(說(shuō)真的JSTL,EL表達(dá)式遠(yuǎn)不及用ThymeLeaf前端模板來(lái)得好,JSTL,EL如果不在tomcat啟動(dòng)就訪問(wèn)不了了,而ThymeLeaf不同)
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%><link rel="stylesheet" type="text/css" href="static/css/style.css" /></head> <body><div class="workingroom"><div class="loginDiv"><c:if test="${empty subject.principal}"><a href="login.jsp">登錄</a><br></c:if><c:if test="${!empty subject.principal}"><span class="desc">你好,${subject.principal},</span><a href="doLogout">退出</a><br> </c:if><a href="listProduct.jsp">查看產(chǎn)品</a><span class="desc">(登錄后才可以查看) </span><br><a href="deleteProduct.jsp">刪除產(chǎn)品</a><span class="desc">(要有產(chǎn)品管理員角色, zhang3沒(méi)有,li4 有) </span><br><a href="deleteOrder.jsp">刪除訂單</a><span class="desc">(要有刪除訂單權(quán)限, zhang3有,li4沒(méi)有) </span><br> </div></body> </html>?
登錄 login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" import="java.util.*"%><!DOCTYPE html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /><div class="workingroom"><div class="errorInfo">${error}</div><form action="login" method="post">賬號(hào): <input type="text" name="name"> <br>密碼: <input type="password" name="password"> <br><br><input type="submit" value="登錄"><br><br><div><span class="desc">賬號(hào):zhang3 密碼:12345 角色:admin</span><br><span class="desc">賬號(hào):li4 密碼:abcde 角色:productManager</span><br></div></form> </div>大體運(yùn)行后是這樣一個(gè)干枯枯燥的頁(yè)面
在demo中,當(dāng)用戶(hù)zhang3登錄后如果查看刪除產(chǎn)品的鏈接,就會(huì)提示錯(cuò)誤,路徑轉(zhuǎn)到了http://localhost:8184/shiro/noRoles.jsp
而進(jìn)入刪除訂單,則沒(méi)有問(wèn)題,因?yàn)閦hang3用戶(hù)有該權(quán)限.
?{經(jīng)歷了一夜的休息,繼續(xù)跟shiro戰(zhàn)斗,將博客下方加上>>筆耕不輟<<,希望今年是個(gè)豐收年}
在登錄了li4這個(gè)角色后,可以看到刪除產(chǎn)品的可以進(jìn)入,而刪除訂單的權(quán)限不足.
也就是說(shuō)Shiro提供的這些Subject,SecurityManager,Authenticator(身份認(rèn)證),Authorizer(授權(quán)認(rèn)證),Realm,,,sessionManager等都是為了做這個(gè)工作,
即讓有權(quán)限的,有角色的才可能進(jìn)入規(guī)定的地方. (其實(shí)就像現(xiàn)在編程這個(gè)行業(yè),許多都是培訓(xùn)班出來(lái)的,半路出家,而很多公司都是卡學(xué)歷的,如果你學(xué)歷不夠,那么就說(shuō)明你沒(méi)有這個(gè)Role,繼而也就沒(méi)有這個(gè)權(quán)限)
listProduct.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" import="java.util.*"%><!DOCTYPE html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /><div class="workingroom">listProduct.jsp ,能進(jìn)來(lái),就表示已經(jīng)登錄成功了<br><a href="#" onClick="javascript:history.back()">返回</a> </div>deleteOrder.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" import="java.util.*"%><!DOCTYPE html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /><div class="workingroom">deleteOrder.jsp ,能進(jìn)來(lái),就表示有deleteOrder權(quán)限<br><a href="#" onClick="javascript:history.back()">返回</a> </div>deleteProduct.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" import="java.util.*"%><!DOCTYPE html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /><div class="workingroom">deleteProduct.jsp,能進(jìn)來(lái)<br>就表示擁有 productManager 角色<br><a href="#" onClick="javascript:history.back()">返回</a> </div>noRoles.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" import="java.util.*"%><!DOCTYPE html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /><div class="workingroom">角色不匹配<br><a href="#" onClick="javascript:history.back()">返回</a> </div>noPerms.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" import="java.util.*"%><!DOCTYPE html><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="static/css/style.css" /><div class="workingroom">權(quán)限不足<br><a href="#" onClick="javascript:history.back()">返回</a> </div>?
?
轉(zhuǎn)載于:https://www.cnblogs.com/ukzq/p/10212413.html
總結(jié)
以上是生活随笔為你收集整理的[shiro] - 怎样使用shiro?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Go语言中使用MySql数据库
- 下一篇: DS二叉树--左叶子数量