认证和鉴权

Shiro可以用于普通Java工程或JavaWeb工程,针对不同的环境,框架内部会进行一些处理,我们不用关心。这篇笔记我们以一个普通Java工程为例,介绍如何用Shiro实现认证和鉴权。

为了熟悉Shiro中的一些组件和API,我们最好先在普通Java工程中搭建一个Shiro工程用来测试学习。

引入Maven依赖

新建一个普通Maven工程,添加依赖引入Shiro框架的核心功能。

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.3.2</version>
</dependency>

警告:Shiro作为一个认证授权框架,一直是信息安全领域漏洞挖掘的关注重点,随着时间的推移上面例子中所使用的Shiro版本可能已经过时,在实际开发中我们务必选择一个没有安全漏洞的较新版本。

此外Shiro还依赖于日志框架,我们引入SLF4J和Logback日志框架。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.26</version>
</dependency>

这里注意Shiro默认依赖于commons-logging这个过时的日志框架(这是Shiro的一个糟糕设计),为了统一使用Logback,我们需要jcl-over-slf4j桥接器来将Shiro的日志用Logback输出。

shiro.ini

我们知道RBAC模型中,说白了是用户<->角色<->权限三者的多对多关系,在工程实践中,为了使授权粒度更细,还可能会有用户直接关联权限的设计,这些和权限相关的内容一般存储在数据库中,工程启动时可能还会放入缓存集群,以提高系统整体性能。

这里我们只是简单体验Shiro的用法因此就不搞那么复杂了,Shiro支持通过一个shiro.ini文件编写用户、角色、权限用于测试,当然,这种情况比较少用于线上环境。

[users]
tom = 123abc, guest, admin
jerry = abc123, guest
lilei = lilei666, guest, doc1_editor
lucy = qqqwwweee, guest, doc1_editor, doc1_manager

[roles]
admin = *
guest = doc:query
doc1_editor = doc:query:doc1, doc:create:doc1, doc:update:doc1
doc1_manager = doc:query:doc1, doc:update:doc1, doc:delete:doc1

我们这里假设有一个大型CMS系统,分为文档doc,视频video等多个大板块,文档板块分为doc1doc2等若干区划,针对每个分区都有对应的工作人员,包括编辑editor,经理manager,除此之外,还有超级管理员admin,以及游客guest

[users]中定义的是该用户的用户名,登录凭证,所属角色。例如,lilei的密码是lilei666,所属角色是guest访客,和doc1_editor文档1编辑。

[roles]中定义的是角色和权限,例如:doc1_manager表示doc1分区的经理,他能对doc板块下,doc1区内容有查询、更新、删除权限,但是不能擅自创建,doc1_editor表示doc1分区的编辑,他能查询、创建、更新内容,但不能擅自删除。

注意权限的写法,例如doc:query:doc1,表示针对doc下的doc1,有查询权限,即:<访问主体>:<访问动作>:<访问主体的实例>*代表对该字段的所有取值都有权限,位于授权字符串后面的*可以省略。

认证鉴权例子

package com.gacfox.demo.demoshiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;

/**
 * @author gacfox
 */
@Slf4j
public class Main {
    public static void main(String[] args) {
        // 初始化SecurityUtils
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        // 获取当前访问客体对象
        Subject subject = SecurityUtils.getSubject();

        // 登录实现
        UsernamePasswordToken token = new UsernamePasswordToken("lucy", "qqqwwweee");
        token.setRememberMe(true);
        try {
            subject.login(token);
        } catch (UnknownAccountException e) {
            log.debug("没有该账号");
        } catch (IncorrectCredentialsException e) {
            log.debug("错误的登录凭证");
        } catch (LockedAccountException e) {
            log.debug("账号已被锁");
        } catch (AuthenticationException e) {
            log.debug("具体原因不明,反正就是登录出错了");
        }

        // 登录成功,鉴权实现
        boolean[] permsCheck = subject.isPermitted(
                "doc:query:doc1",
                "doc:create:doc1",
                "doc:update:doc1",
                "doc:delete:doc1",
                "doc:query:doc2",
                "doc:create:doc2",
                "doc:update:doc2",
                "doc:delete:doc2");
        log.debug("能否查询doc1 " + permsCheck[0]);
        log.debug("能否创建doc1 " + permsCheck[1]);
        log.debug("能否更新doc1 " + permsCheck[2]);
        log.debug("能否删除doc1 " + permsCheck[3]);
        log.debug("能否查询doc2 " + permsCheck[4]);
        log.debug("能否创建doc2 " + permsCheck[5]);
        log.debug("能否更新doc2 " + permsCheck[6]);
        log.debug("能否删除doc2 " + permsCheck[7]);
    }
}

可以看到,上面代码其实就是实现了两个功能:登录和鉴权。

登录过程中,我们调用subject.login(),它会抛出各种运行时异常(这又是Shiro的糟糕设计,Java开发中另一个拒绝用受检查异常的极端,我认为AuthenticationException至少应该是受检查异常),我们可以捕获这些运行时异常,进行各种错误处理。

代码中编写的就是最常用的几种异常:

  • UnknownAccountException:无账号
  • IncorrectCredentialsException:密码错误
  • LockedAccountException:账号已锁定
  • AuthenticationException:Shiro中各种异常的基类

鉴权就比较简单了,调用subject.isPermitted(),参数是我们要检查的权限,返回值表示是否有这些权限。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap