百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Shiro学习(2):自定义Realm(shiro 自定义realm)

toyiye 2024-09-03 22:34 3 浏览 0 评论

写在前面

通过前面的学习,我们知道实际进行权限信息验证的是Realm,而Shiro框架内部提供了两种实现,一种是查询.ini文件的IniRealm;另一种是查询数据库的JdbcRealm。那么本篇就来分别研究这两个实现。

本篇主要学习以下内容:(1)Realm;(2)Shiro功能;(3)Shiro优点:(4)Shiro架构:(5)Shiro概念:(6)Shiro认证流程:(7)Shiro授权流程。

Realm

Shiro从Realm中获取安全数据(用户、角色、权限),也就是说SecurityManager想要验证用户身份,那么它必须从Realm中获取安全数据进而来确定用户的身份是否合法,之后判断用户是否具有某个角色,某个权限。

自定义Realm

一般来说,自定义Realm都需要继承org.apache.shiro.realm.AuthorizingRealm类,查看一下这个类的源码,如下所示:

public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {}

可以看到这个AuthorizingRealm(授权)类继承了AuthenticatingRealm(认证)类,也就是说授权必须在认证之后才能进行,这个是正常逻辑,先进行用户身份认证,之后对用户进行权限授权。其实你如果自己研究的话,可以发现这个AuthenticatingRealm(认证)类是继承了CachingRealm(缓存)类:

public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {}

在前面认证的时候,我们需要使用用户名和密码来构造一个UsernamePasswordToken对象,而这个UsernamePasswordToken其实是AuthenticationToken的子类:

public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {}

public interface HostAuthenticationToken extends AuthenticationToken {}

而这个AuthenticationToken则是一个接口,里面有两个方法,其中getPrincipal方法用户获取用户身份(通常为用户名),而getCredentials方法则用户获取用户凭证(通常为用户密码):

public interface AuthenticationToken extends Serializable {
    Object getPrincipal();
    Object getCredentials();
}

接下来看一下此时的用户认证流程: (1)将AuthenticationToken转为UsernamePasswordToken; (2)从UsernamePasswordToken中获取username; (3)调用数据库中的方法,从数据库中查询username所对应的记录; (4)判断(3)中得到的记录是否存在,不存在则抛出UnkownAccountException异常; (5)根据用户信息来决定其抛出的其他异常; (6)根据用户需要来重写AuthorizingRealm类中的doGetAuthenticationInfo(AuthenticationToken authenticationToken)方法。在该方法内,构造一个AuthenticationInfo对象,通常使用SimpleAuthenticationInfo对象,主要这个SimpleAuthenticationInfo对象有多个重载方法,这里主要使用下面这个方法:

 public SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {
        this.principals = new SimplePrincipalCollection(principal, realmName);
        this.credentials = hashedCredentials;
        this.credentialsSalt = credentialsSalt;
    }

可以看到它有四个参数,第一个是principal,表示认证的主体信息,可以是username,也可以是数据库对应的用户的实体类对象;第二个是hashedCredentials,表示用户凭证(通常为用户密码);第三个是credentialsSalt,表示盐值;第四个是realmName,即当前自定义Realm对象的name,通常直接调用父类的getName()方法即可。

IniRealm

接下来通过一个实例来学习IniRealm如何通过查询.ini文件中的信息来进行权限信息验证:

第一步,新建项目。使用Maven新建一个名为envy-shiroini的文件,然后在其pom.xml依赖文件中新增如下依赖:

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

第二步,在项目src/main/resources目录下新建一个shiro.ini文件,然后在里面新增如下信息:

#定义用户
[users]
#用户名envy,密码1234, 角色admin
envy = 1234,admin
#用户名book,密码4321, 角色producter
book = 4321,developer
#定义角色
[roles]
#管理员拥有所有权限
admin = *
#产品维护人只能维护产品相关信息
producter = addProduct,deleteProduct,editProduct,updateProduct,listProduct
#订单维护人只能维护订单相关信息
orderer = addOrder,deleteOrder,editOrder,updateOrder,listOrder

第三步,在src/main/java目录下新建shiroini目录,并在shiroini目录中新建一个User类,其中的代码如下:

public class User {
    private String name;
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

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

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public User() {}
}

第四步,在src/main/java/shiroini目录下新建一个ShiroIniTest类,其中的代码如下:

public class ShiroIniTest {

