005-shiro的Realm源码解析与自定义Realm

shrio自带的Realm

  • IniRealm:我们前面的示例中使用的,从ini文件中提供用户信息和认证信息
  • JdbcRealm:shrio提供的使用Jdbc访问数据库的方式,提供用户信息和认证信息
  • 其他PropertiesRealm等
    shiro自带的Realm都有其相应的局限性,在大多数情况下我们还是需要根据业务要求自定义Realm

继承AuthorizingRealm来实现自定义Realm

为什么自定义Realm要继承AuthorizingRealm来实现?Realm接口的定义如下:

public interface Realm {
    String getName(); 

    boolean supports(AuthenticationToken var1); 
    // shiro认证一章我们已经提到,每个Realm认证前都会调用该方法
    // 以查看是否supports(支持)已提交AuthenticationToken
    // 如果由于某种原因,您不希望Realm对数据源执行身份验证(也许是因为您只希望Realm执行授权),使该方法返回false即可

    AuthenticationInfo getAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
    // 如果supports方法返回true,则会调用该方法进行用户认证并返回认证信息AuthenticationInfo
}

来看看Realm的继承关系图

在这里插入图片描述

  • 图中我们可以看到shiro自定义的几个Realm都直接或间接继承自AuthorizingRealm
  • CachingRealm提供了可缓存的Realm(最终是通过CacheManager接口来实现的缓存,Realm通过setCacheManager方法来设置)(感兴趣可自己去查看CachingRealm的代码)
  • AuthenticatingRealm实现了Realm的全部3个接口方法,其中getAuthenticationInfo方法的实现如下:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
	// 先获取缓存信息,通过CachingRealm中设置的CacheManager来实现的
   AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
   if (info == null) {
   		// 如果缓存信息为空,则执行本类中的doGetAuthenticationInfo方法,该方法是抽象方法,由实现类实现
       info = this.doGetAuthenticationInfo(token);
       log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
       if (token != null && info != null) {
           this.cacheAuthenticationInfoIfPossible(token, info);
       }
   } else {
       log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
   }

   if (info != null) {
       this.assertCredentialsMatch(token, info);
   } else {
       log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
   }

   return info;
}

通过该源码可知,AuthenticatingRealm实现了shiro的认证流程,也就是调用login方法后如果有缓存则返回缓存认证信息,如果没有缓存则调用doGetAuthenticationInfo方法,我们自定义Realm就需要实现该方法。

  • AuthorizingRealm(注意单词,这几个单词真的太相近了)其实现了Authorizer接口,Authorizer接口中定义了has*、check*等权限认证的方法,AuthorizingRealm中实现了这些权限认证的方法,每个has*,check*方法执行时都会执行如下getAuthorizationInfo方法。
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
   if (principals == null) {
        return null;
    } else {
        AuthorizationInfo info = null;
        if (log.isTraceEnabled()) {
            log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
        }

        Cache<Object, AuthorizationInfo> cache = this.getAvailableAuthorizationCache();
        Object key;
        if (cache != null) {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
            }

            key = this.getAuthorizationCacheKey(principals);
            info = (AuthorizationInfo)cache.get(key);
            if (log.isTraceEnabled()) {
                if (info == null) {
                    log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
                } else {
                    log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
                }
            }
        }

        if (info == null) {
            info = this.doGetAuthorizationInfo(principals);
            if (info != null && cache != null) {
                if (log.isTraceEnabled()) {
                    log.trace("Caching authorization info for principals: [" + principals + "].");
                }

                key = this.getAuthorizationCacheKey(principals);
                cache.put(key, info);
            }
        }

        return info;
    }
}

该方法中doGetAuthorizationInfo方法是抽象方法,需要其实现类实现。且在执行的时候,如果有缓存则直接返回缓存AuthorizationInfo 对象,如果缓存为空,才执行doGetAuthorizationInfo返回AuthorizationInfo 对象

自定义Realm

注意:该示例我们需要创建如下3个类
在这里插入图片描述

MyUser类

package com.yyoo.mytest.shiro1.bean;

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

/**
 * 自己的user对象,一般和你的数据库设计一致
 * 当然你还可以添加部门什么的其他信息
 */
public class MyUser {

    private String userName;

    private String password;

    /**
     * 用户角色列表(我们将角色和用户绑定)
     */
    private List<String> roles = new ArrayList<String>();

    /**
     * 用户权限列表
     */
    private List<String> permissions = new ArrayList<String>();

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

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

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("MyUser{");
        sb.append("userName='").append(userName).append('\'');
        sb.append(", password='").append(password).append('\'');
        sb.append(", roles=").append(roles);
        sb.append(", permissions=").append(permissions);
        sb.append('}');
        return sb.toString();
    }
}

MyRealm1

由于AuthorizingRealm继承自AuthenticatingRealm,其具备了基本的认证逻辑,而AuthorizingRealm本身又实现了基本的授权逻辑,这意味着我们只要继承AuthorizingRealm然后实现doGetAuthenticationInfo、doGetAuthorizationInfo两个方法即可。所以我们自定义的Realm如下:

package com.yyoo.mytest.shiro1.realm;

import com.yyoo.mytest.shiro1.bean.MyUser;
import org.apache.shiro.authc.*;
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 MyRealm1 extends AuthorizingRealm {

    /**
     * 用户认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        // 我们login的时候使用的UsernamePasswordToken,所以此处我直接强转了,大家可以根据自己的需要来做
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;

        // 实际开发中,请使用log打印日志
        System.out.println("realm获取的username:"+token.getUsername());// 需要认证的用户名和密码
        System.out.println("realm获取的password:"+new String(token.getPassword()));

        // 此处可自定义一个Service来实现数据库的查询以验证用户名、密码
        // 当然密码可能需要进行加密比对等,这里我们先写死,一步步来(这里我们再定义一个我们自己的User类MyUser)
        MyUser user = new MyUser(); // 此处应该通过Service获取
        user.setUserName(token.getUsername());
        user.setPassword(new String(token.getPassword()));
        // 建设实际使用中不用返回密码(只需通过用户名、密码查询数据库即可,有值说明认证通过,没有表示用户名、密码错误)
        user.getRoles().add("role1");
        user.getPermissions().add("p1:*");

        // 这里存入user,通过 subject.getPrincipal(); 返回就是对应的MyUser类型
        SimpleAuthenticationInfo simpleAuthenticationInfo =
                new SimpleAuthenticationInfo(user,
                        user.getPassword(), getName());
        return simpleAuthenticationInfo;
    }

    /**
     * 用户授权(角色权限和对应权限添加到shiro)
     * @param principalCollection
     * @return
     * 注:只有在调用如下几种情况的链接时才会执行该方法
     *
     * 直接使用checkPermissions或checkoutRoles方法鉴权
     * 或者是添加了权限注解的url
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取登录用户Bean(因为下面方法中设置的就是MyUser对象)
        // principalCollection就是在上面认证成功后会存在shiro的session的用户信息
        // 调用shiro鉴权方法后,用户信息会通过参数传递到此,所以此处直接可以获取当前登录人的信息
        MyUser user = (MyUser) principalCollection.getPrimaryPrincipal();
        // 这里需要使用获取自定义的用户信息(角色和权限)
        // 添加当前用户所拥有的角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // 添加角色
        simpleAuthorizationInfo.addRoles(user.getRoles());
        // 添加权限
        simpleAuthorizationInfo.addStringPermissions(user.getPermissions());

        return simpleAuthorizationInfo;
    }
}

Demo5

package com.yyoo.mytest.shiro1.demo;

import com.yyoo.mytest.shiro1.realm.MyRealm1;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.Subject;

public class Demo5 {

    public static void main(String[] args) {

        // 定义多个领域
        Realm realm1 = new MyRealm1();

        DefaultSecurityManager securityManager = new DefaultSecurityManager(realm1);
        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("user2","password");
        subject.login(token);

        System.out.println(subject.isAuthenticated());
        System.out.println(subject.getPrincipal());// 打印出来就是我们自定义的MyUser


        System.out.println(subject.isPermitted("p1"));// 会执行我们自定义的doGetAuthorizationInfo方法
        System.out.println(subject.isPermitted("p2"));

        System.out.println(subject.getSession());
        subject.getSession();
        System.out.println(subject.getSession());
        subject.logout();
        System.out.println(subject.getSession());

    }

}

缓存

上面我们提到doGetAuthenticationInfo、doGetAuthorizationInfo这两个方法的缓存都是通过CacheManager来管理,关于缓存的设置与使用我们将在后续章节讨论。

上一篇:004-shiro授权
下一篇:006-springboot整合shrio

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值