權限認證顧名思義,就是在應用系統中,控制誰能訪問哪些資源。核心要素有仨:權限、角色、用戶
權限:即操作資源的權利,如訪問某個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新建roleTest類,通過調用user的hasRole、checkRole方法判斷用戶權限。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; } }
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類,獲取用戶信息,進行身份驗證。