    /**
     * 返回一个Subject对象
     */
    private static Subject getSubject() {
        //1、创建一个SecurityManager对象
        DefaultSecurityManager defaultSecurityManager =new DefaultSecurityManager();
        //2、创建一个IniRealm对象
        IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
        //3、设置Realm信息
        defaultSecurityManager.setRealm(iniRealm);
        //4、将安全管理者放入全局对象
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //5、全局对象通过安全管理者生成Subject对象
        Subject subject = SecurityUtils.getSubject();
        return subject;
    }

    /**
     * 判断用户是否登录
     */
    public static boolean login(User user) {
        Subject subject = getSubject();
        if (subject.isAuthenticated()) {
            //如果用户已经登录,那么就退出
            subject.logout();
        }
        //用户未登录,需要取出用户信息并进行验证
        UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());
        try {
            //将用户信息token传递到IniRealm进行验证
            subject.login(token);
        } catch (AuthenticationException e) {
            //验证失败
            return false;
        }
        return subject.isAuthenticated();
    }

    /**
     * 判断用户是否有对应角色
     */
    public static boolean hasRole(String role) {
        Subject subject = getSubject();
        return subject.hasRole(role);
    }

    /**
     * 判断用户是否有对应权限
     */
    public static boolean isPermitted(String permission) {
        Subject subject = getSubject();
        return subject.isPermitted(permission);
    }

    public static void main(String[] args){
        //模拟几个用户,其中envy和book在之前的shiro.ini文件中,movie用户不存在
        List<User> userList = Arrays.asList(
                new User("envy","1234"),
                new User("book","4321"),
                new User("movie","1234")
        );
        //定义角色信息
        List<String> rolesList = Arrays.asList("admin","producter");

        //定义权限信息
        List<String> permitsList = Arrays.asList("listProduct","listOrder");

        System.out.println("*********用户登录*********");

        //用户登录
        for(User user:userList){
            if(login(user)){
                System.out.println(String.format("%s 登录成功,登录密码为:%s",user.getName(),user.getPassword()));
            }else{
                System.out.println(String.format("%s 登录失败,使用密码为:%s",user.getName(),user.getPassword()));
            }
        }

        System.out.println("*********角色判断*********");

        //判断用户是否属于某个角色
        for(User user:userList){
            for(String role:rolesList){
                if(login(user)){
                    if(hasRole(role)){
                        System.out.println(String.format("%s 属于%s角色",user.getName(),role));
                    }else{
                        System.out.println(String.format("%s 不属于%s角色",user.getName(),role));
                    }
                }
            }
        }

        System.out.println("*********权限判断*********");
        //判断用户是否具有某个权限
        for(User user:userList){
            for(String permit:permitsList){
                if (login(user)){
                    if(isPermitted(permit)){
                        System.out.println(String.format("%s 具有%s权限",user.getName(),permit));
                    }else{
                        System.out.println(String.format("%s 不具有%s权限",user.getName(),permit));
                    }
                }
            }
        }
    };
}

第五步,运行ShiroIniTest测试类,可以发现运行结果如下所示:

通过对比之前的shiro.ini文件,可以发现控制台的输出信息是正确的。

JdbcRealm

使用数据库

IniRealm是通过查询.ini文件中的信息来进行权限信息验证,但是在实际工作中开发者通常都会将权限相关信息存在数据库中,因此IniRealm不太适合这种场景,应当使用另一种权限信息验证的Realm---查询数据库的JdbcRealm。

RBAC解释

在权限控制方面,RBAC是权限系统的设计基础,它有两种解释:一种是Role-Base Access Control(基于角色的访问控制),举个例子当用户想对订单进行编辑和修改,那么用户必须具备订单维护人这一角色;而另一种则是Resource-Base Access Control(基于资源的访问控制),举个例子当用户想对商品进行查询和删除,那么用户必须具备可以查询和删除商品的这些权限。

因此如果使用RBAC理论,那么必须存在三张基础表:用户表、角色表和权限表。当然除此之外,一般还有两个表:一个是用户与角色的多对多关系表,另一个是角色与权限的多对多关系表。请注意,你可能觉得还应该有用户与权限之间的多对多关系表,其实不需要的,因为用户和权限之间可以通过角色来间接建立。

用户与角色的多对多关系是指,一个用户可以有多个角色,一个角色可以对应多个用户;角色与权限的多对多关系表是指,一个角色可以有多个权限,一个权限可以对应多个角色。

