Shiro学习笔记(二)后续内容记录


上一篇:
Shiro学习笔记之Shiro与Spring Boot的结合(两种方式)

前言

本文是在b站学习Shiro时随笔记下,这个视频相比于网上一些文字教程还是比较详细,算是保姆级的教程。虽然时长较长,但对于初学还是比较推荐。

视频地址:https://www.bilibili.com/video/BV1pa4y1471s

此篇内容的例子都基于上一篇在shiro与SpringBoot整合的基础上完成。

此篇是上一篇后续部分,纯是我在看视频后的学习记录,不够专业,目前本人还在学习阶段。如有错误,欢迎指正。

一、Spring Boot应用打包部署

SpringBoot项目继承了web容器(Tomcat),所以Spring Boot应用是可以打包成jar直接运行的。

二、加密

加密规则可以自定义,在项目开发中我们通常使用BASE64和MD5编码方式

  • BASE64: 可反编码的编码方式
    • 明文–[BASE64]–密文
    • 密文–[反编码]–明文
  • MD5:不可逆的编码方式
    • 明文 ----[MD5]---- 密文
  • 如果数据库用户的密码存储的密文,Shiro该如何完成验证呢
  • 使用Shiro提供的加密功能,对输入的密码进行加密之后再进行认证

配置matcher

@Configuration
public class ShiroConfig {

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //matcher用来指定加密规则的
        //加密方式
        matcher.setHashAlgorithmName("md5");
        //加密迭代次数
        matcher.setHashIterations(1);	//此处循环次数要与用户注册时密码加密次数一致
        return matcher;
    }

    //自定义Realm
    @Bean
    public MyRealm getMyRealm(HashedCredentialsMatcher matcher) {
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(matcher);
        return myRealm;
    }
    
    
    //...


}

用户注册密码加密处理

  • regist.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
regist
<hr/>
<form action="user/regist" method="post">
    <p>账号:<input type="text" name="username"/></p>
    <p>密码:<input type="text" name="password"/></p>
    <p><input type="submit" value="提交注册"/></p>
</form>
</body>
</html>
  • UserController

    三种方式加密,1)默认加密;2)加盐加密;3)加盐加密并多次迭代

@RequestMapping("/regist")
public String regist(String username, String password) {
    System.out.println("注册");

    //注册的时候要对密码进行加密存储
    //        Md5Hash md5Hash = new Md5Hash(password);
    //        System.out.println(md5Hash.toString());

    //加盐加密
    Integer random = new Random().nextInt(90000) + 10000;
    Md5Hash md5Hash1 = new Md5Hash(password,random.toString(),1);
    System.out.println(md5Hash1.toString());

    //加盐加密+多次hash
    Md5Hash md5Hash3 = new Md5Hash(password, random.toString(),3);
    System.out.println(md5Hash3.toString());

    //SimpleHash hash = new SimpleHash("md5", password, random.toString(),3);
    //System.out.println(hash.toString());

    //将用户信息保存到数据库时,保存加密后的密码,如果生成的随机盐,保存盐

    return "login";
}

如果密码进行了加盐处理,则realm在返回认证数据时需要返回盐

在自定义realm中:

//把查询出来的安全信息放到AuthenticationInfo中
AuthenticationInfo info = new SimpleAuthenticationInfo(
    username,           //当前用户用户名
    user.getUserPwd(),  //从数据库查询出来的安全密码
    ByteSource.Util.bytes(user.getPwdSalt()),	//获取盐
    getName()           //当前Realm名
);

三、退出登录

在shiro的过滤器中配置,配置logout对应的路径。

当使用logout过滤器时,shiro会将登录的信息都清除。

//在过滤器中设置logout
filterMap.put("/exit","logout");

在页面的退出按钮上,跳转到logout

页面设置路径,这里的路径要与上面的路径相同

<a href="/exit">退出</a>

四、授权

用户登录成功之后,要进行相应的操作就需要有对应的权限;在进行操作之前对权限进行检查–授权

权限控制通常有两类做法:

  • 不同身份的用户登录,我们显示不同的操作菜单(没有权限的菜单不显示)
  • 对所有用户显示所有菜单,当用户点击菜单以后,再验证当前用户是否有此权限,如果没有则提示权限不足

