项目用了shiro,犹如男人娶了老婆,别的妹子想撩需要认证与授权

1:简介

        既然是简介,那就要简单粗暴,shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权,加密和会话管理等功能,组成了一个通用的安全认证框架,官网地址为http://shiro.apache.org/

2:shiro认证

        身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式就是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确

shiro中认证的关键对象

  • subject:主体,访问系统的用户、程序等,进行认证的都称为主体
  • Principal:身份信息,是主体(subject)进行身份认证的标识,标识具有唯一性,如用户名,手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份
  • credential:凭证信息,是只有主体自己知道的安全信息,如密码、证书等

认证流程
在这里插入图片描述

2-1:第一个shiro认证程序

        先建一个简单的maven模块
在这里插入图片描述
在这里插入图片描述

导入maven依赖

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

类路径下建立.ini文件

        因为是简单的例子,我们没使用数据库,所以使用.ini文件存入用户账户密码信息。格式我[users]下,账号=密码
在这里插入图片描述

[users]
zhangsan=123
lisi=1234
wangwu=456

测试例子代码

package com.lingalu;

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.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;

public class TestShiro {
    public static void main( String[] args ) {
        // 创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 从ini文件中那数据给安全管理器设置realm
        securityManager.setRealm(new IniRealm("classpath:myShiro.ini"));
        // 给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //  关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        // 创建令牌,使用正确的账号密码
        UsernamePasswordToken lisi = new UsernamePasswordToken("lisi", "1234");
        try{
            System.out.println("认证状态"+subject.isAuthenticated());
            // 用户认证
            subject.login(lisi);
            System.out.println("认证状态"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("认证失败,用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("认证失败,密码错误");
        }

    }
}

运行结果
在这里插入图片描述

2-2:MD5+盐

MD5算法

  • 作用:一般用来加密或者签名(校验和)
  • 特点:MD5算法不可逆,内容相同的数据无论执行多少次md5加密生成的结果始终一致
  • 生成结果:始终是一个16进制32位长度字符串

        虽然MD5加密是不可逆的,但是简单的数据还是可以根据加密后的数据进行碰撞来破解的,因为我的碰撞库提前存放了简单数据的md5加密后的数据,所以为了提高安全性,这就需要盐了

MD5+盐

  • 所谓盐实际上就是一串随机字符串,加密的时候就把这随机字符串拼上原数据一起进行MD5加密,然后把加密后的数据和盐值一起存放到数据库,如注册账号的时候。如果登录,那么就把加密数据和盐值拿出来,然后就把原密码和盐值进行MD5后和加密数据比较,一样那么密码就正确

        shiro框架还提供了MD5+盐+hash散列

package com.lingalu;

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

public class TestMd5 {
    public static void main(String[] args) {
        // 使用MD5
        Md5Hash md5Hash = new Md5Hash("1234");
        System.out.println(md5Hash.toHex());
        // 使用MD5+salt盐,盐 是随机字符串,假设我们的盐值为"lin*+abc"
        md5Hash = new Md5Hash("1234","lin*+abc");
        System.out.println(md5Hash.toHex());
        // 使用MD5+salt盐+hash散列,散列值一般为1024或者2048
        md5Hash = new Md5Hash("1234","lin*+abc",1024);
        System.out.println(md5Hash.toHex());
    }
}

        运行结果

81dc9bdb52d04dc20036dbd8313ed055
196ce9fb6461c81ab08f072b85452cf8
41b4b8731ad1a347dbe3ccbdc0221224

2-3:自定义Realm的实现认证

2-3-1:明文密码

        自定义Realm

package com.lingalu.shiro;

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;

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();
        System.out.println("在token中获取的用户名:"+principal);
        // 根据身份信查询数据库相关用户数据,这里简单处理
        if("lin".equals(principal)){
            /**
             * 参数说明
             * 参数1:数据库中的用户名
             * 参数2:数据库中的密码
             * 参数3:提供当前realm的名字 this.getName()
             */
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"1234",this.getName());
            return simpleAuthenticationInfo;
        }

        return null;
    }
}

        测试代码,模拟登录

package com.lingalu;

import com.lingalu.shiro.CustomerRealm;
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.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

public class TestShiro {
    public static void main( String[] args ) {
        // 创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 设置自定义realm
        securityManager.setRealm(new CustomerRealm());
        // 给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //  关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        // 创建令牌,使用正确的账号密码
        UsernamePasswordToken lisi = new UsernamePasswordToken("lin", "1234");
        try{
            // 用户认证
            subject.login(lisi);
            System.out.println("登录成功"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("登录失败,用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("登录失败,密码错误");
        }

    }
}

2-3-2:密码加密:MD5

        自定义Realm

package com.lingalu.shiro;

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;

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();
        System.out.println("在token中获取的用户名:"+principal);
        // 根据身份信查询数据库相关用户数据,这里简单处理
        if("lin".equals(principal)){
            /**
             * 参数说明
             * 参数1:数据库中的用户名
             * 参数2:数据库中的密码"81dc9bdb52d04dc20036dbd8313ed055",这是原始密码经过mad加密后的数据
             * 参数3:提供当前realm的名字 this.getName()
             */
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"81dc9bdb52d04dc20036dbd8313ed055",this.getName());
            return simpleAuthenticationInfo;
        }

        return null;
    }
}

        测试代码,模拟登录,这比明文多了凭证匹配器

package com.lingalu;