JdbcRealm实例

接下来通过一个伪实例来学习Shiro如何通过JdbcRealm来查询数据库,并实现权限信息验证。所谓伪实例是指通过模拟数据库的操作,来实现数据库相类似的操作。

第一步,新建项目。使用Maven新建一个名为envy-shirojdbc的文件,然后在其pom.xml依赖文件中新增如下依赖:

    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-all</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

第二步,在src/main/java目录下新建shirojdbc目录,并在shirojdbc目录中新建一个User类,其中的代码如下:

public class User {
    private String name;
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

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

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public User() {}
}

第三步,在src/main/java/shirojdbc目录下新建一个MyUserRealm类,注意这个类需要继承Shiro的AuthorizingRealm类,并重写其中用于认证的doGetAuthenticationInfo和授权的doGetAuthorizationInfo方法,其中的代码如下:

public class MyUserRealm extends AuthorizingRealm {

    /**
     * 模拟数据库用户
     * */
    Map<String,String> userMap = new HashMap<String, String>();

    {
        userMap.put("envy","1234");
        //设置自定义Realm的名称
        super.setName("MyUserRealm");
    }

    /**
     * 模拟数据库通过用户名来查询用户密码
     * */
    public String getPasswordByUserName(String userName){
        return userMap.get(userName);
    }

    /**
     * 模拟数据库通过用户名来查询用户角色名称
     * */
    public Set<String> getRolesByUserName(String userName){
        Set<String> roleSet = new HashSet<String>();
        roleSet.add("admin");
        roleSet.add("user");
        return roleSet;
    }

    /**
     * 模拟数据库通过角色名来查询角色权限
     * */
    public Set<String> getPermissionsByUserName(String roleName){
        Set<String> permissionSet = new HashSet<String>();
        permissionSet.add("user:add");
        permissionSet.add("user:update");
        permissionSet.add("user:delete");
        return permissionSet;
    }

    /**
     * 认证
     * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1、从主体传过来的认证信息中获取用户名
        String userName = (String) authenticationToken.getPrincipal();
        //2、通过用户名去“数据库”中获取用户密码
        String password = getPasswordByUserName(userName);
        if(password==null){
            return null;
        }
        //3、新建一个认证对象
        //验证通过,认证信息中存放账号密码, getName()用于获取当前Realm的继承方法,通常返回当前类名MyDatabaseReal
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName,password,getName());
        return simpleAuthenticationInfo;
    }

    /**
     *授权
     * */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //认证通过后,接下来开始授权操作
        //1、获取用户名
        String userName = (String)  principalCollection.getPrimaryPrincipal();
        //2、从数据库中查询当前username对象所具有的角色和权限
        Set<String> roleSet = getRolesByUserName(userName);
        Set<String> permissionSet = getPermissionsByUserName(userName);
        //3、新建一个授权对象
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roleSet);
        simpleAuthorizationInfo.setStringPermissions(permissionSet);
        return simpleAuthorizationInfo;
    }
}

第四步,在src/main/java/shirojdbc目录下新建一个EnvyShiroJDBCTest类,其中的代码如下:

public class EnvyShiroJDBCTest {
    @Test
    public void test(){
        //1、实例化自定义Realm对象
        MyUserRealm myUserRealm = new MyUserRealm();
        //2、创建一个SecurityManager对象
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //3、设置Realm信息
        defaultSecurityManager.setRealm(myUserRealm);
        //4、将安全管理者放入全局对象
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //5、全局对象通过安全管理者生成Subject对象
        Subject subject = SecurityUtils.getSubject();

        //6、实例化一个对象,并进行登录验证
        UsernamePasswordToken token = new UsernamePasswordToken("envy","1234");
        subject.login(token);

        //判断用户是否认证成功,注意返回值是boolean类型
        System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出true
        // 判断subject即当前登录用户是否具有admin和user两个角色权限,如果没有则会报错
        subject.checkRoles("admin", "user");
        //subject.checkRole("xxx"); // 报错
        // 判断subject即当前登录用户是否具有user:add权限
        subject.checkPermission("user:add");
    }
}

第五步,运行EnvyShiroJDBCTest测试类,可以发现运行结果如下所示,则表明测试通过:

isAuthenticated:true

ok,那么本篇关于自定义Realm的学习就到此为止,后续开始学习其他知识。


相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码