社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
上节我们讲到使用shiro.ini实现认证及授权,但是之前也提到过使用shiro.ini实现认证授权用的并不多,绝大多数情况是使用自定义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,权限管理这一块是完全不在话下的,好好学吧~ 哈哈
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!