shiro学习(通俗易懂)

Posted 小样5411

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shiro学习(通俗易懂)相关的知识,希望对你有一定的参考价值。

一、什么是shiro?

shiro是aoache旗下的一个开源框架,它将软件系统的安全认证的功能抽取出来,简单易用,实现用户身份认证,权限授权,加密、会话、管理等功能,组成了一个通用的安全认证框架。

详细了解shiro链接如下:
https://shiro.apache.org/architecture.html
如下图,红色框住的就是shiro最核心部分,图大家简单有个印象即可
在这里插入图片描述
若文章有错误地方,欢迎评论交流

二、第一个shiro用户认证程序

认证:我们现在应该知道了,身份认证,就是判断一个用户是否为合法用户的过程,也就是最常用的简单身份认证方式,比如登录时对用户输入的账号密码进行验证,判断是否与系统中存储的用户名和密码一致。

注:我IDEA写的代码会压缩成压缩包放在最后以百度网盘链接给出,供大家参考

shiro中对于不同对象有自己的称呼
1、Subject:主体
如登录的用户
2、Principal:身份信息
如用户的用户名,手机号,邮箱这种具有唯一标识的身份信息,主身份(Primary Principal)作为登录的信息,因为不可能所有身份都作为登录的,那多且太麻烦了,只要账号这种身份信息作为登录即可。
3、Credential:凭证信息
主体对象自己知道的安全信息,如密码。注意下面图,等下认证程序就是跟着这幅图写
在这里插入图片描述

构建一个Maven项目,直接构建一个quickstart的即可
在这里插入图片描述

建好项目就导入依赖

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

接下来新建resources文件夹,放入shiro的配置文件,叫shiro.ini,这个配置文件只在刚学习的时候用,后面整合SpringBoot不用这个,这里相当于伪造数据。

[users]
xiaoming=123
zhangsan=123456
yx5411=12345

接着建立一个认证类TestAuthenticator,目录结构如下
在这里插入图片描述

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;

public class TestAuthenticator {
    //根据认证流程写
    public static void main(String[] args) {
        //1、创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2、给安全管理器设置realm,new IniRealm()是读取shiro.ini
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //3、给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //4、关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        //5、创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaoming","123");
        try{
            System.out.println("认证状态:"+subject.isAuthenticated());
            subject.login(token);//用户认证(登录)
            System.out.println("认证状态:"+subject.isAuthenticated());
        }catch (Exception e){
            e.printStackTrace();//认证不通过会报异常
        }
    }
}

在这里插入图片描述
若将令牌的账号变成xiaobai,没有这个账号,则会报未知账户异常
在这里插入图片描述
在这里插入图片描述
如果是密码不对,就会报凭证不正确异常,也就是密码错误
在这里插入图片描述
在这里插入图片描述
于是可以专门捕获这两个异常,用于提示用户
在这里插入图片描述

三、自定义Realm实现

上面是用配置文件自己伪造了数据,如果我们要连接上数据库呢?这里就需要自定义Realm了,Realm就是和数据库连接的。
在这里插入图片描述
上面代码就是运行TestCustomerRealmAuthenticator类,假设登录输入账号密码是xiaoming,1234,由SimpleAuthenticationInfo查到数据库中对应用户名的密码是123,密码不同,就会返回凭证不正确异常(即密码错误),注意这里都是自己弄的假数据,真正连接数据库要用jdbc和mybatis。我们后面都先用CustomerRealm这种造伪数据,也就是假设从数据库中查出的,等真正整合SpringBoot再连接真正数据库。
在这里插入图片描述

如果改成下图,数据库中没有输入的用户,就会显示用户名错误
在这里插入图片描述
在这里插入图片描述

CustomerRealm.java

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * 自定义realm实现,将认证/授权数据的来源转为数据库的实现
 */