import com.lingalu.shiro.CustomerRealm;
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 TestShiro {
    public static void main( String[] args ) {
        // 创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 设置自定义realm
        CustomerRealm customerRealm = new CustomerRealm();
        // 获取凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置算法名字
        credentialsMatcher.setHashAlgorithmName("md5");
        // 设置realm使用hash凭证匹配器
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        securityManager.setRealm(customerRealm);
        // 给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //  关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        // 创建令牌,使用正确的账号密码
        UsernamePasswordToken lisi = new UsernamePasswordToken("lin", "1234");
        try{
            // 用户认证
            subject.login(lisi);
            System.out.println("登录成功"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("登录失败,用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("登录失败,密码错误");
        }

    }
}

2-3-3:密码加密:MD5+盐

        自定义Realm

package com.lingalu.shiro;

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;
import org.apache.shiro.util.ByteSource;

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();
        System.out.println("在token中获取的用户名:"+principal);
        // 根据身份信查询数据库相关用户数据,这里简单处理
        if("lin".equals(principal)){
            /**
             * 参数说明
             * 参数1:数据库中的用户名
             * 参数2:数据库中的密码"196ce9fb6461c81ab08f072b85452cf8",这是原始密码经过mad+盐"lin*+abc"加密后的数据
             * 参数3:盐值
             * 参数4:提供当前realm的名字 this.getName()
             */
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"196ce9fb6461c81ab08f072b85452cf8", ByteSource.Util.bytes("lin*+abc"),this.getName());
            return simpleAuthenticationInfo;
        }

        return null;
    }
}

        测试代码,模拟登录

package com.lingalu;

import com.lingalu.shiro.CustomerRealm;
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 TestShiro {
    public static void main( String[] args ) {
        // 创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 设置自定义realm
        CustomerRealm customerRealm = new CustomerRealm();
        // 获取凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置算法名字
        credentialsMatcher.setHashAlgorithmName("md5");
        // 设置realm使用hash凭证匹配器
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        securityManager.setRealm(customerRealm);
        // 给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //  关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        // 创建令牌,使用正确的账号密码
        UsernamePasswordToken lisi = new UsernamePasswordToken("lin", "1234");
        try{
            // 用户认证
            subject.login(lisi);
            System.out.println("登录成功"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("登录失败,用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("登录失败,密码错误");
        }

    }
}

2-3-4:密码加密:MD5+盐+hash散列值

        自定义Realm

package com.lingalu.shiro;

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;
import org.apache.shiro.util.ByteSource;

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();
        System.out.println("在token中获取的用户名:"+principal);
        // 根据身份信查询数据库相关用户数据,这里简单处理
        if("lin".equals(principal)){
            /**
             * 参数说明
             * 参数1:数据库中的用户名
             * 参数2:数据库中的密码"41b4b8731ad1a347dbe3ccbdc0221224",这是原始密码经过mad+盐"lin*+abc"+散列值1024加密后的数据
             * 参数3:盐值
             * 参数4:提供当前realm的名字 this.getName()
             */
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,
                    "41b4b8731ad1a347dbe3ccbdc0221224",
                    ByteSource.Util.bytes("lin*+abc"),
                    this.getName());
            return simpleAuthenticationInfo;
        }

        return null;
    }
}

        测试代码,模拟登录,,加了散列次数的设置

package com.lingalu;

import com.lingalu.shiro.CustomerRealm;
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 TestShiro {
    public static void main( String[] args ) {
        // 创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 设置自定义realm
        CustomerRealm customerRealm = new CustomerRealm();
        // 获取凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置算法名字
        credentialsMatcher.setHashAlgorithmName("md5");
        // 散列次数
        credentialsMatcher.setHashIterations(1024);
        // 设置realm使用hash凭证匹配器
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        securityManager.setRealm(customerRealm);
        // 给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //  关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        // 创建令牌,使用正确的账号密码
        UsernamePasswordToken lisi = new UsernamePasswordToken("lin", "1234");
        try{
            // 用户认证
            subject.login(lisi);
            System.out.println("登录成功"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("登录失败,用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("登录失败,密码错误");
        }

    }
}

3:shiro授权

3-1:shiro授权介绍

        授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的

关键对象,授权可简单理解为who对what(which)进行how操作

  • who:即主体(Subject),主体需要访问系统中的资源
  • what:即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例
  • how:权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限,用户添加权限,某个类方法的调用权限,编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可

授权流程
在这里插入图片描述

授权方式

  • 基于角色的访问控制:RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
// 伪代码
if(subjecy.hasRole("admin")){
	// 操作什么资源
}
  • 基于资源的访问控住:RBAC基于角色的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制
// 伪代码
if(subjecy.isPermission(user:update:01)){	//资源实例
	// 对01用户进行修改
}
if(subjecy.isPermission(user:update:*)){	//资源类型
	// 对所有用户进行修改
}
···

权限字符串

        权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,”:“是资源/操作/实例的分隔符,权限字符串也可以使用*通配符,如

  • 用户创建权限:user:create,或user:create:*
  • 用户修改实例001的权限:user:update:001
  • 用户实例001的所有权限:user:*:001

shiro中授权编程实现方式

  • 编程式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")){
	// 有权限
}else{
	// 无权限
}
  • 注解式
@RequiresRoles("admin")
public void hello(){
	// 有权限
}
  • 标签式
JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
<shiro:hasRole name="admin">
	<!- 有权限 ->
</shiro:hasRole>
注意:Thymeleaf中使用shiro需要额外集成

3-2:第一个shiro授权程序

        授权一定是要在认证后,才能授权的,自定义授权

package com.lingalu.shiro;

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.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

public class CustomerRealm extends AuthorizingRealm {
    
    // 重写授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        System.out.println("身份信息:"+primaryPrincipal);
        // 根据身份信息,如用户名,获取当前用户的角色信息,以及权限信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // 将数据库中查询角色信息赋值给权限对象,比如数据库中查出来的该用户角色是admin和test
        simpleAuthorizationInfo.addRole("admin");
        simpleAuthorizationInfo.addRole("test");
        // 将数据库中查询权限信息赋值给权限对象
        simpleAuthorizationInfo.addStringPermission("user:*:01");   // 对01用户具有所有权限
        simpleAuthorizationInfo.addStringPermission("product:create:*");    // 对所有产品具有创建权限
        return simpleAuthorizationInfo;
    }

    // 重写认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 在token中获取用户名
        String principal = (String)authenticationToken.getPrincipal();
        System.out.println("在token中获取的用户名:"+principal);
        // 根据身份信查询数据库相关用户数据,这里简单处理
        if("lin".equals(principal)){
            /**
             * 参数说明
             * 参数1:数据库中的用户名
             * 参数2:数据库中的密码"41b4b8731ad1a347dbe3ccbdc0221224",这是原始密码经过mad+盐"lin*+abc"+散列值1024加密后的数据
             * 参数3:盐值
             * 参数4:提供当前realm的名字 this.getName()
             */
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,
                    "41b4b8731ad1a347dbe3ccbdc0221224",
                    ByteSource.Util.bytes("lin*+abc"),
                    this.getName());
            return simpleAuthenticationInfo;
        }

        return null;
    }
}

        授权判断

