Shiro权限管理框架(一)---常用API与JavaSE环境下简单实现

一、权限管理概述

权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。 很多人常将“用户身份认证”、“密码加密”、“系统管理”等概念与权限管理概念混淆。

在权限管理中使用最多的还是功能权限管理中的基于角色访问控制(RBAC,Role Based Access Control)。

image.png

在项目中需要使用权限管理的时候,我们可以选择自己去实现,也可以选择使用第三方实现好框架,

权限管理系统必备的功能

1.权限管理(自定义权限注解/加载权限)

2.角色管理(新增/编辑/删除/关联权限)

3.用户管理(新增/编辑/删除/关联用户)

4.登录功能(定义登录拦截器/登录逻辑实现/登出功能)

5.权限拦截(定义权限拦截器/拦截逻辑实现)

使用权限框架能帮我们做的事情

功能 权限框架能否完成 原因
权限管理 用户根据需求,自定义有哪些权限,如何管理
角色管理 用户根据需求,自定义有哪些角色,如何管理,不同角色有那些角色
用户管理 用户根据需求,自定义有哪些用户,如何管理,不同用户有那些角色
登录功能 (密码加密、验证码、记住我)
权限拦截 (内置很多的拦截器、提供标签/注解/编程方式进行权限认证)
其他功能 (缓存、会话管理等)

以上是对权限管理的简单说明

二、Apache Shiro概述

Apache Shiro 是一个强大且易用的 Java 安全框架,使用 Apache Shiro 的人越来越多,它可实现身份验证、授权、密码和会话管理等功能。

Shiro可以帮助我们完成,认证,授权,加密,会话管理,与WEB继承**(证明在JavaSE环境下也可以使用)**,缓存等;

1、Apache Shiro Features 特性

Apache Shiro是一个全面的、蕴含丰富功能的安全框架。下图为描述Shiro功能的框架图:

v2-e72930a8351ccf1590779ea87ac5cb65_r.jpg

Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。那么就让我们来看看它们吧:

  • **Authentication(认证):**用户身份识别,通常被称为用户“登录”
  • **Authorization(授权):**访问控制。比如某个用户是否具有某个操作的使用权限。
  • **Session Management(会话管理):**特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
  • **Cryptography(加密):**在对数据源使用加密算法加密的同时,保证易于使用。

还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。特别是对以下的功能支持:

  • **Web支持:**Shiro的Web支持API有助于保护Web应用程序。
  • **缓存:**缓存是Apache Shiro API中的第一级,以确保安全操作保持快速和高效。
  • **并发性:**Apache Shiro支持具有并发功能的多线程应用程序。
  • **测试:**存在测试支持,可帮助您编写单元测试和集成测试,并确保代码按预期得到保障。
  • **“运行方式”:**允许用户承担另一个用户的身份(如果允许)的功能,有时在管理方案中很有用。
  • **“记住我”:**记住用户在会话中的身份,所以用户只需要强制登录即可。

注意: Shiro不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给Shiro

2、High-Level Overview 高级概述

在概念层,Shiro 架构包含三个主要的理念:Subject,SecurityManager和 Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。

v2-c0841dfc8cb19a94c322eef635371cf6_1440w.jpg

  • **Subject:**当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
  • **SecurityManager:**管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
  • **Realms:**用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。

我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等.

3、Shiro 认证过程

v2-2531156d2e6fb3ec0702f1d1ed795f43_1440w.jpg

4、Shiro常用方法

4.1 Role-Based Authorization(角色验证)

注解 描述
hasRole(String roleName) 返回true,当前Subject(登陆工号)有该角色权限,false,没有
hasRoles(List roleNames) 返回true,至少在集合中存在一个角色权限,false一个都没有
hasAllRoles(Collection roleNames) 返回true,当前工号拥有列表所有角色,否则返回false

4.2 Role Assertions(角色检查)

求区别在于 check开头的是没有返回值的,当没有权限时就会抛出异常

方法 描述
checkRole(String roleName) 判断当前Subject(工号)有没有该角色, 有则不抛出异常,若没有抛出AuthorizationException
checkRoles(Collection roleNames) 若当前Subject(工号)拥有所有该集合角色不抛出异常,若没有抛出AuthorizationException
checkRoles(String... roleNames) 同上,只不过采用java5的新特性

4.3 Permission-Based Authorization(权限校验)