1.HTML授权

在菜单页面只显示当前用户拥有权限操作的菜单

使用shiro标签

<shiro:hasPermission name="sys:c:save">
    <li><a href="#">入库</a></li>
</shiro:hasPermission>

2.过滤器授权

对需要对应权限才能访问的html在过滤器中配置一下,设置权限

filterMap.put("/c_add.html","perms[sys:c:save]");

//设置未授权访问的页面路径,当权限不足时访问此页面
filterFactoryBean.setUnauthorizedUrl("/lesspermission.html");

3.注解授权

先进行Spring对Shiro注解的支持配置:ShiroConfig.java

@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    autoProxyCreator.setProxyTargetClass(true);
    return autoProxyCreator;
}

//注解加载器
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
    advisor.setSecurityManager(securityManager);
    return advisor;
}

然后在请求的接口上进行对权限控制注解@RequiresPermissions({"sys:k:find"})

还有一种对角色控制的注解为@RequiresRoles({"admin"})

@RequestMapping("list")
@RequiresPermissions({"sys:k:find"})	//对权限的控制
//@RequiresRoles({"admin"})				//对角色的控制
public String list() {
    System.out.println("查询客户信息");
    return "customer_list";
}

通过全局异常处理,指定权限不足时的页面跳转到异常处理页面lesspermission.html

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler
    public String doException(Exception e) {
        //如果为AuthorizationException异常,则跳转到lesspermission.html
        if (e instanceof AuthorizationException) {
            return "lesspermission";
        }
        return null;
    }
}

如图,此例中我登录了lisi用户,而lisi用户为销售人员,不应该有客户管理的权限(此处我在前端页面开放了【查询客户】给所有用户,并未使用shiro的标签来控制,所以可以看到有【查询客户】这个功能,实际上这个功能也不应该开放给非对应角色,只是为了演示),当他点击【查询客户】时,跳转到了异常处理页面lesspermission.html

在这里插入图片描述

如果不加这个全局异常控制,在注解的权限控制下,对于没有权限的操作会报AuthorizationException异常,也就是页面上的500错误。

这点与过滤器控制权限不同,过滤器控制权限的报错则是401,且可以直接在ShiroConfig中配置报错后跳转的页面路径。

//设置未授权访问的页面路径
filterFactoryBean.setUnauthorizedUrl("/lesspermission.html");

4.手动授权

以下就是手动授权,其实就是在代码里判断是否拥有这个权限,基本不用。

  @RequestMapping("list")
    //如果没有sys:k:find权限,则不允许执行此方法
//    @RequiresPermissions({"sys:k:find"})
//    @RequiresRoles({"admin"})
    public String list() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isPermitted("sys:k:find")) {
            System.out.println("查询客户信息");
            return "customer_list";
        } else {
            return "lesspermission";
        }
    }

五、缓存使用

使用shiro进行权限管理过程中,每次授权都会访问Realm中的doGetAuthorizationInfo方法查询当前用户的角色及权限信息,如果系统的用户量较大则会对数据库造成比较大的压力。

Shiro支持缓存以降低对数据库的访问压力(缓存的是授权信息)

导入依赖

<!--spring boot 对缓存的支持-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--第三方缓存ehcache-->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
<!--shiro对缓存的支持-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.7.0</version>
</dependency>

配置缓存策略

在resource目录下创建一个ehcache.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
    <diskStore path="C:\TEMP"/>

    <cache name="users" timeToLiveSeconds="300" maxEntriesLocalHeap="1000"/>

    <defaultCache name="defaultCache"
                  maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  overflowToDisk="false"
                  maxElementsOnDisk="100000"
                  diskPersistent="false"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU"/>
                <!--
                    缓存淘汰策略,当缓存空间比较紧张时,我们要存储新的数据进来,就必然要删除一些老的数据
                    LRU 最近最少使用
                    FIFO 先进先出
                    LFU 最少使用
                -->
</ehcache>

加入缓存管理

ShiroConfig中加入配置

//EhCacheManager的获取
@Bean
public EhCacheManager getEhCacheManager() {
    EhCacheManager ehCacheManager = new EhCacheManager();
    ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
    return ehCacheManager;
}

