Shiro自定义Realm实现认证和授权(五)

戒色诗: 二八佳人体似酥,腰间仗剑斩凡夫。虽然不见人头落,暗里教君骨髓枯。

一. 为什么要自定义Realm

前面,我们在配置 Shiro 的用户信息和权限数据的时候,都是从 shiro.ini 配置文件里面读取的,

这些数据能不能从我们本地的数据库中进行读取呢? 当然可以, 可以通过 JdbcRealm 进行读取,

但是通过 JdbcRealm进行读取时,数据库的表的名称,表的字段都必须固定,非常不利于扩展。

所以,我们需要自定义Realm.

自定义Realm 时,我们常常 使用 org.apache.shiro.realm.AuthorizingRealm 类。

让我们自定义的类, 继承 AuthorizingRealm 抽象类

public abstract class AuthorizingRealm
       extends AuthenticatingRealm
      implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware{

	...
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection paramPrincipalCollection);

	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken paramAuthenticationToken)

	...


}

AuthorizingRealm 有两个抽象方法, doGetAuthorizationInfo 和 doGetAuthenticationInfo

其中, doGetAuthorizationInfo 进行授权操作, doGetAuthenticationInfo 进行认证操作。

认证操作时,常常返回 AuthenticationInfo的子类 SimpleAuthenticationInfo

授权操作时, 常常返回 AuthorizationInfo的子类 simpleAuthorizationInfo

关于Shiro 自定义 Realm获取数据的使用方式,我们详细了解一下。

二. 自定义Realm 获取虚拟的认证授权数据

二.一 创建自定义Realm,继承 AuthorizingRealm 接口

MyRealm

package com.yjl.customer;

public class MyRealm extends AuthorizingRealm{
	
	@Override
	public String getName() {
		return "MyRealm";
	}
	
	//授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection paramPrincipalCollection) {
		return null;
	}
	//认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken paramAuthenticationToken)
			throws AuthenticationException {
		return null;
	}

}

二.二 创建配置文件 customer.ini,用于注入自定义的Realm

[main]
#配置自定义realm
myRealm=com.yjl.customer.MyRealm
#注入自定义的realm
securityManager.realm=$myRealm

二.三 在MyRealm 文件中编写认证的操作

//认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken paramAuthenticationToken)
			throws AuthenticationException {
		System.out.println("进入认证");
		//获取要登录的用户信息
		//UsernamePasswordToken token=(UsernamePasswordToken)paramAuthenticationToken;
		//获取用户名
		//String userName=token.getUsername();
		
		//也可以通过获取身份信息,然后转成 String 类型
		String userName=(String)paramAuthenticationToken.getPrincipal();
		//从数据库里面查询该用户的密码,省略数据库查询,直接固化下来,该用户的密码就是 1234 
		String password="1234";	
		SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo(userName, password, 
				getName());
		return simpleAuthenticationInfo;
		
	}

SimpleAuthenticationInfo 对象里面放置三个参数, 身份,证明 和 getName()

二.四 在MyRealm 文件中编写授权的操作

	
	//授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection paramPrincipalCollection) {
		//获取凭证 用户名
		System.out.println("进入授权");
		String userName=(String)(paramPrincipalCollection.getPrimaryPrincipal());
		System.out.println("输出登录者的凭证:"+userName);
		if(userName==null){ //未认证通过的情况
			return null;
		}
		//模拟权限,手动添加
		List<String> priList=new ArrayList<String>();
		
		//依旧没有修改的权限
		priList.add("user:add");
		priList.add("user:delete");
		priList.add("user:select");
		priList.add("user:toList");
		
		//创建 SimpleAuthorizationInfo 对象
		SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
		
		//添加角色, 通常不用角色判断,用权限判断
		simpleAuthorizationInfo.addRole("role1");
		
		for(String pri:priList){
			//添加权限
			simpleAuthorizationInfo.addStringPermission(pri);
		}
		return simpleAuthorizationInfo;
		
	}

这样,一个自定义的 Realm 就实现了

二.五 编写测试 MyRealmTest

package com.yjl.customer;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
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 MyRealmTest {
	public static void main(String[] args) throws Exception{
		//1. 创建factory
		Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:customer.ini");
		//2 获取实例
		SecurityManager securityManager=factory.getInstance();
		//3 设置
		SecurityUtils.setSecurityManager(securityManager);
		
		//4. 获取 Subject
		
		Subject subject=SecurityUtils.getSubject();
		//5. 设置token
		
		UsernamePasswordToken token=new UsernamePasswordToken("yuejl","1234");
		
		//6. 执行登录操作
		try{
			subject.login(token);	
			System.out.println(subject.getPrincipal()+"登录成功");
			
			boolean flag=subject.hasRole("role1");
			System.out.println("是否拥有角色 role1:"+flag);
			//关于角色验证的那些方法,都可以使用
			flag=subject.hasRole("role2");
			System.out.println("是否拥有角色role2:"+flag);
			
			//关于权限验证的那些方法,都可以使用
			flag=subject.isPermitted("user:add");
			System.out.println("是否拥有权限 user:add "+flag);
			flag=subject.isPermitted("user:update");
			System.out.println("是否拥有权限 user:update "+flag);
			
		}catch(Exception e){
			e.printStackTrace();
			System.out.println("用户名或者密码不正确");
		}
	}

}

控制台打印输出:

有图片

权限检查了四次,所以调用了四次 doGetAuthorizationInfo() 方法, 很浪费资源, 所以常常会进行缓存处理,后面老蝴蝶会讲缓存处理。

发现,我们可以实现自定义的Realm.