Permission在某种程度上可以理解为字符串,为一个权限编号即可,也提供了字符串的权限校验

方法 描述
isPermitted(Permission p)/isPermitted(String perm) 判断当前Subject(工号)是否拥有该权限,是返回true,否则false
isPermitted(List perms)或isPermitted(String perms, ..., String perms) 判断当前Subject(工号)是否拥有集合中的权限,即返回true,否则false
isPermittedAll(Collection perms)/isPermittedAll(String... perms) 判断当前Subject(工号)是否有集合中的所有权限,是返回true,否则false

4.4 Permission Assertions(权限检查)

这个跟角色的是一样的意思,就不解释了,求区别在于 check开头的是没有返回值的,当没有权限时就会抛出异常

方法 描述
checkPermission(Permission p)
checkPermission(String perm)
checkPermissions(Collection perms)
checkPermissions(String... perms)

5、Shiro也可以通过注解来处理,常用的注解有下面这些:

注解都会抛出异常,但这个异常不需要我们来刻意处理,shiro会来处理,跳转到登陆界面或者其他

注解 描述
@RequiresAuthentication 是否经过认证或者登陆,若没有的话会抛出异常UnauthenticatedException
@RequiresGuest 未认证或者叫未登陆,可能在remember me状态下,否则抛出异常UnauthenticatedException
@RequiresPermissions 检查是否有该权限,没有抛出异常AuthorizationException
@RequiresRoles 检查是否有该角色,没有抛出异常AuthorizationException
@RequiresUser 这个刚好跟@RequiresGuest相反,这个必须经过认证,或者从rememberme进行登陆,这个没有RequiresAuthentication严格但类似,否则抛出异常AuthorizationException

6、案例演示:

因为Shiro是支持在JavaSE环境下使用的,我们先在SE环境下,模拟一下Shiro的操作流程, 熟悉一下API

环境准备

  • 创建普通的Maven项目
  • 添加必要的依赖
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.3</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.5.2</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.22</version>
    <scope>provided</scope>
</dependency>
复制代码

方式一: 基于ini的鉴权

在resource文件夹下新建一个.ini文件

shiro默认支持的是ini配置的方式

shiro-authc.ini

#用户的身份、凭据、角色
[users]
yl=888,hr,seller
liuxw=666,seller

#角色与权限信息
[roles]
hr=user:list,user:delete
seller=customer:list,customer:save6
复制代码

新建一个测试类

ShiroDemoText.java

public class ShiroDemoText {

    @Test
    public void testAuthorByIni(){
        /*************************登录逻辑开始****************************/
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        IniRealm iniRealm = new IniRealm("classpath:shiro-authc.ini");

        securityManager.setRealm(iniRealm);

        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("liuxw", "666");

        subject.login(token);

        /*******************************登录逻辑结束,鉴权开始***********************************/

        //判断用户是否登录
        System.out.println("是否登录---->"+subject.isAuthenticated());

        //判断用户是否有某个角色
        System.out.println("是否有hr这个角色---->"+subject.hasRole("hr"));
        System.out.println("是否有seller这个角色---->"+subject.hasRole("seller"));

        //check开头的是没有返回值的,当没有权限时就会抛出异常
        subject.checkRole("seller");

        //是否同时拥有多个角色
        System.out.println("是否同时拥有hr和seller----->"+Arrays.toString(subject.hasRoles(Arrays.asList("hr","seller"))));
        System.out.println("是否同时拥有hr和seller----->"+subject.hasAllRoles(Arrays.asList("hr","seller")));

        //判断用户是否有某个权限
        System.out.println("是否有用户删除权限------>"+subject.isPermitted("user:delete"));

        subject.checkPermission("customer:list");
        
    }
}
复制代码

运行结果: image.png

方式二: 基于自定义Realm的鉴权

新建User.java

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;//用户名
    private String password;//密码
}
复制代码

新建DataMapper.java 模拟从数据库查询数据

