文档章节

Shrio源码分析(4) - 数据域(Realm)

xiaoqiyiye
 xiaoqiyiye
发布于 2018/02/05 00:45
字数 1705
阅读 190
收藏 1
本文在于分析Shiro源码,对于新学习的朋友可以参考
[开涛博客](http://jinnianshilongnian.iteye.com/blog/2018398)进行学习。

本篇主要分析Shiro中的Realm接口。Shiro使用Realm接口作为外部数据源,主要处理认证和授权工作。Realm接口如下。

public interface Realm {

    /**
     * Realm必须要有一个唯一的名称
     */
    String getName();

    /**
     * 判断该Realm是否支持处理给定的token认证
     */
    boolean supports(AuthenticationToken token);

    /**
     * 认证token,并返回已认证的AuthenticationInfo
     * 如果没有账户可以认证,返回null,如果认证失败抛出异常
     */
    AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}

CachingRealm抽象类

CachingRealm是带有缓存功能的Realm抽象实现。在CachingRealm中提供了对Realm进行缓存功能的缓存管理器CacheManager,但并没有实现具体缓存什么。在CachingRealm中提供了对onLogout的处理,该方法从LogoutAware实现来,用来处理用户登出后清理缓存数据。Shiro默认对Realm开启缓存功能。

// Realm名称
private String name;
// 是否开启缓存,默认构造方法开启缓存
private boolean cachingEnabled;
// 缓存管理器
private CacheManager cacheManager;

public CachingRealm() {
    this.cachingEnabled = true;
    this.name = getClass().getName() + "_" + INSTANCE_COUNT.getAndIncrement();
}

值得一提的是afterCacheManagerSet()这个钩子方法,在设置缓存处理器后会调用这个方法,在后面的分析中会由子类重写。

public void setCacheManager(CacheManager cacheManager) {
	this.cacheManager = cacheManager;
	afterCacheManagerSet();
}

protected void afterCacheManagerSet() {
}

AuthenticatingRealm抽象类

AuthenticatingRealm是一个可认证的Realm抽象实现类。 AuthenticatingRealm继承了CachingRealm,并实现了Initializable。Initializable提供的init()方法在初始化时会调用。下面是AuthenticatingRealm的属性和构造方法。

// 凭证匹配器,用来匹配凭证是否正确
private CredentialsMatcher credentialsMatcher;

// 缓存通过认证的认证数据
private Cache<Object, AuthenticationInfo> authenticationCache;

// 是否认证缓存
private boolean authenticationCachingEnabled;

// 认证缓存的名称
private String authenticationCacheName;

/**
 * 定义Realm支持的AuthenticationToken类型
 */
private Class<? extends AuthenticationToken> authenticationTokenClass;

public AuthenticatingRealm() {
    this(null, new SimpleCredentialsMatcher());
}

public AuthenticatingRealm(CacheManager cacheManager) {
    this(cacheManager, new SimpleCredentialsMatcher());
}

public AuthenticatingRealm(CredentialsMatcher matcher) {
    this(null, matcher);
}

public AuthenticatingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
    // 默认支持UsernamePasswordToken类型
    authenticationTokenClass = UsernamePasswordToken.class;

    // 认证不缓存
    this.authenticationCachingEnabled = false;

    // 设置认证缓存的名称
    int instanceNumber = INSTANCE_COUNT.getAndIncrement();
    this.authenticationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
    if (instanceNumber > 0) {
        this.authenticationCacheName = this.authenticationCacheName + "." + instanceNumber;
    }

    // 设置缓存管理器
    if (cacheManager != null) {
        setCacheManager(cacheManager);
    }
    
    // 设置凭证匹配器
    if (matcher != null) {
        setCredentialsMatcher(matcher);
    }
}

从属性和构造方法我们可以看出,AuthenticatingRealm会进行认证,对认证的结果AuthenticationInfo进行缓存,认证时需要使用凭证匹配器来匹配凭证是否正确。下面,我们根据这个思路可以去看看进行认证的方法getAuthenticationInfo(AuthenticationToken token)。

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

    // 从认证缓存中获取认证结果
    AuthenticationInfo info = getCachedAuthenticationInfo(token);
    if (info == null) {
        // 这是一个抽象方法,子类去完成认证过程
        info = doGetAuthenticationInfo(token);
        if (token != null && info != null) {
            // 如果认证通过,则将认证结果缓存起来
            cacheAuthenticationInfoIfPossible(token, info);
        }
    }

    // 匹配凭证是否正确,如果不正确将会抛出异常
    if (info != null) {
        assertCredentialsMatch(token, info);
    }

    return info;
}