然而,用户的密码和权限数据,都只是模拟数据,能不能换成真实的数据,真正从数据库里面查询的呢?

三. 自定义Realm 获取数据库里真实的认证和授权数据

采用 Servlet实现RBAC权限管理(二) 的代码结构,

数据库仍然是 rbac 数据库, 没有看到的读者,一定要先看这一章节的内容。

dao,pojo,utils 包,还有数据库 jdbc.properties,数据库内容 均使用以前的内容。
在这里插入图片描述

三.一 创建自定义的Realm

MyDBRealm ,继承 AuthorizingRealm 抽象类

public class MyDBRealm extends AuthorizingRealm{

	private UserDao userDao=new UserDaoImpl();
	private PrivilegeDao privilegeDao=new PrivilegeDaoImpl();
	private RoleDao roleDao=new RoleDaoImpl();
	@Override
	public String getName() {
		return "MyDBRealm";
	}
	//授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection paramPrincipalCollection) {
		return null;
	}
	//认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken paramAuthenticationToken)
			throws AuthenticationException {
		return null;
	}

}

三.二 编写配置文件 customerdb.ini

[main]
#配置自定义realm
myRealm=com.yjl.customer.MyDBRealm
#注入自定义的realm
securityManager.realm=$myRealm

三.三 在MyDBRealm 编写认证操作

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken paramAuthenticationToken)
		throws AuthenticationException {
	System.out.println("进入认证");
	String code=(String)paramAuthenticationToken.getPrincipal();
	//根据用户名,去查询相应的数据
	User user=userDao.getInfoByNameAndValue("select * from user","code", code);
	
	if(user==null){
		//没有查询出来
		return null;
	}
	SimpleAuthenticationInfo simpleAuthenticationInfo=
			new SimpleAuthenticationInfo(user,user.getPassword(),getName());
	
	return simpleAuthenticationInfo;
	
	
}

注意,这个时候身份并不是以前的用户名,而是整个查询出来的对象, 把数据库中完整的对象当成一个身份,传入进去。 认证通过后,获取时,获取的则是整个完整的对象。

三.四 在MyDBRealm 中编写授权操作

//授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection paramPrincipalCollection) {
		System.out.println("进入授权");
		User user=(User)paramPrincipalCollection.getPrimaryPrincipal();
		System.out.println("输出登录的用户编号:"+user.getCode());
		
		//查询权限
		String priSql="select a.* from privilege a where a.id in ( select rp.pid from user_role ur "
				+ " left join role_privilege rp "
				+"on ur.rid=rp.rid  where ur.uid=? ) and a.type=?";
		List<Privilege> privilegeList= privilegeDao.findInfosBySql(priSql,user.getId(),2);
		
		SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();		
		
		for(Privilege pri:privilegeList){
			if(pri.getPercode()!=null&&!("".equals(pri.getPercode()))){	
				simpleAuthorizationInfo.addStringPermission(pri.getPercode());
			}
		}
		
		//查询角色
		
		String roleSql="select a.* from role a left join user_role b on a.id=b.rid "
				+" where b.uid=?";
		
		List<Role> roleList=roleDao.findInfosBySql(roleSql, user.getId());
		
		for(Role role:roleList){
			simpleAuthorizationInfo.addRole(role.getId()+"");
		}
		
		return simpleAuthorizationInfo;	
	}

三.五 编写测试 MyRealmDBTest

package com.yjl.customer;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;

import com.yjl.pojo.User;
import com.yjl.utils.MD5Utils;
public class MyRealmDBTest {
	public static void main(String[] args) throws Exception{
		//1. 创建factory
		Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:customerdb.ini");
		//2 获取实例
		SecurityManager securityManager=factory.getInstance();
		//3 设置
		SecurityUtils.setSecurityManager(securityManager);
		
		//4. 获取 Subject
		
		Subject subject=SecurityUtils.getSubject();
		//5. 设置token
		
		UsernamePasswordToken token=new UsernamePasswordToken("admin",MD5Utils.md5("1234"));
		
		//6. 执行登录操作
		try{
			subject.login(token);	
			User user=(User)subject.getPrincipal();
			//可以将这个user 设置到session 里面
			System.out.println(user.toString()+"登录成功");
			
			//admin 是管理员
			boolean flag=subject.hasRole("1");
			System.out.println("是否拥有角色 管理员:"+flag);
			//关于角色验证的那些方法,都可以使用
			flag=subject.hasRole("2");
			System.out.println("是否拥有角色 经理:"+flag);
			
			//admin有删除权限,没有添加权限 
			flag=subject.isPermitted("dept:add");
			System.out.println("是否拥有权限 dept:add "+flag);
			flag=subject.isPermitted("dept:delete");
			System.out.println("是否拥有权限 dept:delete "+flag);
			
		}catch(Exception e){
			e.printStackTrace();
			System.out.println("用户名或者密码不正确");
		}
	}

}

控制台打印输出:

有图片

提示警告 SSL, 只要不使用 ssl 即可。

只需要改变数据库的 url 配置, 添加上 useSSL=false 即可。(在jdbc.properties 文件里面)

url=jdbc:mysql://localhost:3306/rbac?characterEncoding=UTF-8&useSSL=false

这样就不会有 ssl 警告了。

本章节代码链接为:

链接:https://pan.baidu.com/s/1pNSP33IhUL1hoD6wD4_1fw 
提取码:6ep5

谢谢您的观看,我是两个蝴蝶飞, 如果喜欢,请关注我,再次感谢 !!!

posted @ 2021-01-29 09:24  两个蝴蝶飞  阅读(353)  评论(0编辑  收藏  举报