Marco's Java【Shiro入门(三) 之 自定义Realm实现认证及授权】 - Go语言中文社区

Marco's Java【Shiro入门(三) 之 自定义Realm实现认证及授权】


前言

上节我们讲到使用shiro.ini实现认证及授权,但是之前也提到过使用shiro.ini实现认证授权用的并不多,绝大多数情况是使用自定义Realm对接我们的数据库实现认证及授权,那么本节,咱们来看看怎么定制属于自己的Realm吧~

自定义Realm实现认证

Realm领域是Shiro对外提供的一个接口,那么既然是接口,则必然有它的实现类了,我们先点开看看Realm中到底有哪些实现类吧
在这里插入图片描述
首当其冲,最顶层无疑是我们Realm接口,CachingRealm负责缓存处理,AuthenticationRealm负责认证,AuthorizingRealm负责授权,而我们通常自定义的realm继承AuthorizingRealm,再回到上一节的文末我让大家思考的问题,就是先认证?还是先授权?
相信,看到这个Realm的树结构,大家应该能够想明白了吧?不难看出AuthorizingRealm授权领域是AuthenticationRealm认证领域的子类,那么AuthenticationRealm有的功能AuthorizingRealm当然都会有,因此答案是先认证,再授权。结合我们的现实情况也是一样的,只有你登陆了,证明了你是管理员,才能对其他的进行授权吧,一般情况,我们在建数据库的时候,会开一个"黑户","黑户"是不存在注册授权的概念的,因此就不用去纠结先有鸡还是先有蛋的概念啦~

好啦,到此我们对Realm的结构也有一定的了解了,那么各位应该知道,我们的自定义Realm该继承谁了吧~
第一步:创建项目
在这里插入图片描述
为了让演示稍微逼真,不那么磕碜,我这里就造个"假",写个UserService意思意思下吧,哈哈,大家有兴趣自己可以连接数据库玩玩,后面的Shiro进阶的Shiro+SMM集成Maven系列三部曲,我会带着大家完整的走一遍
Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(上)】
Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(中)】
Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(下)】
但是如果没有Shiro基础的朋友,还是老老实实先跟着我的思路走咯~ 否则很难理解

我这里就上UserServiceImpl的代码啦

package com.marco.service.impl;

import java.util.ArrayList;
import java.util.List;

import com.marco.domain.User;
import com.marco.service.UserService;

public class UserServiceImpl implements UserService {

	@Override
	public User queryUserByUserName(String userName) {
		//模拟数据库查询用户信息
		switch (userName) {
		case "spiderman":
			return new User("spiderman", "123456");
		case "ironman":
			return new User("ironman", "123456");
		case "marco":
			return new User("marco", "123456");
		default:
			return null;
		}
	}

	@Override
	public List<String> queryRolesByUserName(String userName) {
		//模拟给用户授权角色
		List<String> roles = new ArrayList<String>();
		roles.add("systemAdmin");
		roles.add("statisticsAdmin");
		roles.add("customerAdmin");
		return roles;
	}

	@Override
	public List<String> queryPermissionsByUserName(String userName) {
		//模拟给角色分配权限
		List<String> permissions = new ArrayList<String>();
		permissions.add("user:query");
		permissions.add("user:delete");
		permissions.add("user:add");
		return permissions;
	}
}

第二步:创建ActiveUser容器
创建ActiveUser的用意是做一个储存用户信息的容器,因为用户的信息不仅仅只包含用户的基本信息,还包含用户的角色和权限,在后面使用自定义Realm时,我们需要一个这样的容器,将关键信息都储存起来,放在Realm领域中,这样,这样在这个作用域中,我们就可以随时将容器中的值取出来,大家接着往下看应该就可以明白了。

package com.marco.realm;

import java.util.List;

import com.marco.domain.User;

public class ActiveUser{
	private User user;
	
	private List<String> roles;
	
	private List<String> permissions;

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	public List<String> getRoles() {
		return roles;
	}

	public void setRoles(List<String> roles) {
		this.roles = roles;
	}

	public List<String> getPermissions() {
		return permissions;
	}

	public void setPermissions(List<String> permissions) {
		this.permissions = permissions;
	}
}

第三步:创建自定义UserRealm

package com.marco.realm;

import java.util.List;

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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import com.marco.domain.User;
import com.marco.service.UserService;
import com.marco.service.impl.UserServiceImpl;