package com.lingalu;

import com.lingalu.shiro.CustomerRealm;
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;
import java.util.Arrays;

public class TestShiro {
    public static void main( String[] args ) {
        // 创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 设置自定义realm
        CustomerRealm customerRealm = new CustomerRealm();
        // 获取凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置算法名字
        credentialsMatcher.setHashAlgorithmName("md5");
        // 散列次数
        credentialsMatcher.setHashIterations(1024);
        // 设置realm使用hash凭证匹配器
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        securityManager.setRealm(customerRealm);
        // 给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //  关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        // 创建令牌,使用正确的账号密码
        UsernamePasswordToken lisi = new UsernamePasswordToken("lin", "1234");
        try{
            // 用户认证
            subject.login(lisi);
            System.out.println("登录成功"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("登录失败,用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("登录失败,密码错误");
        }

        // 授权
        if(subject.isAuthenticated()){
            // 基于角色权限控制
            System.out.println("基于角色权限控制======");
            System.out.println(subject.hasRole("admin"));   //true
            System.out.println(subject.hasRole("super"));   //false
            // 基于多角色权限控制
            System.out.println("基于多角色权限控制======");
            System.out.println(subject.hasAllRoles(Arrays.asList("admin","test")));   //true
            System.out.println(subject.hasAllRoles(Arrays.asList("admin","super")));   //false
            // 是否具有其中一个角色
            System.out.println("否具有其中一个角色======");
            boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "super", "test"));
            for(boolean b:booleans){
                System.out.println(b);
            }
            System.out.println("==========分隔符==========");
            // 基于权限字符串的访问控制    资源标识符:操作:资源类型或者资源实例
            System.out.println("基于权限字符串的访问控制======");
            // true,因为该主体对01号user有所有权限
            System.out.println(subject.isPermitted("user:update:01"));
            // false,因为该主体对product只拥有create权限
            System.out.println(subject.isPermitted("product:update:*"));
            // 同时具有哪些权限
            System.out.println("同时具有哪些权限======");
            //false
            System.out.println(subject.isPermittedAll("user:update:01","product:update:*"));
            // true
            System.out.println(subject.isPermittedAll("user:update:01","product:create:005"));
            // 分别具有那些权限
            System.out.println("分别具有那些权限======");
            boolean[] permitted = subject.isPermitted("user:delete:01", "product:create:005","order:create:005");
            for(boolean b:permitted){
                System.out.println(b);
            }
        }
    }
}

        运行结果

在token中获取的用户名:lin
登录成功true
基于角色权限控制======
身份信息:lin
true
身份信息:lin
false
基于多角色权限控制======
身份信息:lin
身份信息:lin
true
身份信息:lin
身份信息:lin
false
否具有其中一个角色======
身份信息:lin
身份信息:lin
身份信息:lin
true
false
true
==========分隔符==========
基于权限字符串的访问控制======
身份信息:lin
true
身份信息:lin
false
同时具有哪些权限======
身份信息:lin
身份信息:lin
false
身份信息:lin
身份信息:lin
true
分别具有那些权限======
身份信息:lin
身份信息:lin
身份信息:lin
true
true
false

        从运行结果可见,每次判断都走了授权方法,而方法内每次都查询数据库,这是很耗时的操作,所以小需要使用缓存来替代,下面会说到

4:springBoot+shiro+jsp

4-1:springBoot+jsp

        我们先建立springBoot+jsp基本项目
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
        项目建立后删除无用信息
在这里插入图片描述
        因为我们是集成jsp,所以要有web.app,自己加上去
在这里插入图片描述
        jsp页面快速生成内容可以输入!+tab,然后在头部加上

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" isELIgnored="false" %>

        maveny引入jsp的解析依赖和jstl的依赖

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

        yml配置文件配置

server:
  port: 8888
  servlet:
    context-path: /shiro  # 项目虚拟路径
spring:
  application:
    name: shiro
  mvc:
    view:
      prefix: / # 视图前缀
      suffix: .jsp  # 视图后缀

        如果项目启动访问不成功的,那就配置以下
在这里插入图片描述
        启动访问
在这里插入图片描述
        到此jsp的搭建完毕

4-2:springBoot+jsp+mybatis

        关于mybatis的集成,可以看这篇SpringBoot,写完老板又让我转回后端的第六节SpingBoot整合数据访问

4-3:springBoot+jsp+mybatis+shiro

        要使用shiro,就得mavan导入依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.5.3</version>
</dependency>

        来一个shiro的盐的工具类SaltUtils

package com.lingaolu.utils;

import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;

