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的某些功能的应用?大致就是这些,如有错误,欢迎指正。
如有侵权,联系删除。