權限認證顧名思義,就是在應用系統中,控制誰能訪問哪些資源。核心要素有仨:權限、角色、用戶

權限:即操作資源的權利,如訪問某個url,對某個模塊數據進行增刪改查
角色:權限的集合,一種角色可以包含多種權限。例如操作員角色可查看系統賬單、進行結賬操作多種權限。
用戶:也就是身份認證中提到的subject一角。
二、授權
shiro授權的方式通常有三種:
1、編程式授權:在代碼中進行授權操作,可基於角色和權限兩種方式。
2、注解式授權:使用注解對方法或類進行授權,標注該類可被哪些權限、角色所使用。
3、Jsp標簽授權:shiro比較靈活的地方筆者覺得就是jsp標簽授權,通過shiro的guest、user、principal等標簽,可通過訪問權限的不同,控制頁面信息顯示。免去了一大部分後台處理邏輯。好方便,好好用。後面會有詳細介紹。
三、編程式授權實例
1、同樣首先創建ini文件
[users] java1234=123456,role1,role2 jack=123,role1
這是一個通過角色授權的方式,具體含義是指:用戶名為java1234的用戶享有role1,role2角色的權利,jack用戶享有role1的權利。
2、java代碼通過hasRole 和checkRole的方法可判斷某用戶是否具有role1、role2角色權利。
這裡首先將一些shiro初始化、預處理的操作封裝成一個util類
public class ShiroUtil {
public static Subject login(String configFile,String userName,String password){
// 讀取配置文件,初始化SecurityManager工廠
Factory factory=new IniSecurityManagerFactory(configFile);
// 獲取securityManager實例
SecurityManager securityManager=factory.getInstance();
// 把securityManager實例綁定到SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 得到當前執行的用戶
Subject currentUser=SecurityUtils.getSubject();
// 創建token令牌,用戶名/密碼
UsernamePasswordToken token=new UsernamePasswordToken(userName, password);
try{
// 身份認證
currentUser.login(token);
System.out.println("身份認證成功!");
}catch(AuthenticationException e){
e.printStackTrace();
System.out.println("身份認證失敗!");
}
return currentUser;
}
} 新建roleTest類,通過調用user的hasRole、checkRole方法判斷用戶權限。public class RoleTest {
@Test
public void testHasRole() {
Subject currentUser=ShiroUtil.login("classpath:shiro_role.ini", "java1234", "123456");
// Subject currentUser=ShiroUtil.login("classpath:shiro_role.ini", "jack", "123");
System.out.println(currentUser.hasRole("role1")?"有role1這個角色":"沒有role1這個角色");
boolean []results=currentUser.hasRoles(Arrays.asList("role1","role2","role3"));
System.out.println(results[0]?"有role1這個角色":"沒有role1這個角色");
System.out.println(results[1]?"有role2這個角色":"沒有role2這個角色");
System.out.println(results[2]?"有role3這個角色":"沒有role3這個角色");
System.out.println(currentUser.hasAllRoles(Arrays.asList("role1","role2"))?"role1,role2這兩個角色都有":"role1,role2這個兩個角色不全有");
currentUser.logout();
}
@Test
public void testCheckRole() {
Subject currentUser=ShiroUtil.login("classpath:shiro_role.ini", "java1234", "123456");
// Subject currentUser=ShiroUtil.login("classpath:shiro_role.ini", "jack", "123");
currentUser.checkRole("role1");
currentUser.checkRoles(Arrays.asList("role1","role2"));
currentUser.checkRoles("role1","role2","role3");
currentUser.logout();
}
}
通過權限permission的權限驗證方式如下:
[users] java1234=123456,role1,role2 jack=123,role1 [roles] role1=user:select role2=user:add,user:update,user:delete
這裡就補充了role1、role2用戶具體具有哪些操作權,role1可進行select操作,同理,role2可進行增刪改操作。同樣調用user關於permission認證的方法,可對用戶具體操作權限進行認證。
public class PermissionTest {
@Test
public void testIsPermitted() {
Subject currentUser=ShiroUtil.login("classpath:shiro_permission.ini", "java1234", "123456");
// Subject currentUser=ShiroUtil.login("classpath:shiro_permission.ini", "jack", "123");
System.out.println(currentUser.isPermitted("user:select")?"有user:select這個權限":"沒有user:select這個權限");
System.out.println(currentUser.isPermitted("user:update")?"有user:update這個權限":"沒有user:update這個權限");
boolean results[]=currentUser.isPermitted("user:select","user:update","user:delete");
System.out.println(results[0]?"有user:select這個權限":"沒有user:select這個權限");
System.out.println(results[1]?"有user:update這個權限":"沒有user:update這個權限");
System.out.println(results[2]?"有user:delete這個權限":"沒有user:delete這個權限");
System.out.println(currentUser.isPermittedAll("user:select","user:update")?"有user:select,update這兩個權限":"user:select,update這兩個權限不全有");
currentUser.logout();
}
@Test
public void testCheckPermitted() {
Subject currentUser=ShiroUtil.login("classpath:shiro_permission.ini", "java1234", "123456");
// Subject currentUser=ShiroUtil.login("classpath:shiro_permission.ini", "jack", "123");
currentUser.checkPermission("user:select");
currentUser.checkPermissions("user:select","user:update","user:delete");
currentUser.logout();
}
}
下面介紹在java web程序中使用Shiro進行權限認證。
首先也是添加shiro相關jar包:shiro-web、shiro-core;commons-logging、slf4j-api、log4j;jstl、javax.servlet.jsp-api、javax.servlet-api
在web.XML中添加shiroFilter過濾器,並初始化創建的shiro.ini配置文件。
org.apache.shiro.web.env.EnvironmentLoaderListener ShiroFilter org.apache.shiro.web.servlet.ShiroFilter ShiroFilter /*
shiro.ini文件
[main] authc.loginUrl=/login roles.unauthorizedUrl=/unauthorized.jsp perms.unauthorizedUrl=/unauthorized.jsp [users] java1234=123456,admin jack=123,teacher marry=234 json=345 [roles] admin=user:* teacher=student:* [urls] /login=anon /admin=authc /student=roles[teacher] /teacher=perms["user:create"]
3.創建login和adminservlet,分別用於直接登陸轉發到login.jsp,和admin登錄進行身份驗證,轉發到succeess.jsp和error.jsp
public class LoginServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("login doget");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("login dopost");
String userName=req.getParameter("userName");
String password=req.getParameter("password");
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(userName, password);
try{
subject.login(token);
resp.sendRedirect("success.jsp");
}catch(Exception e){
e.printStackTrace();
req.setAttribute("errorInfo", "用戶名或者密碼錯誤");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}
}
4.配置數據源-這裡介紹兩種reaml:Text Reaml和自定義JDBC reaml 。
Text Reaml 配置詳情與訪問流程
text Reaml配置整合到Shiro.ini文件中,具體配置了四個用戶,java1234擁有admin角色權限,jack擁有teacher角色權限;角色admin可對user進行任意crud操作,teacher可對student進行crud操作;訪問的urls,請求/login地址時,這裡的anon是指游客身份,不需要進行任何身份認證;請求/admin地址時,需要進行身份認證,進行filter過濾後,跳轉到【main】中進行了配置為/login (jsp頁面),同樣,role和perms的權限認證頁設置為unauthorized.jsp;
上面ini配置達到的效果就是:當請求訪問localhost:8080/shiro/login 時直接跳到hello頁面,無需進行身份驗證。當訪問localhost:8080/shiro/admin時,先轉發到login.jps進行身份驗證,進入adminServlet,驗證結束後轉發到error或succeess頁面。再次訪問admin地址時,由於第一次訪問記錄了用戶登錄信息,故無需在登陸直接跳轉到success頁面。而訪問localhost:8080/shiro/student時,login信息如果使用json(沒有任何角色權限的用戶),則因為該用戶權限不足(因為配置中訪問student需要teacher角色)直接跳轉到無權限訪問頁面。這就是使用 textReaml進行身份驗證和權限驗證的配置。
自定義 JDBC Reaml的配置與訪問流程
由於text Reaml的信息畢竟有限,配置也相對比較麻煩,所以一般應用程序使用的都是自定義reaml,此處創建一個自定義JDBC readml並演示reaml與java程序結合的流程。

由於需要創建數據庫(創建用戶、角色、權限三張表,依次主外鍵關聯),然後首先引入數據庫驅動jar包;在shiro,ini文件中指定當前securityManager使用的驗證策略是自定義jdbcReaml。
[main] authc.loginUrl=/login roles.unauthorizedUrl=/unauthorized.jsp perms.unauthorizedUrl=/unauthorized.jsp myRealm=com.java.realm.MyRealm securityManager.realms=$myRealm [urls] /login=anon /admin*=authc /student=roles[teacher] /teacher=perms["user:create"]最後創建數據庫連接Util類和myReaml類調用底層數據查詢dao即可。
/**
* 數據庫工具類
* @author
*/
public class DbUtil {
public Connection getCon() throws Exception{
Class.forName("com.mysql.jdbc.Driver");
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/db_shiro", "root", "123456");
return con;
}
public void closeCon(Connection con)throws Exception{
if(con!=null){
con.close();
}
}
public static void main(String[] args) {
DbUtil dbUtil=new DbUtil();
try {
dbUtil.getCon();
System.out.println("數據庫連接成功");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("數據庫連接失敗");
}
}
}MyReaml類:繼承shiro
AuthorizingReaml類,重寫身份驗證和權限驗證兩個方法。(這裡也就解釋了為什麼訪問student地址時,並未配置需要先登錄,程序卻自動跳轉到登錄面。因為底層封裝了默認請求都先進行身份認證的方法。)
public class MyRealm extends AuthorizingRealm{
private UserDao userDao=new UserDao();
private DbUtil dbUtil=new DbUtil();
/**
* 為當前登錄的用戶授予角色和權限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName=(String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
Connection con=null;
try{
con=dbUtil.getCon();
authorizationInfo.setRoles(userDao.getRoles(con,userName));
authorizationInfo.setStringPermissions(userDao.getPermissions(con,userName));
}catch(Exception e){
e.printStackTrace();
}finally{
try {
dbUtil.closeCon(con);
} catch (Exception e) {
e.printStackTrace();
}
}
return authorizationInfo;
}
/**
* 驗證當前登錄的用戶
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName=(String)token.getPrincipal();
Connection con=null;
try{
con=dbUtil.getCon();
User user=userDao.getByUserName(con, userName);
if(user!=null){
AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(),"xx");
return authcInfo;
}else{
return null;
}
}catch(Exception e){
e.printStackTrace();
}finally{
try {
dbUtil.closeCon(con);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
}
這兩個方法分別封裝了一個身份信息實體類AuthenticationInfo 和AuthorizetionInfo返回。
首先獲取用戶信息,根據用戶名查詢數據庫中的用戶、權限等其他信息驗證即可。整體流程是當浏覽器訪問例如:localhost:8080/shiro/admin 地址時,先調用loginServlet,在執行user.login方法時,進入自定義MyReaml類,獲取用戶信息,進行身份驗證。