public class SaltUtils {
    /**
     * 随机数生成器
     */
    private static RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();

    /**
     * 指定hash算法为MD5
     */
    private static final String HASHALGORITHMNAME = "md5";

    /**
     * 指定散列次数为1024次,即加密1024次
     */
    private static final int HASHITERATIONS = 1024;

    /**
     * true指定Hash散列值使用Hex加密存. false表明hash散列值用用Base64-encoded存储
     */
    private static final boolean STOREDCREDENTIALSHEXENCODED = true;

    /**
     * 获得hash算法HASHALGORITHMNAME
     * @return
     */
    public static String getHASHALGORITHMNAME() {
        return HASHALGORITHMNAME;
    }

    /**
     * 获得指定散列次数为1024次HASHITERATIONS
     * @return
     */
    public static int getHASHITERATIONS() {
        return HASHITERATIONS;
    }

    /**
     * 获得加密用的盐
     *
     * @return
     */
    public static String createSalt() {
        return randomNumberGenerator.nextBytes().toHex();
    }

    /**
     * 获得加密后的凭证
     *
     * @param credentials 凭证(即密码)
     * @param salt        盐
     * @return
     */
    public static String createCredentials(String credentials, String salt) {
        SimpleHash simpleHash = new SimpleHash(HASHALGORITHMNAME, credentials,
                salt, HASHITERATIONS);
        return STOREDCREDENTIALSHEXENCODED ? simpleHash.toHex() : simpleHash.toBase64();
    }


    /**
     * 进行密码验证
     *
     * @param credentials        未加密的密码
     * @param salt               盐
     * @param encryptCredentials 加密后的密码
     * @return
     */
    public static boolean checkCredentials(String credentials, String salt, String encryptCredentials) {
        return encryptCredentials.equals(createCredentials(credentials, salt));
    }

    public static void main(String[] args) {
        //盐
        String salt = createSalt();
        System.out.println(salt);
        System.out.println(salt.length());
        //凭证+盐加密后得到的密码
        String credentials = createCredentials("1234567", salt);
        System.out.println(credentials);
        System.out.println(credentials.length());
        boolean b = checkCredentials("1234567", salt, credentials);
        System.out.println(b);
    }
}

        使用shiro主要的就是自定义shiro的Realm类,CustomerRealm

package com.lingaolu.shiro;

import com.lingaolu.bean.Perms;
import com.lingaolu.bean.Role;
import com.lingaolu.bean.User;
import com.lingaolu.servive.UserService;
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.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import java.util.List;

public class CustomerRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    // 重写授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取身份信息
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        // 获取用户的角色信息,从数据库中查找
        User user = userService.finaRolesByUserName(primaryPrincipal);
        // 这个user一定是有的,因为下面都能认证 过来了,所以只需要判断角色是否为空
        if(!CollectionUtils.isEmpty(user.getRoles())){
            // 授权角色信息,从数据库中查找
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            user.getRoles().forEach(role->{
                simpleAuthorizationInfo.addRole(role.getName());
                // 权限信息,从数据库中查找
                List<Perms> permsList = userService.findPermsByRoleId(role.getId());
                if(!CollectionUtils.isEmpty(permsList)){
                    permsList.forEach(perms->{
                        simpleAuthorizationInfo.addStringPermission(perms.getName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    }

    // 重写认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String)authenticationToken.getPrincipal();
        User user = userService.findByUserName(principal);
        if(!ObjectUtils.isEmpty(user)){
            return new SimpleAuthenticationInfo(principal,user.getPassWord(), ByteSource.Util.bytes(user.getSalt()),this.getName());
        }
        return null;
    }
}

        接着就是自定义配置类ShiroConfig ,把自定义的CustomerRealm注入到shiro的相应属性容器中,以及shiro的拦截器ShiroFilter等等

package com.lingaolu.config;

import com.lingaolu.shiro.CustomerRealm;
import com.lingaolu.utils.SaltUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //  1.创建shiroFilter
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean filterBean = new ShiroFilterFactoryBean();
        // 给filter设置安全管理器
        filterBean.setSecurityManager(securityManager);
        // 配置系统的受限资源和公共资源
        Map<String,String> map = new HashMap<>();
        // anon表示放行
        map.put("/user/login","anon");
        map.put("/user/register","anon");
        map.put("/register.jsp","anon");
        // authc表示拦截
        map.put("/**","authc");
        filterBean.setFilterChainDefinitionMap(map);
        // 默认认证界面路径
        filterBean.setLoginUrl("/login.jsp");
        return filterBean;
    }

    // 2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 给安全管理器 设置Realm
        securityManager.setRealm(realm);
        return securityManager;
    }

    // 3、创建自定义realm
    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        // 获取凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置算法名字,md5
        credentialsMatcher.setHashAlgorithmName(SaltUtils.getHASHALGORITHMNAME());
        // 散列次数,1024
        credentialsMatcher.setHashIterations(SaltUtils.getHASHITERATIONS());
        // 设置realm使用hash凭证匹配器
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        return customerRealm;
    }
}

        map中的值,anon和authc是过滤器,分别表示放行和拦截的意思,更多的用法如下
在这里插入图片描述
        这用到的权限表,简单的有5个表,用户表user,角色表role,用户和角色的关系表user_role,权限表perms,角色和权限的关系表role_perms,当然更多更详细的表设计可以根据具体的业务需求
在这里插入图片描述
        接下来就是相关的实体类

package com.lingaolu.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Long id;
    private String userName;
    private String passWord;
    private String salt;

    // 角色集合
    private List<Role> roles;

}
package com.lingaolu.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {

    private Long id;
    private String name;

    // 定义权限集合
    private List<Perms> perms;
}
package com.lingaolu.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Perms {
    private Long id;
    private String name;
    private String url;
}

        dao层和xml

