自定义Realm
写在前面
通过前面的学习,我们知道实际进行权限信息验证的是Realm,而Shiro框架内部提供了两种实现,一种是查询.ini
文件的IniRealm;另一种是查询数据库的JdbcRealm。那么本篇就来分别研究这两个实现。
本篇主要学习以下内容:(1)Realm;(2)Shiro功能;(3)Shiro优点:(4)Shiro架构:(5)Shiro概念:(6)Shiro认证流程:(7)Shiro授权流程。
Realm
Shiro从Realm中获取安全数据(用户、角色、权限),也就是说SecurityManager想要验证用户身份,那么它必须从Realm中获取安全数据进而来确定用户的身份是否合法,之后判断用户是否具有某个角色,某个权限。
自定义Realm
一般来说,自定义Realm都需要继承org.apache.shiro.realm.AuthorizingRealm
类,查看一下这个类的源码,如下所示:
1 | public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {} |
可以看到这个AuthorizingRealm(授权)类继承了AuthenticatingRealm(认证)类,也就是说授权必须在认证之后才能进行,这个是正常逻辑,先进行用户身份认证,之后对用户进行权限授权。其实你如果自己研究的话,可以发现这个AuthenticatingRealm(认证)类是继承了CachingRealm(缓存)类:
1 | public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {} |
在前面认证的时候,我们需要使用用户名和密码来构造一个UsernamePasswordToken
对象,而这个UsernamePasswordToken
其实是AuthenticationToken
的子类:
1 | public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {} |
而这个AuthenticationToken则是一个接口,里面有两个方法,其中getPrincipal方法用户获取用户身份(通常为用户名),而getCredentials方法则用户获取用户凭证(通常为用户密码):
1 | public interface AuthenticationToken extends Serializable { |
接下来看一下此时的用户认证流程:
(1)将AuthenticationToken
转为UsernamePasswordToken
;
(2)从UsernamePasswordToken
中获取username;
(3)调用数据库中的方法,从数据库中查询username所对应的记录;
(4)判断(3)中得到的记录是否存在,不存在则抛出UnkownAccountException
异常;
(5)根据用户信息来决定其抛出的其他异常;
(6)根据用户需要来重写AuthorizingRealm
类中的doGetAuthenticationInfo(AuthenticationToken authenticationToken)
方法。在该方法内,构造一个AuthenticationInfo
对象,通常使用SimpleAuthenticationInfo
对象,主要这个SimpleAuthenticationInfo对象有多个重载方法,这里主要使用下面这个方法:
1 | public SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) { |
可以看到它有四个参数,第一个是principal,表示认证的主体信息,可以是username,也可以是数据库对应的用户的实体类对象;第二个是hashedCredentials,表示用户凭证(通常为用户密码);第三个是credentialsSalt,表示盐值;第四个是realmName,即当前自定义Realm对象的name,通常直接调用父类的getName()
方法即可。
IniRealm
接下来通过一个实例来学习IniRealm如何通过查询.ini
文件中的信息来进行权限信息验证:
第一步,新建项目。使用Maven新建一个名为envy-shiroini的文件,然后在其pom.xml依赖文件中新增如下依赖:
1 | <dependencies> |
第二步,在项目src/main/resources
目录下新建一个shiro.ini文件,然后在里面新增如下信息:
1 | #定义用户 |
第三步,在src/main/java
目录下新建shiroini目录,并在shiroini目录中新建一个User类,其中的代码如下:
1 | public class User { |
public class ShiroIniTest {
/**
* 返回一个Subject对象
*/
private static Subject getSubject() {
//1、创建一个SecurityManager对象
DefaultSecurityManager defaultSecurityManager =new DefaultSecurityManager();
//2、创建一个IniRealm对象
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
//3、设置Realm信息
defaultSecurityManager.setRealm(iniRealm);
//4、将安全管理者放入全局对象
SecurityUtils.setSecurityManager(defaultSecurityManager);
//5、全局对象通过安全管理者生成Subject对象
Subject subject = SecurityUtils.getSubject();
return subject;
}
/**
* 判断用户是否登录
*/
public static boolean login(User user) {
Subject subject = getSubject();
if (subject.isAuthenticated()) {
//如果用户已经登录,那么就退出
subject.logout();
}
//用户未登录,需要取出用户信息并进行验证
UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());
try {
//将用户信息token传递到IniRealm进行验证
subject.login(token);
} catch (AuthenticationException e) {
//验证失败
return false;
}
return subject.isAuthenticated();
}
/**
* 判断用户是否有对应角色
*/
public static boolean hasRole(String role) {
Subject subject = getSubject();
return subject.hasRole(role);
}
/**
* 判断用户是否有对应权限
*/
public static boolean isPermitted(String permission) {
Subject subject = getSubject();
return subject.isPermitted(permission);
}
public static void main(String[] args){
//模拟几个用户,其中envy和book在之前的shiro.ini文件中,movie用户不存在
List<User> userList = Arrays.asList(
new User("envy","1234"),
new User("book","4321"),
new User("movie","1234")
);
//定义角色信息
List<String> rolesList = Arrays.asList("admin","producter");
//定义权限信息
List<String> permitsList = Arrays.asList("listProduct","listOrder");
System.out.println("*********用户登录*********");
//用户登录
for(User user:userList){
if(login(user)){
System.out.println(String.format("%s 登录成功,登录密码为:%s",user.getName(),user.getPassword()));
}else{
System.out.println(String.format("%s 登录失败,使用密码为:%s",user.getName(),user.getPassword()));
}
}
System.out.println("*********角色判断*********");
//判断用户是否属于某个角色
for(User user:userList){
for(String role:rolesList){
if(login(user)){
if(hasRole(role)){
System.out.println(String.format("%s 属于%s角色",user.getName(),role));
}else{
System.out.println(String.format("%s 不属于%s角色",user.getName(),role));
}
}
}
}
System.out.println("*********权限判断*********");
//判断用户是否具有某个权限
for(User user:userList){
for(String permit:permitsList){
if (login(user)){
if(isPermitted(permit)){
System.out.println(String.format("%s 具有%s权限",user.getName(),permit));
}else{
System.out.println(String.format("%s 不具有%s权限",user.getName(),permit));
}
}
}
}
};
}
1 | 第五步,运行ShiroIniTest测试类,可以发现运行结果如下所示: |
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
1 | 第二步,在`src/main/java`目录下新建shirojdbc目录,并在shirojdbc目录中新建一个User类,其中的代码如下: |
public class User {
private String name;
private String password;
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;
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public User() {}
}
1 | 第三步,在`src/main/java/shirojdbc`目录下新建一个MyUserRealm类,注意这个类需要继承Shiro的AuthorizingRealm类,并重写其中用于认证的`doGetAuthenticationInfo`和授权的`doGetAuthorizationInfo`方法,其中的代码如下: |
public class MyUserRealm extends AuthorizingRealm {
/**
* 模拟数据库用户
* */
Map<String,String> userMap = new HashMap<String, String>();
{
userMap.put("envy","1234");
//设置自定义Realm的名称
super.setName("MyUserRealm");
}
/**
* 模拟数据库通过用户名来查询用户密码
* */
public String getPasswordByUserName(String userName){
return userMap.get(userName);
}
/**
* 模拟数据库通过用户名来查询用户角色名称
* */
public Set<String> getRolesByUserName(String userName){
Set<String> roleSet = new HashSet<String>();
roleSet.add("admin");
roleSet.add("user");
return roleSet;
}
/**
* 模拟数据库通过角色名来查询角色权限
* */
public Set<String> getPermissionsByUserName(String roleName){
Set<String> permissionSet = new HashSet<String>();
permissionSet.add("user:add");
permissionSet.add("user:update");
permissionSet.add("user:delete");
return permissionSet;
}
/**
* 认证
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1、从主体传过来的认证信息中获取用户名
String userName = (String) authenticationToken.getPrincipal();
//2、通过用户名去“数据库”中获取用户密码
String password = getPasswordByUserName(userName);
if(password==null){
return null;
}
//3、新建一个认证对象
//验证通过,认证信息中存放账号密码, getName()用于获取当前Realm的继承方法,通常返回当前类名MyDatabaseReal
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName,password,getName());
return simpleAuthenticationInfo;
}
/**
*授权
* */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//认证通过后,接下来开始授权操作
//1、获取用户名
String userName = (String) principalCollection.getPrimaryPrincipal();
//2、从数据库中查询当前username对象所具有的角色和权限
Set<String> roleSet = getRolesByUserName(userName);
Set<String> permissionSet = getPermissionsByUserName(userName);
//3、新建一个授权对象
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roleSet);
simpleAuthorizationInfo.setStringPermissions(permissionSet);
return simpleAuthorizationInfo;
}
}
1 | 第四步,在`src/main/java/shirojdbc`目录下新建一个EnvyShiroJDBCTest类,其中的代码如下: |
public class EnvyShiroJDBCTest {
@Test
public void test(){
//1、实例化自定义Realm对象
MyUserRealm myUserRealm = new MyUserRealm();
//2、创建一个SecurityManager对象
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//3、设置Realm信息
defaultSecurityManager.setRealm(myUserRealm);
//4、将安全管理者放入全局对象
SecurityUtils.setSecurityManager(defaultSecurityManager);
//5、全局对象通过安全管理者生成Subject对象
Subject subject = SecurityUtils.getSubject();
//6、实例化一个对象,并进行登录验证
UsernamePasswordToken token = new UsernamePasswordToken("envy","1234");
subject.login(token);
//判断用户是否认证成功,注意返回值是boolean类型
System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出true
// 判断subject即当前登录用户是否具有admin和user两个角色权限,如果没有则会报错
subject.checkRoles("admin", "user");
//subject.checkRole("xxx"); // 报错
// 判断subject即当前登录用户是否具有user:add权限
subject.checkPermission("user:add");
}
}
1 | 第五步,运行EnvyShiroJDBCTest测试类,可以发现运行结果如下所示,则表明测试通过: |
isAuthenticated:true
```
ok,那么本篇关于自定义Realm的学习就到此为止,后续开始学习其他知识。