public class CustomerRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //token中获取用户名
        String principal = (String) authenticationToken.getPrincipal();//用户名(输入的)
        //根据这个身份信息,用jdbc或者mybatis和数据库中数据比对,看有没有这个用户
        //下面我们就不查了,做一个伪数据,假设数据库中只有小明这一个用户数据,后续和SpringBoot整合再连接数据库,假设xiaoming是数据库中仅存的数据
        if ("xiaoming".equals(principal)){
            //三个参数表示从数据库中查到的账号和密码,最后一个就是Realm的名字,直接用getName即可
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"123",this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

TestCustomerRealmAuthenticator.java

import com.yx.realm.CustomerRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

/**
 * 使用自定义的realm
 */
public class TestCustomerRealmAuthenticator {
    public static void main(String[] args) {
        //1、创建SecurityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //2、设置自定义realm
        defaultSecurityManager.setRealm(new CustomerRealm());
        //3、给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //4、关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        //5、创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaoming","1234");
        try{
            subject.login(token);
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名错误");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();//认证不通过会报异常
            System.out.println("密码错误");
        }
    }
}

四、MD5算法加密

我们这里是没有进行加密的,伪造查到的数据库中数据123也是明文,不安全,尤其是以后用户支付密码,那就更不能明文存储了
在这里插入图片描述
我们会用MD5+Salt进行加密,MD5是不可逆的,只能明文生成密文,同一个明文每次生成的密文都是一致的,有些网站所谓的MD5破解,只是将一些常见的密码,比如123456,root,888888,这种简单密码,做了一个密文数据库,如果有匹配,那就给你返回它存储的明文,但稍微难一点的都不能。所以我们在设置密码的时候,都会提示你要数字+英文字符这样这种网站就推不出来了。但是虽然网站这样提示要求,最后用户也可能输入一个aaa123456这种简单又符合输入要求的,所以就需要加密算法再提升一下,也就是用Salt,让加密更加复杂化,会在MD5加密后的字符串后面再加几个随机复杂字符。

测试一下Md5加密

import org.apache.shiro.crypto.hash.Md5Hash;

public class TestShiroMd5 {
    public static void main(String[] args) {
        Md5Hash md5Hash = new Md5Hash("123");//给123加密
        System.out.println(md5Hash.toHex());

        //Md5+salt处理
        Md5Hash md5Hash1 = new Md5Hash("123","x0*7p");//这个盐这里定死了,后面一个写一个随机生成字符的方法
        System.out.println(md5Hash1.toHex());

        //Md5+salt处理+hash散列
        Md5Hash md5Hash2 = new Md5Hash("123","x0*7p",1024);//1024表示散列1024次,更加复杂与安全
        System.out.println(md5Hash2.toHex());
    }
}

加密后就是16进制的32位字符串,最后一个最复杂,也最安全
在这里插入图片描述

在这里插入图片描述
两个类
TestCustomerMD5RealmAuthenticator .java

import com.yx.realm.CustomerMd5Realm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

public class TestCustomerMD5RealmAuthenticator {
    public static void main(String[] args) {
        //1、创建SecurityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //2、设置自定义realm
        CustomerMd5Realm realm = new CustomerMd5Realm();
        //设置realm使用hash凭证匹配器,用md5
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        realm.setCredentialsMatcher(hashedCredentialsMatcher);
        defaultSecurityManager.setRealm(realm);
        //3、给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //4、关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        //5、创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaoming","123");
        try{
            subject.login(token);
            System.out.println("登录成功");
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名错误");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();//认证不通过会报异常
            System.out.println("密码错误");
        }
    }
}

CustomerMd5Realm.java

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

//Md5+salt+hash
public class CustomerMd5Realm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String) authenticationToken.getPrincipal();//用户名(输入的)
        if ("xiaoming".equals(principal)){
            //三个参数表示从数据库中查到的账号和密码,最后一个就是Realm的名字,直接用getName即可
            return new SimpleAuthenticationInfo(principal,"202cb962ac59075b964b07152d234b70",this.getName());
        }
        return null;
    }
}

用MD5加密需要在设置自定义realm改一改,加一个设置md5,因为默认不是md5,相当于告诉程序用md5加密,运行TestCustomerMD5RealmAuthenticator 类
在这里插入图片描述
CustomerMd5Realm.java加入salt
在这里插入图片描述
同样运行TestCustomerMD5RealmAuthenticator 类
在这里插入图片描述
这里他会自动加上盐,再与数据库中这个加密的字符串比较

再加上Hash散列呢,散列可是散列1024次
在这里插入图片描述
TestCustomerMD5RealmAuthenticator 类中加上散列多少次,相当于告诉密码散列了多少次
在这里插入图片描述
在这里插入图片描述
以上就是MD5+Salt+Hash散列加密方式,我们再把核心代码贴出来

public class TestCustomerMD5RealmAuthenticator {
    public static void main(String[] args) {
        //1、创建SecurityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //2、设置自定义realm
        CustomerMd5Realm realm = new CustomerMd5Realm();
        //设置realm使用hash凭证匹配器,用md5
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(hashedCredentialsMatcher);
        defaultSecurityManager.setRealm(realm);
        //3、给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //4、关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        //5、创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaoming","123");
        try{
            subject.login(token);
            System.out.println("登录成功");
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名错误");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();//认证不通过会报异常
            System.out.println("密码错误");
        }
    }
}

//MD5+Salt+Hash
public class CustomerMd5Realm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String) authenticationToken.getPrincipal();//用户名(输入的)
        if ("xiaoming".equals(principal)){
            //三个参数表示从数据库中查到的账号和密码,最后一个就是Realm的名字,直接用getName即可
            return new SimpleAuthenticationInfo(principal,
                    "4d41fdb34ea9f4b5dd2b890a3b89943e",
                    ByteSource.Util.bytes("x0*7p"),
                    this.getName());
        }
        return null;
    }
}

五、shiro中的授权

之前是认证,也就是登录进行认证,现在是授权,也就是访问权限,不同用户具有不同的访问权限,如对某些用户密码修改权限,只能管理员才能有。授权是基于认证的,因为你只有合法登录通过,你才有一些访问权限。
在这里插入图片描述

两种授权方式:1、基于角色的访问控制 2、基于资源的访问控制

我们直接来实践一下,再来讲解,我们接着用md5+salt
+hash的认证后面加内容,因为授权要先认证通过,也就是要先登录通过。

CustomerMd5Realm.java

首先基于角色的访问控制,假设xiaoming有两个角色,admin和user,后面SpringBoot整合真正写数据库,现在先造这两个数据,一般用户有哪些角色都会存在数据库中。

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.以上是关于shiro学习(通俗易懂)的主要内容,如果未能解决你的问题,请参考以下文章

机器学习:通俗易懂决策树与随机森林及代码实践

通俗易懂权限管理模块设计-Java

通俗易懂权限管理模块设计-Java

通俗易懂的图解机器学习之机器学习概论

通俗易懂的图解机器学习之机器学习概论

通俗易懂谈机器学习