package com.lingaolu.dao;

import com.lingaolu.bean.Perms;
import com.lingaolu.bean.Role;
import com.lingaolu.bean.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {

    List<User> userList();

    User findByUserName(String userName);

    Long deleteUserById(Long id);

    Long insertUser(User user);

    Long updateUser(User user);

    User finaRolesByUserName(String userName);

    // 根据角色id查询权限集合
    List<Perms> findPermsByRoleId(Long id);

}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lingaolu.dao.UserMapper">

    <sql id="Base_Column_List" >
          id
        , user_name
        , pass_word
        , salt
    </sql>

    <sql id="QueryCondition">
        <where>
            <if test="userName != null and userName!=''" >
                and LOCATE(#{userName},user_name) > 0
            </if>
        </where>
    </sql>
    <select id="userList" parameterType="map" resultType="User">
        SELECT
        <include refid="Base_Column_List" />
        FROM
        user
        where user_name = #{userName}
    </select>

    <select id="findByUserName" parameterType="String" resultType="User">
        SELECT
        <include refid="Base_Column_List" />
        FROM
        user
        <include refid="QueryCondition" />
    </select>

    <insert id="insertUser" parameterType="User" useGeneratedKeys="true"
            keyColumn="id" keyProperty="id">
        insert into user
        <trim prefix="(" suffix=")" suffixOverrides="," >
            <if test="id != null" >
                id,
            </if>
            <if test="userName != null" >
                user_name,
            </if>
            <if test="passWord != null" >
                pass_word,
            </if>
            <if test="salt != null" >
                salt
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides="," >
            <if test="id != null" >
                #{id},
            </if>
            <if test="userName != null" >
                #{userName},
            </if>
            <if test="passWord != null" >
                #{passWord},
            </if>
            <if test="salt != null" >
                #{salt},
            </if>
        </trim>
    </insert>

    <update id="updateUser" parameterType="User">
        update user
        <set >
            <if test="userName != null" >
                user_name = #{userName},
            </if>
            <if test="passWord != null" >
                pass_word = #{passWord},
            </if>
            <if test="salt != null" >
                salt = #{salt},
            </if>
        </set>
        where
        id = #{id}
    </update>

    <delete id="deleteUserById" parameterType="long">
        delete from user where id = #{id}
    </delete>

    <resultMap id="userMap" type="User">
        <id column="uid" property="id"/>
        <result column="user_name" property="userName"/>
        <collection property="roles" javaType="list" ofType="Role">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
        </collection>
    </resultMap>

    <select id="finaRolesByUserName" parameterType="String" resultMap="userMap">
        select u.id uid,u.user_name,r.id,r.name
        from user u
        left join user_role ur
        on u.id=ur.user_id
        left join role r
        on ur.role_id=r.id
        where u.user_name=#{userName}
    </select>

    <select id="findPermsByRoleId" parameterType="long" resultType="Perms">
        select p.* from role_perms rp
        left join perms p
        on rp.perms_id = p.id
        where rp.role_id=#{id}
    </select>
</mapper>

        service层,这里要注意的就是注册的时候,密码的加密,当然更新密码也是需要注意修改加密后的密码,这里更新我只是简单带过

package com.lingaolu.servive;

import com.lingaolu.bean.Perms;
import com.lingaolu.bean.Role;
import com.lingaolu.bean.User;

import java.util.List;

public interface UserService {

    List<User> userList();

    User findByUserName(String userName);

    Long deleteUserById(Long id);

    Long insertUser(User user);

    Long updateUser(User user);

    User finaRolesByUserName(String UserName);

    List<Perms> findPermsByRoleId(Long id);
}
package com.lingaolu.serviceImpl;

import com.lingaolu.bean.Perms;
import com.lingaolu.bean.User;
import com.lingaolu.dao.UserMapper;
import com.lingaolu.servive.UserService;
import com.lingaolu.utils.SaltUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> userList() {
        return userMapper.userList();
    }

    @Override
    public User findByUserName(String userName){
        return userMapper.findByUserName(userName);
    }

    @Override
    public Long deleteUserById(Long id) {
       return userMapper.deleteUserById(id);
    }

    @Override
    public Long insertUser(User user) {
        // 明文密码MD5+salt+hash散列
        // 获得随机盐
        String salt = SaltUtils.createSalt();
        // 获得密文
        String credentials = SaltUtils.createCredentials(user.getPassWord(), salt);
        // 保存盐和密文
        user.setSalt(salt);
        user.setPassWord(credentials);
        return userMapper.insertUser(user);
    }

    @Override
    public Long updateUser(User user) {
        // 这里需要处理加密
        return userMapper.updateUser(user);
    }

    @Override
    public User finaRolesByUserName(String userName){
        return userMapper.finaRolesByUserName(userName);
    }

    @Override
    public List<Perms> findPermsByRoleId(Long id){
        return userMapper.findPermsByRoleId(id);
    }
}

        接着就是控制层了UserController

package com.lingaolu.controller;

import com.lingaolu.bean.User;
import com.lingaolu.servive.UserService;
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.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 用户注册
     * @param user  用户
     * @return
     */
    @RequestMapping("register")
    public String register(User user){
        try{
            userService.insertUser(user);
            return "redirect:/index.jsp";
        }catch (Exception e){
            e.printStackTrace();
            return "redirect:/register.jsp";
        }
    }

    /**
     * 用户登录
     * @param username  用户名
     * @param password  密码
     * @return
     */
    @RequestMapping("login")
    public String login(String username,String password){
        Subject subject = SecurityUtils.getSubject();
        try{
            subject.login(new UsernamePasswordToken(username, password));
            System.out.println("登录成功"+subject.isAuthenticated());
            return "redirect:/index.jsp";
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("登录失败,用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("登录失败,密码错误");
        }
        return "redirect:/login.jsp";
    }

    /**
     * 用户登出
     * @return
     */
    @RequestMapping("logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login.jsp";
    }
}

        这我们的例子用到了3个页面
