初识Shiro安全权限框架有哪些需要注意的地方?

欢迎光顾我的博客

开端

好的,起因是我把最近做好的项目给一部分人进行了测试,发现大部分朋友都提出了同一个问题,你的系统权限管理是如何实现的。我只能尴尬的说一句,不好意思这部分还没开发。然而我也知道,其实对于一个项目来说,权限可以说是最主要的一部分。后端除了对权限进行处理,其实也就是提供一些业务逻辑对 CRUD 进行组合拼装。所以我打算接下来学习权限控制方面的知识并整合到我的项目中去,顺便把我的学习笔记分享给大家。

而对于权限控制的框架呢,听的最多的还是 Shiro 还有 Spring Security。Spring 的安全框架单独用在 Spring 项目中是无可挑剔的,不管是功能上还是维护方面。但是考虑到 Shiro 是一个全能性的框架,可以用在各种场合,甚至非 Web 项目中,由于它的会话独立于容器,后期学习分布式和微服务的时候也比较方便使用。~~最重要的是 Spring 官网用的也是 Shiro 的框架。~~所以还是打算学习 Shiro。

由于这部分内容比较多,我也发现了前后端项目部署那篇文章接近 1w 字导致阅读的时候不是很舒服,所以我打算把这个内容分成几个部分:初识 Shiro、配置重点、整合技巧来讲述。这篇文章是关于初识 Shiro 的,主要是 Shiro 的一些基本特性和架构,以及官网上的小例子。

Shiro 简介

  • Apache 软件基金会开发的 Java 安全(权限)框架

  • 适用于 Java SE 和 Java EE 的环境。

  • 主要功能有:认证、授权、加密、会话管理、集成 Web、缓存等。

Shiro 具体功能

功能 翻译 解释
Authentication 身份验证/登录 验证用户登录时的身份
Authorization 权限验证 验证用户拥有哪些权限
Session Manager 会话管理 一次登录即一次会话
Cryptography 加密 保护数据安全,密码加密存储
Web Support Web支持 支持方便的Web集成
Caching 缓存 提高查找效率
Run As 身份替换 假装成其他用户(若被允许)
Remember Me 二次记忆 二次登录无需验证

初次见面看不懂没有关系的,现在心中有个初步的印象,等到全部学习完整合完你就恍然大悟了。我就简单的讲两个:

Authentication

首先前两个功能比较容易混淆,到时候写程序千万看清别把方法名写错。Authentication 是身份的验证,我们对于权限的结构划分是基于 RBAC 模型来的(具体可以见我的另一篇博文),所以会有用户、角色、权限三层数据表。而这个功能就是判断用户的角色,是管理员、运维、开发还是测试等等。我们来看一段 Realm 中的方法 doGetAuthenticationInfo:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String userName = token.getUsername();
        User user = userService.selectByName(userName);
        return new SimpleAuthenticationInfo(
            user.getUsrName(),
            user.getUsrPassword(),
            getName()
        );
}

这段方法中首先获取用户身份 token 参数,然后获取用户名。使用我自定义的 Mapper 中的 selectByName( ) 在用户数据表中查询数据,最后返回一个 Simple 身份验证信息,参数为用户名、密码和 CachingRealm 的名字。当然正统的方式是把用户对象直接传给他,但是我测试的时候发现传用户名和密码出错的可能性更低,具体可以去看看 SimpleAuthenticationInfo 类的源码。

Authorization

第二个是用来验证角色所对应的权限。比如说管理员用户拥有增删改查所有权限,游客只拥有查找的权限等等。我们还是通过一段 Java 代码来理解这个功能。

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        String userName = (String)principalCollection.getPrimaryPrincipal();
        if (userName == null) {
            LOGGER.error("授权失败,用户信息为空!");
            return null;
        }
        try {
            Set<String> roles = roleService.findRoleByUserName(userName);
            simpleAuthorizationInfo.addRoles(roles);
            for (String role : roles) {
                Set<String> permissions = permissionService.findPermissionByRole(role);
                simpleAuthorizationInfo.addStringPermissions(permissions);
            }
            return simpleAuthorizationInfo;
        } catch (Exception e) {
            LOGGER.error("授权失败,系统内部错误!");
        }
        return simpleAuthorizationInfo;
    }

首先 new 一个简单权限验证信息对象(SimpleAuthorizationInfo),使用 getPrimaryPrincipal( ) 方法从函数参数中获取用户名。当用户名存在时,调用自定义的 Mapper 中的 findRoleByUserName( ) 在 sys-user-role 表中找到该用户所对应的角色,这个角色可以是一对多的(我的项目里是一对一的)。之后在简单权限验证信息对象中把角色添加进去。对于每一个角色都赋予了多个权限,所以接下来使用 foreach 循环再次调用 findPermissionByRole( ) 在 sys-role-permission 表中找到每一个角色对应的权限,然后同样加入到简单权限验证信息对象中。至此,获取系统内部角色和权限并把这些信息告诉 Shiro 的步骤就完成了。这个功能中的代码是比较重要的,涉及了用户的数据表和 Mybatis 的操作,因人而异。

其它

其他功能的话,Session Manager 和 Remember Me 还是比较常用的,但是配置比较简单,照葫芦画瓢就行,不多赘述。

Shiro 具体架构

架构 翻译 解释
Subject 用户 即与应用交互的用户
Security Manager 安全管理 管理所有用户(Shiro的心脏)
Authenticator 用户验证 自定义验证
Authorizer 授权器 控制用户不同权限
Realm 安全实体数据源 通过JDBC等实现
Cache Manager 缓存控制器 加速访问
Cryptography 密码模块 密码的加密与解密

架构与某些功能相类似,通过这些模块才能实现特定的功能。我也挑两个讲一下:

Subject

这里的 Subject 不是课程,我第一次看到也以为是课程,这个类和我项目中的 Subject 类竟然一样,import 的时候差点搞错。我们看一段登录接口的代码:

@PostMapping(value = "/login")
@ResponseBody
public Response<User> userLogin(@RequestBody User sysUser) {
        User result;
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(
          sysUser.getUsrName(), sysUser.getUsrPassword()
        );
        token.setRememberMe(true);
        try {
            subject.login(token);
            result = userService.userLogin(sysUser.getUsrName(), sysUser.getUsrPassword());
            session.setAttribute("user", subject);
        } catch (UnknownAccountException e) {
            return getFailResult(404, "Message not found");
        } catch (IncorrectCredentialsException e) {
            return getFailResult(412, "Incorrect message");
        } catch (LockedAccountException e) {
            return getFailResult(401, "Account locked");
        } catch (Exception e) {
            e.printStackTrace();
            return getFailResult(408, "Unknown error");
        }
        return getSuccessResult(result);
    }

这里我们定义了一个 Subject 类的对象,然后使用 SecurityUtils.getSubject( ) 获取当前登录用户的信息。可以 conmand + 单击这个类看一下源码,原来 SecurityUtils 中放了一个 SecurityManager 对象,也就是第二部分的架构,这部分下面再讲。然后我们通过前端获取到的用户名和密码 new 一个 UsernamePasswordToken 类的对象,生成一个 token。最后使用这个唯一的 token 进行 login,对于用户不存在、密码错误、账户锁定、未知错误四种异常进行捕获并反馈。

Security Manager

刚刚讲到 SecurityUtils 中放了一个 SecurityManager 对象,而 SecurityManager 对象非常的简单,就只包含登录、退出、创建用户三个方法。

public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
    Subject login(Subject var1, AuthenticationToken var2) throws AuthenticationException;
    void logout(Subject var1);
    Subject createSubject(SubjectContext var1);
}

这个架构是 Shiro 的心脏,可以用来管理所有用户。在第三个方法中提供了用户信息上下文的建立与保存,session 和 principals 的建立等等,都是非常重要的内容。

Authorizer

这个架构其实是先前 Authentication 和 Authorization 的结合,通过用户-角色-权限来管理用户所拥有的不同权限。