public class DataMapper {
    //用户集合
    private static Map<String, User> userData = new HashMap<String, User>();
    //角色集合
    private static Map<String, List<String>> roleData = new HashMap<String, List<String>>();
    //权限集合
    private static Map<String, List<String>> permissionData = new HashMap<String, List<String>>();
    static{
        //初始化用户数据
        User u1 = new User("lanxw","666");
        userData.put(u1.getUsername(),u1);
        roleData.put(u1.getUsername(), Arrays.asList("seller"));
        permissionData.put(u1.getUsername(),
                Arrays.asList("customer:list","customer:save"));

        User u2 = new User("yl","888");
        userData.put(u2.getUsername(),u2);
        roleData.put(u2.getUsername(), Arrays.asList("seller","hr"));
        permissionData.put(u1.getUsername(),
                Arrays.asList("customer:list","customer:save","user:list","user:delete"));
    }
    //提供静态方法,模拟数据库返回数据
    public static User getUserByName(String username){
        return userData.get(username);
    }
    public static List<String> getRoleByName(String username){
        return roleData.get(username);
    }
    public static List<String> getPermissionByName(String username){
        return permissionData.get(username);
    }
}

复制代码

新建UserRealm.java 继承 AuthorizingRealm类

AuthorizingRealm类是Reaml类的子类,有缓冲,认证,授权的功能,我们继承它使用就好了

public class UserRealm extends AuthorizingRealm {



    //授权
    /**提供授权信息*/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //principalCollection.getPrimaryPrincipal()
        //其实就是在认证时放入SimpleAuthenticationInfo的第一个参数
        User user = (User) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //根据登录用的名称查询到其拥有的所有角色的编码
        List<String> roleByName = DataMapper.getRoleByName(user.getUsername());
        //将用户拥有的角色添加到授权信息对象中,供Shiro权限校验时使用
        info.addRoles(roleByName);
        //根据登录用户的名称查询到其拥有的所有权限表达式
        List<String> permissionByName = DataMapper.getPermissionByName(user.getUsername());
        //将用户拥有的权限添加到授权信息对象中,供Shiro权限校验时使用
        info.addStringPermissions(permissionByName);

        return info;
    }

    //认证
    /**提供认证信息*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //从页面传入的账户
        String username = (String) token.getPrincipal();
        //模拟数据库中查询数据
        User user = DataMapper.getUserByName(username);
        if (user == null) {
            //如果没有查到就返回一个空
            return null;
        } else {
            //如果存在需要封装成AuthenticationInfo对象返回
            /**
             * user,身份对象,可以理解为在Web环境中登录成功后需要放入Session中的对象
             * user.getPassword()凭证(密码),需要和传入的凭证(密码)做比对
             * this.getName()当前 Realm 的名称,暂时无用,不需纠结
             *
             * */
            return new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
        }
    }
}
复制代码

新建一个测试类

    @Test
    public void testAuthorByRealm(){
        /*************************登录验证**************************/
        //创建Shiro安全管理器,是Shiro的核心
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        //加载shiro.ini配置,得到配置中的用户信息
        securityManager.setRealm(new UserRealm());

        //把安全管理器注入到当前的环境中
        SecurityUtils.setSecurityManager(securityManager);

        //无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
        Subject subject = SecurityUtils.getSubject();

        //创建令牌(携带用户的账户和密码)
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("liuxw","666");

        System.out.println("认证状态----->"+subject.isAuthenticated());

        /*************************登录验证结束**************************/

        //判断用户是否登录
        System.out.println("是否登录---->"+subject.isAuthenticated());

        //判断用户是否有某个角色
        System.out.println("是否有hr这个角色---->"+subject.hasRole("hr"));
        System.out.println("是否有seller这个角色---->"+subject.hasRole("seller"));

        //check开头的是没有返回值的,当没有权限时就会抛出异常
        //subject.checkRole("seller");

        //是否同时拥有多个角色
        System.out.println("是否同时拥有hr和seller----->"+Arrays.toString(subject.hasRoles(Arrays.asList("hr","seller"))));
        System.out.println("是否同时拥有hr和seller----->"+subject.hasAllRoles(Arrays.asList("hr","seller")));

        //判断用户是否有某个权限
        System.out.println("是否有用户删除权限------>"+subject.isPermitted("user:delete"));

        //subject.checkPermission("customer:list");

 }
复制代码

结果: image.png

后记:

​ 这个是案例是简单的在JavaSE环境下对Shiro的使用,数据都是模拟的,通过ini文件和自定义Realm两种方式,熟悉ShiroAPI,Shiro具体的内部是怎么实现的整理好了再发出来,还有Shiro"加盐加密"的基本操作下次发出来.

猜你喜欢

转载自juejin.im/post/7036281606264324133