在这里插入图片描述
        登录页

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>登录页</h1>
    <form action="${pageContext.request.contextPath}/user/login" method="post">
        用户名:<input type="text" name="username"><br/>
        密码:<input type="text" name="password"><br/>
        <input type="submit" value="登录">
    </form>
</body>
</html>

        注册页

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>用户注册</h1>
    <form action="${pageContext.request.contextPath}/user/register" method="post">
        用户名:<input type="text" name="userName"><br/>
        密码:<input type="text" name="passWord"><br/>
        <input type="submit" value="立即注册">
    </form>
</body>
</html>

        以及首页,首页举例了如何使用标签根据权限来判断什么用户什么权限能查看什么数据,只是举例,使用**<shiro:hasAnyRoles name=""><shiro:hasRole name=""><shiro:hasPermission name="">**等等

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>系统主页</h1>
    <a href="${pageContext.request.contextPath}/user/logout">退出用户</a>
    <ul>
        <shiro:hasAnyRoles name="test,user">
            <li><a href="">用户管理</a></li>
            <ul>
                <shiro:hasPermission name="user:*:*">
                    <li><a href="">用户修改</a></li>
                    <li><a href="">用户删除</a></li>
                </shiro:hasPermission>
            </ul>
        </shiro:hasAnyRoles>
        <shiro:hasRole name="admin">
            <li><a href="">文章管理</a></li>
            <li><a href="">视频管理</a></li>
        </shiro:hasRole>
        <shiro:hasAnyRoles name="user,admin">
            <li><a href="">图片管理</a></li>
            <ul>
                <li><a href="">图片修改</a></li>
                <shiro:hasPermission name="image:delete:*">
                    <li><a href="">图片删除1</a></li>
                </shiro:hasPermission>
            </ul>
        </shiro:hasAnyRoles>
    </ul>
</body>
</html>

        更多的权限控制如可以在接口处理,如使用注解@RequiresRoles("")和 @RequiresPermissions("")等等

    /**
     * 查看订单
     */
    @RequiresRoles("admin")
    @RequiresPermissions("order:list:*")
    @RequestMapping("order")
    public String order(){
        // 查看订单操作
        return "order.jsp";
    }

        在我们的自定义CustomerRealm中,每次判断权限都要走一遍doGetAuthorizationInfo方法,而doGetAuthorizationInfo里有从数据库获取用户权限的操作,如下如,这样很耗资源,所以我们需要用缓存来处理,这下一节详解
在这里插入图片描述

4-4:springBoot+jsp+mybatis+shiro+缓存

        缓存的流程为
在这里插入图片描述

4-4-1:缓存EhCache

        maven导入包

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

        然后在获取自定义CustomerRealm中方法中开启缓存

 // 开启缓存管理
customerRealm.setCacheManager(new EhCacheManager());
// 开启全局缓存
customerRealm.setCachingEnabled(true);
// 开启认证缓存,设置认证缓存名
customerRealm.setAuthenticationCachingEnabled(true);
customerRealm.setAuthenticationCacheName("authenticationCache");
// 开启授权缓存,设置授权缓存名
customerRealm.setAuthorizationCachingEnabled(true);
customerRealm.setAuthorizationCacheName("authorizationCache");

        此时我们的自定义配置类ShiroConfig的代码为

package com.lingaolu.config;

import com.lingaolu.shiro.CustomerRealm;
import com.lingaolu.utils.SaltUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //  1.创建shiroFilter
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean filterBean = new ShiroFilterFactoryBean();
        // 给filter设置安全管理器
        filterBean.setSecurityManager(securityManager);
        // 配置系统的受限资源和公共资源
        Map<String,String> map = new HashMap<>();
        // anon表示放行
        map.put("/user/login","anon");
        map.put("/user/register","anon");
        map.put("/register.jsp","anon");
        // authc表示拦截
        map.put("/**","authc");
        filterBean.setFilterChainDefinitionMap(map);
        // 默认认证界面路径
        filterBean.setLoginUrl("/login.jsp");
        return filterBean;
    }

    // 2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(CustomerRealm customerRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 给安全管理器 设置Realm
        securityManager.setRealm(customerRealm);
        return securityManager;
    }

    // 3、创建自定义realm
    @Bean
    public CustomerRealm getCustomerRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        // 获取凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置算法名字,md5
        credentialsMatcher.setHashAlgorithmName(SaltUtils.getHASHALGORITHMNAME());
        // 散列次数,1024
        credentialsMatcher.setHashIterations(SaltUtils.getHASHITERATIONS());
        // 设置realm使用hash凭证匹配器
        customerRealm.setCredentialsMatcher(credentialsMatcher);


        // 开启缓存管理
        customerRealm.setCacheManager(new EhCacheManager());
        // 开启全局缓存
        customerRealm.setCachingEnabled(true);
        // 开启认证缓存,设置认证缓存名
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("authenticationCache");
        // 开启授权缓存,设置授权缓存名
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCacheName("authorizationCache");


        return customerRealm;
    }
}

        这样的话,除了登录会访问doGetAuthorizationInfo方法外,其他刷新操作都不会执行doGetAuthorizationInfo方法,也就是不会重新执行从数据库查找角色权限操作,减轻了系统的开销。
        但是这样还有一个缺点,那就是如果修改了用户的权限,那么缓存并没有修改,除非切换账号再次登录。

4-4-2:缓存Redis

        使用redis,mavan引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

        配置文件配置

