zoukankan      html  css  js  c++  java
  • Apache Shiro(二)-登录认证和权限管理数据库操作

    数据库支持

     上一篇中使用ini 配置文件进行了相关权限数据的配置。 但是实际工作中,我们都会把权限相关的内容放在数据库里。 所以本知识点讲解如何放在数据库里来撸。

    RBAC 概念

    RBAC 是当下权限系统的设计基础,同时有两种解释:
      一: Role-Based Access Control,基于角色的访问控制
        即,你要能够删除产品,那么当前用户就必须拥有产品经理这个角色
      二:Resource-Based Access Control,基于资源的访问控制
        即,你要能够删除产品,那么当前用户就必须拥有删除产品这样的权限

    表结构

    基于 RBAC 概念, 就会存在3 张基础表: 用户,角色,权限, 以及 2 张中间表来建立 用户与角色的多对多关系,角色与权限的多对多关系。 用户与权限之间也是多对多关系,但是是通过 角色间接建立的。

    这里给出了表结构,导入数据库即可。

    注: 补充多对多概念: 用户和角色是多对多,即表示:
      一个用户可以有多种角色,一个角色也可以赋予多个用户。 
      一个角色可以包含多种权限,一种权限也可以赋予多个角色。

     1 DROP DATABASE IF EXISTS shiro;
     2 CREATE DATABASE shiro DEFAULT CHARACTER SET utf8;
     3 USE shiro;
     4  
     5 drop table if exists user;
     6 drop table if exists role;
     7 drop table if exists permission;
     8 drop table if exists user_role;
     9 drop table if exists role_permission;
    10  
    11 create table user (
    12   id bigint auto_increment,
    13   name varchar(100),
    14   password varchar(100),
    15   constraint pk_users primary key(id)
    16 ) charset=utf8 ENGINE=InnoDB;
    17  
    18 create table role (
    19   id bigint auto_increment,
    20   name varchar(100),
    21   constraint pk_roles primary key(id)
    22 ) charset=utf8 ENGINE=InnoDB;
    23  
    24 create table permission (
    25   id bigint auto_increment,
    26   name varchar(100),
    27   constraint pk_permissions primary key(id)
    28 ) charset=utf8 ENGINE=InnoDB;
    29  
    30 create table user_role (
    31   uid bigint,
    32   rid bigint,
    33   constraint pk_users_roles primary key(uid, rid)
    34 ) charset=utf8 ENGINE=InnoDB;
    35  
    36 create table role_permission (
    37   rid bigint,
    38   pid bigint,
    39   constraint pk_roles_permissions primary key(rid, pid)
    40 ) charset=utf8 ENGINE=InnoDB;
    点击展开

    表数据

    这里基于 Shiro入门中的shiro.ini 文件,插入一样的用户,角色和权限数据。

     1 INSERT INTO `permission` VALUES (1,'addProduct');
     2 INSERT INTO `permission` VALUES (2,'deleteProduct');
     3 INSERT INTO `permission` VALUES (3,'editProduct');
     4 INSERT INTO `permission` VALUES (4,'updateProduct');
     5 INSERT INTO `permission` VALUES (5,'listProduct');
     6 INSERT INTO `permission` VALUES (6,'addOrder');
     7 INSERT INTO `permission` VALUES (7,'deleteOrder');
     8 INSERT INTO `permission` VALUES (8,'editOrder');
     9 INSERT INTO `permission` VALUES (9,'updateOrder');
    10 INSERT INTO `permission` VALUES (10,'listOrder');
    11 INSERT INTO `role` VALUES (1,'admin');
    12 INSERT INTO `role` VALUES (2,'productManager');
    13 INSERT INTO `role` VALUES (3,'orderManager');
    14 INSERT INTO `role_permission` VALUES (1,1);
    15 INSERT INTO `role_permission` VALUES (1,2);
    16 INSERT INTO `role_permission` VALUES (1,3);
    17 INSERT INTO `role_permission` VALUES (1,4);
    18 INSERT INTO `role_permission` VALUES (1,5);
    19 INSERT INTO `role_permission` VALUES (1,6);
    20 INSERT INTO `role_permission` VALUES (1,7);
    21 INSERT INTO `role_permission` VALUES (1,8);
    22 INSERT INTO `role_permission` VALUES (1,9);
    23 INSERT INTO `role_permission` VALUES (1,10);
    24 INSERT INTO `role_permission` VALUES (2,1);
    25 INSERT INTO `role_permission` VALUES (2,2);
    26 INSERT INTO `role_permission` VALUES (2,3);
    27 INSERT INTO `role_permission` VALUES (2,4);
    28 INSERT INTO `role_permission` VALUES (2,5);
    29 INSERT INTO `role_permission` VALUES (3,6);
    30 INSERT INTO `role_permission` VALUES (3,7);
    31 INSERT INTO `role_permission` VALUES (3,8);
    32 INSERT INTO `role_permission` VALUES (3,9);
    33 INSERT INTO `role_permission` VALUES (3,10);
    34 INSERT INTO `user` VALUES (1,'zhang3','12345');
    35 INSERT INTO `user` VALUES (2,'li4','abcde');
    36 INSERT INTO `user_role` VALUES (1,1);
    37 INSERT INTO `user_role` VALUES (2,2);
    点击展开

    User

    在原来的基础上增加了一个id字段。 其实。。。本知识点也没有用到这个id,只是和数据库关联了嘛,数据库里有,还是加上

     1 package com.how2java;
     2  
     3 public class User {
     4  
     5     private int id;
     6     private String name;
     7     private String password;
     8     public String getName() {
     9         return name;
    10     }
    11     public void setName(String name) {
    12         this.name = name;
    13     }
    14     public String getPassword() {
    15         return password;
    16     }
    17     public void setPassword(String password) {
    18         this.password = password;
    19     }
    20     public int getId() {
    21         return id;
    22     }
    23     public void setId(int id) {
    24         this.id = id;
    25     }
    26      
    27 }

    DAO

    这个DAO提供了和权限相关查询。 但是,并没有提供权限数据本身的维护。 比如没有做用户的增删改,角色和权限表也没有。 因为那些在提供了 表数据 的基础上,就不是必须的了。 
    为了专注于 Shiro 和 DAO 的结合,只提供必要的数据库操作支持。
    1. getPassword 方法:
    根据用户名查询密码,这样既能判断用户是否存在,也能判断密码是否正确
     
    String sql = "select password from user where name = ?";
     


    2. listRoles 方法:
    根据用户名查询此用户有哪些角色,这是3张表的关联
     
    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 = ?";
     



    3. listPermissions 方法:
    根据用户名查询此用户有哪些权限,这是5张表的关联
     
    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 =?";
     


    4. 主方法测试
    运行之后,看到如图所示 zhang3,li4 拥有的角色和权限和  Shiro入门中的shiro.ini 文件 中的数据是一致的
    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",
                    "admin");
        }
     
        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"));
        }
    }
    点击展开

    Realm 概念

    在 Shiro 中存在 Realm 这么个概念, Realm 这个单词翻译为 域,其实是非常难以理解的。 
    域 是什么鬼?和权限有什么毛关系? 这个单词Shiro的作者用的非常不好,让人很难理解。 
    那么 Realm 在 Shiro里到底扮演什么角色呢? 
    当应用程序向 Shiro 提供了 账号和密码之后, Shiro 就会问 Realm 这个账号密码是否对, 如果对的话,其所对应的用户拥有哪些角色,哪些权限。 
    所以Realm 是什么? 其实就是个中介。 Realm 得到了 Shiro 给的用户和密码后,有可能去找 ini 文件,就像Shiro 入门中的 shiro.ini,也可以去找数据库,就如同本知识点中的 DAO 查询信息。

    Realm 就是干这个用的,它才是真正进行用户认证和授权的关键地方。

    DatabaseRealm

    DatabaseRealm 就是用来通过数据库 验证用户,和相关授权的类。
    两个方法分别做验证和授权:
    doGetAuthenticationInfo(), doGetAuthorizationInfo()
    细节在代码里都有详细注释,请仔细阅读。

    注: DatabaseRealm 这个类,用户提供,但是不由用户自己调用,而是由 Shiro 去调用。 就像Servlet的doPost方法,是被Tomcat调用一样。
    那么 Shiro 怎么找到这个 Realm 呢? 那么就需要下一步,修改 shiro.ini
     
    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 {
     
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //能进入到这里,表示账号已经通过验证了
            String userName =(String) principalCollection.getPrimaryPrincipal();
            //通过DAO获取角色和权限
            Set<String> permissions = new DAO().listPermissions(userName);
            Set<String> roles = new DAO().listRoles(userName);
             
            //授权对象
            SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
            //把通过DAO获取到的角色和权限放进去
            s.setStringPermissions(permissions);
            s.setRoles(roles);
            return s;
        }
     
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //获取账号密码
            UsernamePasswordToken t = (UsernamePasswordToken) token;
            String userName= token.getPrincipal().toString();
            String password= new String( t.getPassword());
            //获取数据库中的密码
            String passwordInDB = new DAO().getPassword(userName);
     
            //如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不是抛出具体错误原因,免得给破解者提供帮助信息
            if(null==passwordInDB || !passwordInDB.equals(password))
                throw new AuthenticationException();
             
            //认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
            SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,password,getName());
            return a;
        }
     
    }

    修改 shiro.ini

    前面准备了DatabaseRealm,那么在配置文件里,就指定当前的realm 是 他。 默认情况下是找 IniRealm。
    shiro.ini 中原本的数据信息,都删除掉了
     
    [main]
    databaseRealm=com.how2java.DatabaseRealm
    securityManager.realms=$databaseRealm

    TestRealm

    TestRealm 没任何变化,运行效果和基于 ini的效果一模一样。
    TestRealm
      1 package com.how2java;
      2  
      3 import java.util.ArrayList;
      4 import java.util.List;
      5  
      6 import org.apache.shiro.SecurityUtils;
      7 import org.apache.shiro.authc.AuthenticationException;
      8 import org.apache.shiro.authc.UsernamePasswordToken;
      9 import org.apache.shiro.config.IniSecurityManagerFactory;
     10 import org.apache.shiro.mgt.SecurityManager;
     11 import org.apache.shiro.subject.Subject;
     12 import org.apache.shiro.util.Factory;
     13  
     14 public class TestShiro {
     15     public static void main(String[] args) {
     16         //用户们
     17         User zhang3 = new User();
     18         zhang3.setName("zhang3");
     19         zhang3.setPassword("12345");
     20  
     21         User li4 = new User();
     22         li4.setName("li4");
     23         li4.setPassword("abcde");
     24          
     25         User wang5 = new User();
     26         wang5.setName("wang5");
     27         wang5.setPassword("wrongpassword");
     28  
     29         List<User> users = new ArrayList<>();
     30          
     31         users.add(zhang3);
     32         users.add(li4);
     33         users.add(wang5);      
     34         //角色们
     35         String roleAdmin = "admin";
     36         String roleProductManager ="productManager";
     37          
     38         List<String> roles = new ArrayList<>();
     39         roles.add(roleAdmin);
     40         roles.add(roleProductManager);
     41          
     42         //权限们
     43         String permitAddProduct = "addProduct";
     44         String permitAddOrder = "addOrder";
     45          
     46         List<String> permits = new ArrayList<>();
     47         permits.add(permitAddProduct);
     48         permits.add(permitAddOrder);
     49          
     50         //登陆每个用户
     51         for (User user : users) {
     52             if(login(user))
     53                 System.out.printf("%s 	成功登陆,用的密码是 %s	 %n",user.getName(),user.getPassword());
     54             else
     55                 System.out.printf("%s 	成功失败,用的密码是 %s	 %n",user.getName(),user.getPassword());
     56         }
     57          
     58         System.out.println("-------how2j 分割线------");
     59          
     60         //判断能够登录的用户是否拥有某个角色
     61         for (User user : users) {
     62             for (String role : roles) {
     63                 if(login(user)) {
     64                     if(hasRole(user, role))
     65                         System.out.printf("%s	 拥有角色: %s	%n",user.getName(),role);
     66                     else
     67                         System.out.printf("%s	 不拥有角色: %s	%n",user.getName(),role);
     68                 }
     69             }  
     70         }
     71         System.out.println("-------how2j 分割线------");
     72  
     73         //判断能够登录的用户,是否拥有某种权限
     74         for (User user : users) {
     75             for (String permit : permits) {
     76                 if(login(user)) {
     77                     if(isPermitted(user, permit))
     78                         System.out.printf("%s	 拥有权限: %s	%n",user.getName(),permit);
     79                     else
     80                         System.out.printf("%s	 不拥有权限: %s	%n",user.getName(),permit);
     81                 }
     82             }  
     83         }
     84     }
     85      
     86     private static boolean hasRole(User user, String role) {
     87         Subject subject = getSubject(user);
     88         return subject.hasRole(role);
     89     }
     90      
     91     private static boolean isPermitted(User user, String permit) {
     92         Subject subject = getSubject(user);
     93         return subject.isPermitted(permit);
     94     }
     95  
     96     private static Subject getSubject(User user) {
     97         //加载配置文件,并获取工厂
     98         Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
     99         //获取安全管理者实例
    100         SecurityManager sm = factory.getInstance();
    101         //将安全管理者放入全局对象
    102         SecurityUtils.setSecurityManager(sm);
    103         //全局对象通过安全管理者生成Subject对象
    104         Subject subject = SecurityUtils.getSubject();
    105          
    106         return subject;
    107     }
    108      
    109     private static boolean login(User user) {
    110         Subject subject= getSubject(user);
    111         //如果已经登录过了,退出
    112         if(subject.isAuthenticated())
    113             subject.logout();
    114          
    115         //封装用户的数据
    116         UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());
    117         try {
    118             //将用户的数据token 最终传递到Realm中进行对比
    119             subject.login(token);
    120         } catch (AuthenticationException e) {
    121             //验证错误
    122             return false;
    123         }              
    124          
    125         return subject.isAuthenticated();
    126     }
    127      
    128 }
    点击展开

     最后

    代码下载地址:https://gitee.com/fengyuduke/my_open_resources/blob/master/shiro-database.zip

     
     
  • 相关阅读:
    64_l2
    64_l1
    64_k2
    64_k1
    64_j2
    64_j1
    64_g6
    64_g5
    64_g4
    64_g3
  • 原文地址:https://www.cnblogs.com/fengyuduke/p/10401381.html
Copyright © 2011-2022 走看看