//SecurityManager(安全管理器)
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm, EhCacheManager ehCacheManager) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    //securityManager要完成校验需要realm的支持
    securityManager.setRealm(myRealm);
   	//这里设置的就是上面获取到的ehCacheManager
    securityManager.setCacheManager(ehCacheManager);
    //若参数中不传ehCacheManager,使用以下方式也可
    //securityManager.setCacheManager(getEhCacheManager());
    return securityManager;
}

六、Session管理

Shiro进行认证和授权是基于session实现的,Shiro包含了对session的管理。

  • 如果我们需要对session进行管理

    • 自定义session管理器
    • 将自定义的session管理器设置给SecurityManager
  • 配置自定义SessionManger:在ShiroConfig中配置其生命周期

    //session生命周期的配置
    @Bean
    public DefaultWebSessionManager getDefaultWebSessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        System.out.println("------------" + sessionManager.getGlobalSessionTimeout()); //1800000
        //配置sessionManager
        sessionManager.setGlobalSessionTimeout(5*60*1000);
        return sessionManager;
    }
    
    //SecurityManager(安全管理器)
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm, EhCacheManager ehCacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //securityManager要完成校验需要realm的支持
        securityManager.setRealm(myRealm);
        //对缓存的配置
        securityManager.setCacheManager(ehCacheManager);
        //对session生命周期的配置
        securityManager.setSessionManager(getDefaultWebSessionManager());
        return securityManager;
    }
    

七、RememberMe

Shiro将用户对页面访问的权限分为三个级别

  • 未认证可访问的页面
    • login.html、regist.html
  • 曾认证可访问的页面(RememberMe)
    • info.html
  • 已认证可访问的页面
    • 转账.html

1.在过滤器中设置RememberMe可访问的url

其实就是在ShiroConfig中的过滤器中配置

//user 使用remenberme的用户可访问(已认证也可访问)
filterMap.put("/index.html", "user");

2.在ShiroConfig.java中配置基于cookie的rememberMe管理器

//rememberMe管理器
@Bean
public CookieRememberMeManager cookieRememberMeManager() {
    CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
    //这一步必须要给cookie设置名字
    SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
    //设置cookie的存活时间
    simpleCookie.setMaxAge(30*24*60*60);
    //将cookie设置到rememberMe管理器中
    rememberMeManager.setCookie(simpleCookie);
    return rememberMeManager;
}

//SecurityManager(安全管理器)
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm, EhCacheManager ehCacheManager) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    //securityManager要完成校验需要realm的支持
    securityManager.setRealm(myRealm);
    securityManager.setCacheManager(ehCacheManager);
    securityManager.setSessionManager(getDefaultWebSessionManager());
    //设置rememberMe管理器
    securityManager.setRememberMeManager(cookieRememberMeManager());
    return securityManager;
}

3.登录认证时设置token“记住我”

登录页面,这里加了一个单选按钮<p>记住我:<input type="radio" name="rememberMe"/></p>

<form action="user/login" method="post">
    <p>账号:<input type="text" name="username"/></p>
    <p>密码:<input type="text" name="password"/></p>
    <p>记住我:<input type="radio" name="rememberMe"/></p>
    <p><input type="submit" value="login"/></p>
</form>

控制层,这里只是加了一个boolean类型的参数rememberMe

@Autowired
private UserSeviceImpl userSevice;

@RequestMapping("login")
public String login(String username, String password, boolean rememberMe) {
    try {
        userSevice.checkLogin(username, password, rememberMe);
        System.out.println("登录成功!");
        return "index";
    } catch (Exception e) {
        System.out.println("用户名账号错误!");
        return "login";
    }
}

实现类,这里是将rememberMe设置给token:token.setRememberMe(rememberMe);

@Service
public class UserSeviceImpl implements UserService{

    @Override
    public void checkLogin(String username, String password, boolean rememberMe) throws Exception {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //在token传给认证器之前在他里面设置一个rememberMe
        token.setRememberMe(rememberMe);
        subject.login(token);
    }
}

在这里插入图片描述

在浏览器的控制台中我们可以看到有rememberMe这个cookie,也就是我们在ShiroConfig中设置的cookie名称。

总结

此篇文章算是一些shiro的某些功能的应用?大致就是这些,如有错误,欢迎指正。

如有侵权,联系删除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值