spring:
  redis:
    port: 9637
    host: 你的redis服务ip
    database: 3
    password: 135246

        使用Redie替代处理shiro的缓存,需要自定义缓存管理器MyRedisCacheManager

package com.lingaolu.shiro;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;

// 自定义的shiro缓存管理器
public class MyRedisCacheManager implements CacheManager {

    // 参数实际上就是认证或授权的缓存名称
    @Override
    public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
        return new MyRedisCache<>(cacheName);
    }
}

        自定义缓存管理器MyRedisCacheManager中用到MyRedisCache,这个是我们自写的缓存处理,从redis读取数据

package com.lingaolu.shiro;

import com.lingaolu.utils.ApplicationContextUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.Collection;
import java.util.Set;

// 自定义Redis缓存的实现
public class MyRedisCache<K,V> implements Cache<K,V> {

    private String cacheName;

    public MyRedisCache(String cacheName) {
        this.cacheName = cacheName;
    }

    public MyRedisCache() {
    }

    @Override
    public V get(K k) throws CacheException {
        System.out.println("get key:"+k);
        Object o = getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
        return (V)o;
    }

    @Override
    public V put(K k, V v) throws CacheException {
        getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
        return v;
    }

    @Override
    public V remove(K k) throws CacheException {
        Long delete = getRedisTemplate().opsForHash().delete(this.cacheName, k.toString());
        return (V)delete;
    }

    @Override
    public void clear() throws CacheException {
        getRedisTemplate().opsForHash().delete(this.cacheName);
    }

    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }

    @Override
    public Set<K> keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }

    @Override
    public Collection<V> values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }

    private RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate)ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }

}

        自写的缓存处理用到自写的工具ApplicationContextUtils,因为RedisTemplate没法注入,所以自写一个工具ApplicationContextUtils从容器中获取

package com.lingaolu.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextUtils implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    // 根据bean的名字获取工厂中指定的bean对象
    public static Object getBean(String beanName){
        return context.getBean(beanName);
    }
}

        因为shiro有点小bug,盐值无法序列化问题,其默认的SimpleByteSource没有实现序列化接口,所以导致从redis拿出数据后,无法反序列化的问题,所以需要一个类来替代MyByteSource,这个很重要,其做法就是用一个类实现ByteSource,再实现Serializable ,其实现的代码复制SimpleByteSource,把类名改为MyByteSource,再加一个空参构造方法,实现序列化和反序列化

package com.lingaolu.shiro;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.codec.Hex;
import org.apache.shiro.util.ByteSource;
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;

/**
 * 解决:
 *  shiro 使用缓存时出现:java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource
 *  序列化后,无法反序列化的问题
 */
public class MyByteSource implements ByteSource, Serializable {

    private  byte[] bytes;
    private String cachedHex;
    private String cachedBase64;

    public MyByteSource(){
    }

    public MyByteSource(byte[] bytes) {
        this.bytes = bytes;
    }

    public MyByteSource(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    }

    public MyByteSource(String string) {
        this.bytes = CodecSupport.toBytes(string);
    }

    public MyByteSource(ByteSource source) {
        this.bytes = source.getBytes();
    }

    public MyByteSource(File file) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
    }

    public MyByteSource(InputStream stream) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);
    }

    public static boolean isCompatible(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    @Override
    public byte[] getBytes() {
        return this.bytes;
    }


    @Override
    public String toHex() {
        if(this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }
        return this.cachedHex;
    }

    @Override
    public String toBase64() {
        if(this.cachedBase64 == null) {
            this.cachedBase64 = Base64.encodeToString(this.getBytes());
        }

        return this.cachedBase64;
    }

    @Override
    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }
    @Override
    public String toString() {
        return this.toBase64();
    }

    @Override
    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0? Arrays.hashCode(this.bytes):0;
    }

    @Override
    public boolean equals(Object o) {
        if(o == this) {
            return true;
        } else if(o instanceof ByteSource) {
            ByteSource bs = (ByteSource)o;
            return Arrays.equals(this.getBytes(), bs.getBytes());
        } else {
            return false;
        }
    }

    private static final class BytesHelper extends CodecSupport {
        private BytesHelper() {
        }

        public byte[] getBytes(File file) {
            return this.toBytes(file);
        }

        public byte[] getBytes(InputStream stream) {
            return this.toBytes(stream);
        }
    }

}

        有了自定义的MyByteSource后,其创建SimpleAuthenticationInfo的参数中,盐的传参也要改为new MyByteSource(盐),所以自定义Realm类CustomerRealm为

package com.lingaolu.shiro;

import com.lingaolu.bean.Perms;
import com.lingaolu.bean.Role;
import com.lingaolu.bean.User;
import com.lingaolu.servive.UserService;
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.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import java.util.List;