关于AuthenticationInfo缓存过程中的一些细节。在缓存的过程中是以AuthenticationToken中的身份进行缓存的,所有身份肯定要是唯一的。属性authenticationCache可以由外部提供,也可以通过缓存管理器生成,一般情况下authenticationCache不需要外部设置。

AuthorizingRealm抽象类

AuthorizingRealm继承了AuthenticatingRealm,负责处理角色和权限。AuthorizingRealm的实现方式和AuthenticatingRealm一样,提供了一个抽象的doGetAuthorizationInfo(PrincipalCollection principals)方法。这里不做详细介绍,我们会在后面分析角色权限时介绍。

基于Jdbc的Realm(JdbcRealm类)

JdbcRealm类可以直接和数据库连接,从数据中获取用户名、密码、角色、权限等数据信息。通过和数据库的直接连接来判断认证是否正确,是否有角色权限功能。

在JdbcRealm中提供了一些Sql语句常量,通过这些sql来做数据库操作。当然,操作数据肯定需要数据库数据源。

// 通过用户名查找密码的Sql语句
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";

/**
 * 通过用户名称查找密码和加密盐的Sql语句
 */
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";

/**
 * 通过用户名查找用户所有角色
 */
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";

/**
 * 通过角色名称查找角色拥有的权限
 */
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";

/**
 * 定义了几种加盐模式:
 *   NO_SALT - 密码没有加密盐
 *   CRYPT - unix加密(这种模式目前还支持)
 *   COLUMN - 加密盐存储在数据库表字段中 
 *   EXTERNAL - 加密盐没有存储在数据库
 */
public enum SaltStyle {NO_SALT, CRYPT, COLUMN, EXTERNAL};

// 数据库数据源
protected DataSource dataSource;

// 查询密码和加密盐的SQL
protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;

// 查询用户角色的SQL
protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;

// 查询角色拥有权限的SQL
protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY;

protected boolean permissionsLookupEnabled = false;

// 密码没有加密盐模式
protected SaltStyle saltStyle = SaltStyle.NO_SALT;

对于不同的数据库,这些默认的Sql是可以更改的,JdbcRealm都提供了相应的setter方法。那么,Jdbc是如何认证和获取角色权限的呢?下面继续分析doGetAuthenticationInfo和doGetAuthorizationInfo这两个方法。

  1. 认证过程
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

    // 只支持UsernamePasswordToken类型
    UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    
    // 用户名
    String username = upToken.getUsername();

    // 用户名空判断
    if (username == null) {
        throw new AccountException("Null usernames are not allowed by this realm.");
    }

    Connection conn = null;
    SimpleAuthenticationInfo info = null;
    try {
    
        // 获取数据库连接
        conn = dataSource.getConnection();

        String password = null;
        String salt = null;
        switch (saltStyle) {
            case NO_SALT:
                password = getPasswordForUser(conn, username)[0];
                break;
            case CRYPT:
                // TODO: separate password and hash from getPasswordForUser[0]
                throw new ConfigurationException("Not implemented yet");
                //break;
            case COLUMN:
                String[] queryResults = getPasswordForUser(conn, username);
                password = queryResults[0];
                salt = queryResults[1];
                break;
            case EXTERNAL:
                password = getPasswordForUser(conn, username)[0];
                // 以用户名作为加密盐
                salt = getSaltForUser(username);
        }

        if (password == null) {
            throw new UnknownAccountException("No account found for user [" + username + "]");
        }

        // 创建一个认证信息
        info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
        
        if (salt != null) {
            info.setCredentialsSalt(ByteSource.Util.bytes(salt));
        }

    } catch (SQLException e) {
        throw new AuthenticationException(message, e);
    } finally {
        // 关闭数据连接
        JdbcUtils.closeConnection(conn);
    }

    return info;
}

2 授权过程

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

    // 身份不能为空
    if (principals == null) {
        throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
    }

    // 从身份中获取用户名
    String username = (String) getAvailablePrincipal(principals);

    Connection conn = null;
    Set<String> roleNames = null;
    Set<String> permissions = null;
    try {
        // 获取数据库连接
        conn = dataSource.getConnection();

        // 获取角色集合
        roleNames = getRoleNamesForUser(conn, username);
        if (permissionsLookupEnabled) {
            // 获取权限集合
            permissions = getPermissions(conn, username, roleNames);
        }
    } catch (SQLException e) {
        throw new AuthorizationException(message, e);
    } finally {
        JdbcUtils.closeConnection(conn);
    }

    // 返回带有角色权限的认证信息
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
    info.setStringPermissions(permissions);
    return info;

}