public class UserRealm extends AuthorizingRealm{
	//模拟Spring的IoC/DI的自动装配
	UserService userService = new UserServiceImpl();
	
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//获取当前的principal身份信息(一般是用户名、邮箱或者手机号等等)
		String principal = (String) token.getPrincipal();
		System.out.println("Current principal is " + principal);
		//获取当前的credential凭证信息(一般是密码等等)
		char[] credentials = (char[]) token.getCredentials();
		String credential = new String(credentials);
		System.out.println("Current credential is " + credential);
		System.out.println("###@#####################################################");
		//获取账号信息
		String userName = principal.toString();
		//根据userrname获取用户
		User user = userService.queryUserByUserName(userName);
		//判断用户是否存在
		if(null != user) {
			//模拟获取用户的角色和权限
			List<String> roles = userService.queryRolesByUserName(user.getUserName());
			List<String> permissions = userService.queryPermissionsByUserName(user.getUserName());
			ActiveUser activeUser = new ActiveUser();
			//将用户,用户角色,用户权限存储在principals容器中,便于在doGetAuthorizationInfo()中获取
			activeUser.setPermissions(permissions);
			activeUser.setRoles(roles);
			activeUser.setUser(user);
			/**
			 * arg1:参数传递对象  可以在授权的方法或者在subject里面得到,相当与一个可贯穿整个流程的容器
			 * arg2:数据库中查出来的密码,一般是加密之后的密码,后面我们会讲到MD5加密
			 * arg3:当前的Realm的名称
			 * 
			 */
			SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(activeUser, user.getPassword(), "UserRealm");
			return authenticationInfo;
		}  else {
			return null;
		}
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		//从principals中获取当前的User
		ActiveUser user  = (ActiveUser) principals.getPrimaryPrincipal();
		//获取当前的principal身份信息(一般是用户名、邮箱或者手机号等等)
		List<String> roles = user.getRoles();
		List<String> permissions = user.getPermissions();
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		//添加用户角色,实际开发中这些信息都是在数据库中查询出来的
		if(null != roles && roles.size() > 0) {
			authorizationInfo.addRoles(roles);
		}
		//添加用户权限
		if(null != permissions && permissions.size() > 0) {
			authorizationInfo.addStringPermissions(permissions);
		}
		//将用户原本的权限也添加进去
		authorizationInfo.addStringPermissions(user.getPermissions());
		return authorizationInfo;
	}
}

第四步:绑定安全管理器和UserRealm并实现登录
接下来我们创建一个控制器,将自定义的UserRealm绑定到我们的SecurityManager中,绑定的方式有两种,一种是使用我们之前的shiro.ini文件配置的方式,另一种是直接通过securityManager.setRealm()的方式,我们先来看第一种。

方式一:shiro.ini文件实现Realm绑定

#配置自定义的Realm信息
[main]
##等同于<bean id="userRealm" class="com.marco.realms.UserRealm" />
userRealm=com.marco.realm.UserRealm 
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
##给securityManager注入realm,$等同于Spring配置文件的ref,指向id(userRealm)
securityManager.realm=$userRealm

首先还是准备shiro.ini文件,写明以上的配置,经本人测试,如果你的shiro的版本是1.4.1以上,只需要填写userRealm=com.marco.realm.UserRealm 即可

package com.marco.test;

import java.util.Arrays;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;

public class LoginController{
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		
		//模拟获取登录的账号密码
		String username = "spiderman";
		String password = "123456";
		//将账号密码封装到token(令牌)中
		UsernamePasswordToken token = new UsernamePasswordToken(username, password);
		
		//创建安全管理工厂
		IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
		//获取DefaultSecurityManager安全管理对象
		SecurityManager securityManager = securityManagerFactory.getInstance();
		//把安全管理器绑定到当前运行环境
		SecurityUtils.setSecurityManager(securityManager);
		//从当前环境里面得到Subject主体对象
		Subject subject = SecurityUtils.getSubject();
		//调用主体的登陆方法
		try {
			subject.login(token);
			System.out.println("登陆成功!");
		} catch (IncorrectCredentialsException e) {
			System.err.println("密码不正确");
		}catch (UnknownAccountException e) {
			System.err.println("用户名不存在");
		}
		boolean hasSysRole = subject.hasRole("systemAdmin");
		boolean hasStatRole = subject.hasRole("statisticsAdmin");
		System.out.println("用户是否拥有systemAdmin角色:" + hasSysRole);
		System.out.println("用户是否拥有statisticsAdmin角色:" + hasStatRole);
		