public class CustomerRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    // 重写授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权--------------------------");
        // 获取身份信息
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        // 获取用户的角色信息
        User user = userService.finaRolesByUserName(primaryPrincipal);
        // 这个user一定是有的,因为下面都能认证 过来了,所以只需要判断角色是否为空
        if(!CollectionUtils.isEmpty(user.getRoles())){
            // 授权角色信息
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            user.getRoles().forEach(role->{
                simpleAuthorizationInfo.addRole(role.getName());
                // 权限信息
                List<Perms> permsList = userService.findPermsByRoleId(role.getId());
                if(!CollectionUtils.isEmpty(permsList)){
                    permsList.forEach(perms->{
                        simpleAuthorizationInfo.addStringPermission(perms.getName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    }

    // 重写认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String)authenticationToken.getPrincipal();
        User user = userService.findByUserName(principal);
        if(!ObjectUtils.isEmpty(user)){
            return new SimpleAuthenticationInfo(principal,user.getPassWord(),new MyByteSource(user.getSalt()),this.getName());
        }
        return null;
    }
}

        还没玩,我们自定义了缓存管理器还没用呢,用在shiro开启缓存管理时设置缓存管理器的时候,参数的传参customerRealm.setCacheManager(new MyRedisCacheManager());,所以Shiro的配置类为

package com.lingaolu.config;

import com.lingaolu.shiro.CustomerRealm;
import com.lingaolu.shiro.MyRedisCacheManager;
import com.lingaolu.utils.SaltUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //  1.创建shiroFilter
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean filterBean = new ShiroFilterFactoryBean();
        // 给filter设置安全管理器
        filterBean.setSecurityManager(securityManager);
        // 配置系统的受限资源和公共资源
        Map<String,String> map = new HashMap<>();
        // anon表示放行
        map.put("/user/login","anon");
        map.put("/user/register","anon");
        map.put("/register.jsp","anon");
        // authc表示拦截
        map.put("/**","authc");
        filterBean.setFilterChainDefinitionMap(map);
        // 默认认证界面路径
        filterBean.setLoginUrl("/login.jsp");
        return filterBean;
    }

    // 2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(CustomerRealm customerRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 给安全管理器 设置Realm
        securityManager.setRealm(customerRealm);
        return securityManager;
    }

    // 3、创建自定义realm
    @Bean
    public CustomerRealm getCustomerRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        // 获取凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置算法名字,md5
        credentialsMatcher.setHashAlgorithmName(SaltUtils.getHASHALGORITHMNAME());
        // 散列次数,1024
        credentialsMatcher.setHashIterations(SaltUtils.getHASHITERATIONS());
        // 设置realm使用hash凭证匹配器
        customerRealm.setCredentialsMatcher(credentialsMatcher);


        // 开启缓存管理
        customerRealm.setCacheManager(new MyRedisCacheManager());
        // 开启全局缓存
        customerRealm.setCachingEnabled(true);
        // 开启认证缓存,设置认证缓存名
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("authenticationCache");
        // 开启授权缓存,设置授权缓存名
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCacheName("authorizationCache");


        return customerRealm;
    }
}

5:springBoot+shiro+thymeleaf

        本章是相对于第四章springBoot+shiro+jsp来说的,springBoot+shiro+thymeleaf和第四节的springBoot+shiro+jsp差不多,只不过需要 把jsp换为thymeleaf而已,视图的返回按照thymeleaf方式,其整合shiro的方式是一样的,所以需要maven导入thymeleaf与SpringBoot整合的依赖thymeleaf与shiro整合的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

        然后试图路径的配置改为,不需要jsp的视图路径了

spring:
  thymeleaf:
    cache: false
    suffix: .html
  mvc:
    view:
      prefix: classpath:templates/

        为了提示th,html头文件要引入

xmlns:th="http://www.thymeleaf.org"

        为了提示shiro,html头文件要引入

xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

        另外页面标签的权限判断方式和JSP的也有差别,变成了这样的

<li shiro:hasRole="admin"><a href="">用户管理</a></li>

        其shiro:hasRole等其他标签的名字和jsp的一样,只不过是使用方式不一样而已

<!doctype html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>系统主页</h1>
    <!--获取身份信息-->
    <span shiro:principal=""></span>
    <br/>
    <span shiro:authenticated="">
        认证的数据
    </span>
    <span shiro:notAuthenticated="">
        非认证的数据
    </span>
    <ul>
        <li shiro:hasRole="admin"><a href="">用户管理</a></li>
    </ul>
</body>
</html>

特别注意

        以上权限标签的还没有效果,这也是thymeleaf和jsp的大区别,所以shiro的自定义配置类ShiroConfig上需要多额外的bean注入到容器

 @Bean(name="shiroDialect")
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

       所以相比第四章的ShiroConfig类,使用shiro+thymeleaf的ShiroConfig为

package com.lingaolu.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.lingaolu.shiro.CustomerRealm;
import com.lingaolu.shiro.MyRedisCacheManager;
import com.lingaolu.utils.SaltUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Bean(name="shiroDialect")
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }
    //  1.创建shiroFilter
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean filterBean = new ShiroFilterFactoryBean();
        // 给filter设置安全管理器
        filterBean.setSecurityManager(securityManager);
        // 配置系统的受限资源和公共资源
        Map<String,String> map = new HashMap<>();
        // anon表示放行
        map.put("/user/login","anon");
        map.put("/user/registerview","anon");
        map.put("/user/register","anon");
        // authc表示拦截
        map.put("/**","authc");
        filterBean.setFilterChainDefinitionMap(map);
        // 默认认证界面路径
        filterBean.setLoginUrl("/user/loginview");
        return filterBean;
    }

    // 2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(CustomerRealm customerRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 给安全管理器 设置Realm
        securityManager.setRealm(customerRealm);
        return securityManager;
    }

    // 3、创建自定义realm
    @Bean
    public CustomerRealm getCustomerRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        // 获取凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置算法名字,md5
        credentialsMatcher.setHashAlgorithmName(SaltUtils.getHASHALGORITHMNAME());
        // 散列次数,1024
        credentialsMatcher.setHashIterations(SaltUtils.getHASHITERATIONS());
        // 设置realm使用hash凭证匹配器
        customerRealm.setCredentialsMatcher(credentialsMatcher);


        // 开启自定义redis缓存管理
        customerRealm.setCacheManager(new MyRedisCacheManager());
        // 开启全局缓存
        customerRealm.setCachingEnabled(true);
        // 开启认证缓存,设置认证缓存名
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("authenticationCache");
        // 开启授权缓存,设置授权缓存名
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCacheName("authorizationCache");


        return customerRealm;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深知她是一场梦

你打不打赏,我都会一直写博客

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值