在Shiro中还提供了一些其他的Realm。SimpleAccountRealm、TextConfigurationRealm、IniRealm、PropertiesRealm。这里就不一一介绍了,有兴趣可以自己去看。

总结

在Shiro中Realm接口作为一个与应用程序外接的接口,可以通过Realm提供认证和授权的数据信息。在开发使用中最常用的就是从AuthenticatingRealm或AuthorizingRealm抽象类来实现业务中具体的Realm实例。doGetAuthenticationInfo(AuthenticationToken token)处理认证过程,doGetAuthorizationInfo(PrincipalCollection principals)处理授权过程。

© 著作权归作者所有

xiaoqiyiye
粉丝 17
博文 44
码字总数 80350
作品 0
杭州
私信 提问
shrio教程初级(五)shiro基础(授权)

一、前言 回忆过去,shiro的认证忘不了: 0:通过securityFactory建立securityManager,加入securityUtil加入环境 1、subject(主体)请求认证,调用login(token) 2、securityManager开始认...

ycy蓝码
2015/10/15
0
0
【Shiro】Shiro从小白到大神(二)-Subject认证结合MySQL

上一节博客讲的文本数据验证,基本不会在项目中用到,只是方便用来学习和测试 在本节,进行简单的数据库安全验证实例 Subject认证主体 Subject认证主体包含两个信息: Principals: 身份,可以...

qq_26525215
2017/09/22
0
0
Spring Boot -Shiro配置多Realm

核心类简介 xxxToken:用户凭证 xxxFilter:生产token,设置登录成功,登录失败处理方法,判断是否登录连接等 xxxRealm:依据配置的支持Token来认证用户信息,授权用户权限 核心配置 Shrio整...

MeiJM
03/04
86
0
SpringBoot集成Shiro三个渐进式项目以及Shiro功能介绍

版权声明:本文为谙忆原创文章,转载请附上本文链接,谢谢。 https://blog.csdn.net/qq_26525215/article/details/82499114 首先,本篇博客的目的的重点是这里介绍的三个SpringBoot集成Shiro...

谙忆
2018/09/07
0
0
Shiro学习笔记入门--Hello Shiro

Apache Shiro是Apache的一个安全框架.对比Spring Security,可能没有Spring Security功能多,但是在实际并不需要那么重的东西.shiro简小精悍.大多项目绰绰有余.(JBOSS好像也有个什么安全框架....

浮躁的码农
2015/12/01
754
0

没有更多内容

加载失败,请刷新页面

加载更多

CC攻击带来的危害我们该如何防御?

随着网络的发展带给我们很多的便利,但是同时也带给我们一些网站安全问题,网络攻击就是常见的网站安全问题。其中作为站长最常见的就是CC攻击,CC攻击是网络攻击方式的一种,是一种比较常见的...

云漫网络Ruan
今天
8
0
实验分析性专业硕士提纲撰写要点

为什么您需要研究论文的提纲? 首先当您进行研究时,您需要聚集许多信息和想法,研究论文提纲可以较好地组织你的想法, 了解您研究资料的流畅度和程度。确保你写作时不会错过任何重要资料以此...

论文辅导员
今天
7
0
作为一个(IT)程序员!聊天没有话题?试试这十二种技巧

首先呢?我是一名程序员,经常性和同事没话题。 因为每天都会有自己的任务要做,程序员对于其他行业来说;是相对来说比较忙的。你会经常看到程序员在发呆、调试密密麻麻代码、红色报错发呆;...

小英子wep
今天
30
0
【SpringBoot】产生背景及简介

一、SpringBoot介绍 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程,该框架使用了特定的方式来进行配置,从而使开发人员不再需要...

zw965
今天
14
0
简述并发编程分为三个核心问题:分工、同步、互斥。

总的来说,并发编程可以总结为三个核心问题:分工、同步、互斥。 所谓分工指的是如何高效地拆解任务并分配给线程,而同步指的是线程之间如何协作,互斥则是保证同一时刻只允许一个线程访问共...

dust8080
今天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部