一、权限认证核心要素
权限认证顾名思义,就是在应用系统中,控制谁能访问哪些资源。核心要素有仨:权限、角色、用户
权限:即操作资源的权利,如访问某个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<SecurityManager> 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(); } }
四、shiro与web集成进行权限认证
下面介绍在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配置文件。
<listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener> <!-- 添加shiro支持 --> <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> <!--省略loginServlet 和adminServlet配置ps.login 针对所有的/login请求,admin针对/admin-->
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类,获取用户信息,进行身份验证。