Shiro:添加数据库操作和Realm

RBAC 概念

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

需要用到的jar包:

shiro-all-1.3.2.jar
mysql-connector-java-5.1.38.jar
commons-beanutils-1.8.3.jar
slf4j-api-1.7.2.jar
commons-logging-1.2.jar

一:只添加数据库

表结构

基于RBAC概念, 就会存在3 张基础表: 用户,角色,权限, 以及 2 张中间表来建立 用户与角色的多对多关系,角色与权限的多对多关系。 用户与权限之间也是多对多关系,但是是通过 角色间接建立的。
这里给出了表结构,导入数据库即可,新建一个数据库。

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;

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);

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

创建数据表实体类:

package Second_Shiro;

public class User {

	private int id;
	private String name;
	private String password;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	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;
	}
	
}

创建对数据表进行操作的DAO类:

package Second_Shiro;

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 ShiroDAO {

	public ShiroDAO() {
		// TODO Auto-generated constructor stub
		try {
			Class.forName("com.mysql.jdbc.Driver");
			
			}catch (ClassNotFoundException e) {
				// TODO: handle exception
				e.printStackTrace();
			}			
		}
	public Connection getConnect()  throws SQLException{
		return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8", "root", "123456");
	}
	/*
	 1. getPassword 方法:
		根据用户名查询密码,这样既能判断用户是否存在,也能判断密码是否正确
  		String sql = "select password from user where name = ?";
	 */
	
	public String getPassword(String username) {
		String sql="SELECT password  FROM user WHERE name=?";
		try (Connection connection = getConnect(); PreparedStatement preparedStatement = connection.prepareStatement(sql);){
			preparedStatement.setString(1, username);
			ResultSet resultSet=  preparedStatement.executeQuery();
			if(resultSet.next()) {
				return resultSet.getString("password");
			}
			
		} catch (SQLException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return null;
	}
	/*
	 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 = ?";
	 */
	public Set listRoles(String username){
		Set 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 connection = getConnect(); PreparedStatement preparedStatement = connection.prepareStatement(sql);){
			preparedStatement.setString(1, username);
			ResultSet resultSet=  preparedStatement.executeQuery();
			while(resultSet.next()) {
				roles.add(resultSet.getString(1));
			}
			
		} catch (SQLException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return roles;		
	}
	/*
	 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 =?";
	 */
	public Set listPermissions(String username){
		Set 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 connection = getConnect(); PreparedStatement preparedStatement = connection.prepareStatement(sql);){
			preparedStatement.setString(1, username);
			ResultSet resultSet=  preparedStatement.executeQuery();
			while(resultSet.next()) {
				permissions.add(resultSet.getString(1));
			}
			
		} catch (SQLException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return permissions;
	}
	
	
	public static void main(String[] args) {
        System.out.println(new ShiroDAO().listRoles("zhang3"));
        System.out.println(new ShiroDAO().listRoles("li4"));
        System.out.println(new ShiroDAO().listPermissions("zhang3"));
        System.out.println(new ShiroDAO().listPermissions("li4"));
    }
}

运行截图:

Shiro:添加数据库操作和Realm_第1张图片

二:Realm

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

Shiro:添加数据库操作和Realm_第2张图片

实体类和DAO不变,去除DAO的main函数,创建 DatabaseRealm ,继承自AuthorizingRealm,并实现方法:

package Third_Shiro;

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) {
		// TODO Auto-generated method stub
		//能进入这里,表示账号已经验证通过
		String username= (String)principalCollection.getPrimaryPrincipal();
		//通过ShiroDAO获取角色和权限
		Set permissions=new ShiroDAO().listPermissions(username);
		Set roles = new ShiroDAO().listRoles(username);
		
		//授权对象
		SimpleAuthorizationInfo simpleAuthorizationInfo= new SimpleAuthorizationInfo();
		
		//把获取的居然色和权限放进去
		simpleAuthorizationInfo.setStringPermissions(permissions);
		simpleAuthorizationInfo.setRoles(roles);
		
		return simpleAuthorizationInfo;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// TODO Auto-generated method stub
		 //获取账号密码
        UsernamePasswordToken t = (UsernamePasswordToken) token;
        String userName= token.getPrincipal().toString();
        String password= new String( t.getPassword());
        //获取数据库中的密码
        String passwordInDB = new ShiroDAO().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:

[main]
databaseRealm=Third_Shiro.DatabaseRealm
securityManager.realms=$databaseRealm

创建测试类:

package Third_Shiro;

import java.util.*;

import org.apache.shiro.SecurityUtils; 
import org.apache.shiro.authc.*;
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 TestShiroRealm {
    public static void main(String[] args) {
        //用户们
        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 users = new ArrayList<>();
         
        users.add(zhang3);
        users.add(li4);
        users.add(wang5);      
        //角色们
        String roleAdmin = "admin";
        String roleProductManager ="productManager";
         
        List roles = new ArrayList<>();
        roles.add(roleAdmin);
        roles.add(roleProductManager);
         
        //权限们
        String permitAddProduct = "addProduct";
        String permitAddOrder = "addOrder";
         
        List permits = new ArrayList<>();
        permits.add(permitAddProduct);
        permits.add(permitAddOrder);
         
        //登陆每个用户
        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("-------TestShiroRealm 分割线------");
         
        //判断能够登录的用户是否拥有某个角色
        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);
                    else
                        System.out.printf("%s\t 不拥有角色: %s\t%n",user.getName(),role);
                }
            }  
        }
        System.out.println("-------TestShiroRealm 分割线------");
 
        //判断能够登录的用户,是否拥有某种权限
        for (User user : users) {
            for (String permit : permits) {
                if(login(user)) {
                    if(isPermitted(user, permit))
                        System.out.printf("%s\t 拥有权限: %s\t%n",user.getName(),permit);
                    else
                        System.out.printf("%s\t 不拥有权限: %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 factory = new IniSecurityManagerFactory("classpath:Third_Shiro/shiro.ini");
        //获取安全管理者实例
        SecurityManager sm = factory.getInstance();
        //将安全管理者放入全局对象
        SecurityUtils.setSecurityManager(sm);
        //全局对象通过安全管理者生成Subject对象
        Subject subject = SecurityUtils.getSubject();
         
        return subject;
    }
     
    private static boolean login(User user) {
        Subject subject= getSubject(user);
        //如果已经登录过了,退出
        if(subject.isAuthenticated())
            subject.logout();
         
        //封装用户的数据
        UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());
        try {
            //将用户的数据token 最终传递到Realm中进行对比
            subject.login(token);
        } catch (AuthenticationException e) {
            //验证错误
            return false;
        }              
         
        return subject.isAuthenticated();
    }
     
}

运行图

Shiro:添加数据库操作和Realm_第3张图片

你可能感兴趣的:(Shiro,Java)