		boolean[] permitted = subject.isPermitted("user:query","user:update");
		System.out.println("用户是否拥有user:query和user:delete的权限:" + Arrays.toString(permitted));
	}
}

方式二:使用securityManager注入Realm
注意了,当我们使用这种方式的时候,必须要将SecurityManager转换为DefaultSecurityManager,因为SecurityManager是一个接口,没有setRealm的方法,而SecurityManager下的实现类就只有DefaultSecurityManager和DefaultWebSecurityManager了,DefaultWebSecurityManager是用于Web的,我们暂时还用不到,因此我们强转为DefaultSecurityManager就可以了。
在这里插入图片描述

package com.marco.test;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

import com.marco.realm.UserRealm;

public class LoginController {
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		
		//模拟获取登录的账号密码
		String username = "marco";
		String password = "123456";
		//将账号密码封装到token(令牌)中
		UsernamePasswordToken token = new UsernamePasswordToken(username, password);
		
		//创建安全管理工厂
		IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory();
		//获取DefaultSecurityManager安全管理对象
		DefaultSecurityManager securityManager = (DefaultSecurityManager) securityManagerFactory.getInstance();
		//设置Realm获取数据(Realm在开发中是从数据库中获取数据)
		securityManager.setRealm(new UserRealm());
		//把安全管理器绑定到当前运行环境
		SecurityUtils.setSecurityManager(securityManager);
		//从当前环境里面得到Subject主体对象
		Subject subject = SecurityUtils.getSubject();
		//调用主体的登陆方法
		try {
			subject.login(token);
			System.out.println("登陆成功!");
		} catch (IncorrectCredentialsException e) {
			System.err.println("密码不正确");
		}catch (UnknownAccountException e) {
			System.err.println("用户名不存在");
		}
		
	}
}

subject.login(token)方法本质上就是调用我们UserRealm中的doGetAuthenticationInfo(AuthenticationToken token)方法做验证,而UserRealm中还有一个很重要的对象就是SimpleAuthenticationInfo,它就相当于一个传报员(报信的),activeUser就是我们之前定义的储存用户信息的容器,只要在Realm领域,我们都可以把这里面的值给取出来,其实我们SimpleAuthenticationInfo本质上就是带着我们输入的密码去找凭证匹配器去匹配,关于细节方面的分析,后面Shiro进阶篇分析Shiro底层实现原理的时候我们会详细说。

/**
 * arg1:参数传递对象  可以在授权的方法或者在subject里面得到,相当与一个可贯穿整个流程的容器
 * arg2:数据库中查出来的密码,一般是加密之后的密码,后面我们会讲到MD5加密
 * arg3:当前的Realm的名称
 */
SimpleAuthenticationInfo authenticationInfo 
	= new SimpleAuthenticationInfo(activeUser, user.getPassword(), "UserRealm");

有朋友会有疑惑了,就是我们把activeUser放到Realm领域中具体有什么作用呢?其实大家回过头来看我之前实现授权的代码,应该会有一种顿悟的感觉,这里的PrincipalCollection其实就是我们之前存入的activeUser对象,大家试想一下,我们做授权肯定需要拿到用户的信息、角色信息和权限信息的对吧,但是如果我们将查询用户角色和权限的部分代码放在授权这里

List<String> roles = userService.queryRolesByUserName(user.getUserName());
List<String> permissions = userService.queryPermissionsByUserName(user.getUserName());

那么我们的登录控制器就没有办法通过ActiveUser activeUser = (ActiveUser) subject.getPrincipal() 方法取到用户的角色和权限信息了,因此这么去做是有考究的。
在这里插入图片描述

第五步:测试
我这里就使用方式一做测试啦,咱们先输入错误的密码
在这里插入图片描述
再输入错误的账号
在这里插入图片描述
接着输入正确的账号和密码
在这里插入图片描述
是不是很完美!再结合我们的Web项目,完全可以实现系统的权限管理,比如菜单是否可见,最恐怖的是还可以精确到按钮的权限管理!所以说如果能用好Shiro,权限管理这一块是完全不在话下的,好好学吧~ 哈哈

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_44698119/article/details/98457620
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