Realm

安全实体数据源又是一个非常重要的内容,我们之前的 doGetAuthenticationInfo( ) 和 doGetAuthorizationInfo( ) 两个自定义获取数据库中的角色和权限信息的方法就是写在这里的,主要使用 JDBC 来操作,用户的授权首先都是要经过这一块关卡的。

其它

其它功能的话,也不能说不重要,没什么必要讲。就好比密码模块,我暂时还没有用到,涉及到盐值加密(salt)、MD5 码校验等等,比较复杂。~~其实是我不会所以不讲。~~还有缓存的话也只要模式化的设定一个缓存即可,如果是在公司一般都有企业内部的缓存方式。

官网示例

我们从官网把最新的源代码 down 下来或者 clone,研究一下他最简单的实现原理。我们一共需要四部分的文件才能启动一个简单的 helloword 程序。可以在源代码中的 samples 下的 quickstart 中将这四个文件单独拷贝到你的项目中启动。

jar 包

关于 jar 包如何导入请看 Shiro 整合 SSM 一文。

log4j.properties

这是一个日志该如何生成和怎样生成的配置文件。

# 级别从低到高分别为 DEBUG INFO WARN ERROR FATAL,INFO 代表重要信息
log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries WARN 代表警告信息
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

我们可以看到,对于不同的文件产生的输出。

shiro.ini

这是一个 Shiro 的配置文件,里面设置了用户权限密码等基本信息,只用于测试,一般项目开发不怎么用。

[users]
# 以下示例配置了5位用户,等号后分别是密码和用户对应的角色,比如管理员、访客等等
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

[roles]
# 以下示例配置了3种觉得,等号后是他们所拥有的权限,*代表全部,:代表内含权限,而第三种的三个参数分别代表用户角色、用户权限、允许操作的实例号
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

Quickstart.java

类似于 Spring 的 Application,这是一个启动类。在启动类中我们也可以看到 IDEA 报了个错,说 Shiro 的工厂类已经过时,不推荐使用 ini 的形式配置。看一下主函数中一些重要的代码吧:

// 通过工厂导入ini配置
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
// 创建单例
SecurityUtils.setSecurityManager(securityManager);
// 获取当前的角色
Subject currentUser = SecurityUtils.getSubject();
// 测试session
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
// 验证用户是否登录
if (!currentUser.isAuthenticated()) {
  	// 封装用户名密码
    UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
    token.setRememberMe(true);
    // 登录成功
    try {
        currentUser.login(token);
    } 
    // 用户名不存在异常
    catch (UnknownAccountException uae) {
        log.info("There is no user with username of " + token.getPrincipal());
    } 
    // 用户名存在,密码错误异常
    catch (IncorrectCredentialsException ice) {
        log.info("Password for account " + token.getPrincipal() + " was incorrect!");
    } 
    // 用户锁定异常
    catch (LockedAccountException lae) {
        log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                "Please contact your administrator to unlock it.");
    }
    // 其他异常... catch more exceptions here (maybe custom ones specific to your application?
    catch (AuthenticationException ae) {
        //unexpected condition?  error?
    }
}
// 测试是否有该角色 test a role:
if (currentUser.hasRole("schwartz")) {
    log.info("May the Schwartz be with you!");
} else {
    log.info("Hello, mere mortal.");
}
// 测试是否有该权限 test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
    log.info("You may use a lightsaber ring.  Use it wisely.");
} else {
    log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

好啦,大家可以自行到官网下载实例本地测试,看源码是一个能够最快理解程序的方式。

总结

这篇博文从我的项目需要整合 Shiro 开始讲起,主要是写了我在初学 Shiro 时的所感所悟,以及对一些源码的解析。因为初学,可能有些地方理解没到位或者有偏差,请谅解。欢迎评论指正!求求大家别白嫖了,看过打个卡留个言吧!!!全部原创,真情实感,更新不易,需要动力 :)

你可能感兴趣的:(Shiro,shiro)