Shiro学习笔记
一、主流框架简介
1.1 Shiro与SpringSecurity对比
shiro:
- Shrio学习比较简单,可以用简单的操作实现复杂的权限管理
- 与Spring Security相比,Shiro更加主流、也更加简单易用,它不但是适用于javaSE环境,也适用于javaEE环境
- 易于理解的 Java Security API;
- 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);
- 对角色的简单的签权(访问控制),支持细粒度的签权;
- 支持一级缓存,以提升应用程序的性能;
- 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
- 异构客户端会话访问;
- 非常简单的加密 API;
- 不跟任何的框架或者容器捆绑,可以独立运行。
SpringSecurity
- 充分利用了Spring IoC,DI和AOP功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
区别
- hiro比Spring更容易使用,实现和最重要的理解
- Spring Security更加知名的唯一原因是因为品牌名称
- “Spring”以简单而闻名,但讽刺的是很多人发现安装Spring Security很难,然而,Spring Security却有更好的社区支持
- Apache Shiro在Spring Security处理密码学方面有一个额外的模块
- Spring-security 对spring 结合较好,如果项目用的springmvc ,使用起来很方便。但是如果项目中没有用到spring,那就不要考虑它了。
- Shiro 功能强大、且 简单、灵活。是Apache 下的项目比较可靠,且不跟任何的框架或者容器绑定,可以独立运行
二、Shiro简介
Shiro是apache下的一个开源框架,将软件系统的安全认证相关的功能抽取出来,实现用户身份认证、权限授权、加密、会话管理等功能。
2.1核心架构

2.2 认证
身份认证:判断一个用户是否为合法用户的处理过程。
2.2.1认证的关键对象
- Subject 主体:访问系统的用户。进行认证的都是主体。
- Principal 身份信息:主体进行身份认证的标识,必须具有唯一性。一个主体可以有多个身份,但是只能有一个主身份。
- credential 凭证信息:只有主体自己知道的安全信息,如密码,证书等。
2.2.2 认证的开发
- 引入依赖
1 |
|
- 引入配置
配置文件为 .ini文件,可以写一些复杂数据格式
2.2.3 quickstart
1 |
|
2.2.4 认证流程源码查看
用户名校验:SimpleAccountRealm中的doGetAuthenticationInfo方法根据token完成校验。封装得非常深
密码校验:AuthenticatingRealm中的assertCredentialMatch。由Shiro自动校验。
AuthenticatingRealm 用来认证Realm->doGetAuthenticationInfo
AuthorizingRealm 用来授权Realm->doGetAuthorizationInfo
SimpleAccountRealm有doGetAuthenticationInfo和doGetAuthorizationInfo两个方法,继承了AuthorizingRealm。
如果我们需要自己设计认证和授权的方式,自定义Realm,只需要继承AuthorizingRealm然后重写两个方法就行了。
1 |
|
三、Shiro工作原理
- Anthentication 认证,验证用户是否有相应的身份—登录认证;
- Authorization 授权,即权限验证;对已经通过认证的用户检查是否具有某个权限或者角色,从而控制是否能够进行某种操作;
- Session Managment 会话管理,用户在认证成功之后创建会话,在没有退出之前,当前用户的所有信息都会保存在这个会话中;可以是普通的JavaSE应用,也可以是web应用;
- Cryptography 加密,对敏感信息进行加密处理,shiro就提供这种加密机制;
- 支持的特性:
- Web Support — Shiro提供了过滤器,可以通过过滤器拦截web请求来处理web应用的访问控制
- Caching 缓存支持,shiro可以缓存用户信息以及用户的角色权限信息,可以提高执行效率
- Concurrency shiro支持多线程应用
- Testing 提供测试支持
- Run As 允许一个用户以另一种身份去访问
- Remeber Me
- 说明:Shiro是一个安全框架,不提供用户、权限的维护(用户的权限管理需要我们自己去设计)
四、Shiro整合Springboot
- JavaSE应用中使用
- web应用中使用
- SSM整合Shiro(配置多,用的少)
- SpringBoot应用整合Shiro
4.1 创建SpringBoot应用
- lombok
- spring web
- thymeleaf
4.2 整合Druid和MyBatis
依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<!-- druid starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>配置
1
2
3
4
5
6
7
8
9
10
11
12
13spring:
datasource:
druid:
url: jdbc:mysql://localhost:33006/tmp
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
initial-size: 1
min-idle: 1
max-active: 20
mybatis:
mapper-locations: classpath:mappers/*Mapper.xml
type-aliases-package: top.retain.shirodemo.beans
2.3 整合Shiro
导入依赖
1
2
3
4
5<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>Shiro配置(java配置方式)
- SpringBoot默认没有提供对Shiro的自动配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47@Configuration
public class ShiroConfig {
@Bean
public IniRealm getIniRealm(){
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
return iniRealm;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(IniRealm iniRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//securityManager要完成校验,需要realm
securityManager.setRealm(iniRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
//过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的
filter.setSecurityManager(securityManager);
//设置shiro的拦截规则
// anon 匿名用户可访问
// authc 认证用户可访问
// user 使用RemeberMe的用户可访问
// perms 对应权限可访问
// role 对应的角色可访问
Map<String,String> filterMap = new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/login.html","anon");
filterMap.put("/regist.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/regist","anon");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filter.setFilterChainDefinitionMap(filterMap);
filter.setLoginUrl("/login.html");
//设置未授权访问的页面路径
filter.setUnauthorizedUrl("/login.html");
return filter;
}
}认证测试
- UserServiceImpl.java
1
2
3
4
5
6
7
8
9
10@Service
public class UserServiceImpl {
public void checkLogin(String userName,String userPwd) throws Exception{
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
subject.login(token);
}
}- UserController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20@Controller
@RequestMapping("user")
public class UserController {
@Resource
private UserServiceImpl userService;
@RequestMapping("login")
public String login(String userName,String userPwd){
try {
userService.checkLogin(userName,userPwd);
System.out.println("------登录成功!");
return "index";
} catch (Exception e) {
System.out.println("------登录失败!");
return "login";
}
}
}login.html
1 |
|
4.2 SpringBoot整合JdbcRealm
4.2.1 JdbcRealm规定的表结构
用户信息表: users
1
2
3
4
5
6
7
8
9
10
11
12create table users(
id int primary key auto_increment,
username varchar(60) not null unique,
password varchar(20) not null,
password_salt varchar(20)
);
insert into users(username,password) values('zhangsan','123456');
insert into users(username,password) values('lisi','123456');
insert into users(username,password) values('wangwu','123456');
insert into users(username,password) values('zhaoliu','123456');
insert into users(username,password) values('chenqi','123456');角色信息表: user_roles
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17create table user_roles(
id int primary key auto_increment,
username varchar(60) not null,
role_name varchar(100) not null
);
-- admin系统管理员
-- cmanager 库管人员
-- xmanager 销售人员
-- kmanager 客服人员
-- zmanager 行政人员
insert into user_roles(username,role_name) values('zhangsan','admin');
insert into user_roles(username,role_name) values('lisi','cmanager');
insert into user_roles(username,role_name) values('wangwu','xmanager');
insert into user_roles(username,role_name) values('zhaoliu','kmanager');
insert into user_roles(username,role_name) values('chenqi','zmanager');权限信息表:roles_permissions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30create table roles_permissions(
id int primary key auto_increment,
role_name varchar(100) not null,
permission varchar(100) not null
);
-- 权限 sys:c:save sys:c:delete...
-- 管理员具备所有权限
insert into roles_permissions(role_name,permission) values("admin","*");
-- 库管人员
insert into roles_permissions(role_name,permission) values("cmanager","sys:c:save");
insert into roles_permissions(role_name,permission) values("cmanager","sys:c:delete");
insert into roles_permissions(role_name,permission) values("cmanager","sys:c:update");
insert into roles_permissions(role_name,permission) values("cmanager","sys:c:find");
-- 销售人员
insert into roles_permissions(role_name,permission) values("xmanager","sys:c:find");
insert into roles_permissions(role_name,permission) values("xmanager","sys:x:save");
insert into roles_permissions(role_name,permission) values("xmanager","sys:x:delete");
insert into roles_permissions(role_name,permission) values("xmanager","sys:x:update");
insert into roles_permissions(role_name,permission) values("xmanager","sys:x:find");
insert into roles_permissions(role_name,permission) values("xmanager","sys:k:save");
insert into roles_permissions(role_name,permission) values("xmanager","sys:k:delete");
insert into roles_permissions(role_name,permission) values("xmanager","sys:k:update");
insert into roles_permissions(role_name,permission) values("xmanager","sys:k:find");
-- 客服人员
insert into roles_permissions(role_name,permission) values("kmanager","sys:k:find");
insert into roles_permissions(role_name,permission) values("kmanager","sys:k:update");
-- 新增人员
insert into roles_permissions(role_name,permission) values("zmanager","sys:*:find");
4.2.2 SpringBoot整合Shiro
创建SpringBoot应用
整合Druid和MyBatis
整合shiro
添加依赖
1
2
3
4
5<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>配置Shiro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43@Configuration
public class ShiroConfig {
@Bean
public JdbcRealm getJdbcRealm(DataSource dataSource){
JdbcRealm jdbcRealm = new JdbcRealm();
//JdbcRealm会自行从数据库查询用户及权限数据(数据库的表结构要符合JdbcRealm的规范)
jdbcRealm.setDataSource(dataSource);
//JdbcRealm默认开启认证功能,需要手动开启授权功能
jdbcRealm.setPermissionsLookupEnabled(true);
return jdbcRealm;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(JdbcRealm jdbcRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(jdbcRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
//过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的
filter.setSecurityManager(securityManager);
Map<String,String> filterMap = new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/login","anon");
filterMap.put("/register","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/register","anon");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filter.setFilterChainDefinitionMap(filterMap);
filter.setLoginUrl("/login.html");
//设置未授权访问的页面路径
filter.setUnauthorizedUrl("/login.html");
return filter;
}
}
4.3 自定义Realm
使用JdbcRealm可以完成用户权限管理,但是我们必须提供JdbcRealm规定的数据表结构;如果在我们的项目开发中 ,这个JdbcRealm规定的数据表结构不能满足开发需求,该如何处理呢?
- 自定义数据库表结构
- 自定义Realm实现认证和授权
4.3.1 数据库设计
RBAC基于角色的访问控制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94-- 用户信息表
create table tb_users(
user_id int primary key auto_increment,
username varchar(60) not null unique,
password varchar(20) not null,
password_salt varchar(60)
);
insert into tb_users(username,password) values('zhangsan','123456');
insert into tb_users(username,password) values('lisi','123456');
insert into tb_users(username,password) values('wangwu','123456');
insert into tb_users(username,password) values('zhaoliu','123456');
insert into tb_users(username,password) values('chenqi','123456');
-- 角色信息表
create table tb_roles(
role_id int primary key auto_increment,
role_name varchar(60) not null
);
insert into tb_roles(role_name) values('admin');
insert into tb_roles(role_name) values('cmanager'); -- 仓管
insert into tb_roles(role_name) values('xmanager'); -- 销售
insert into tb_roles(role_name) values('kmanager'); -- 客服
insert into tb_roles(role_name) values('zmanager'); -- 行政
-- 权限信息表
create table tb_permissions(
permission_id int primary key auto_increment, -- 1
permission_code varchar(60) not null, -- sys:c:find
permission_name varchar(60) -- 仓库查询
);
insert into tb_permissions(permission_code,permission_name) values('sys:c:save','入库');
insert into tb_permissions(permission_code,permission_name) values('sys:c:delete','出库');
insert into tb_permissions(permission_code,permission_name) values('sys:c:update','修改');
insert into tb_permissions(permission_code,permission_name) values('sys:c:find','查询');
insert into tb_permissions(permission_code,permission_name) values('sys:x:save','新增订单');
insert into tb_permissions(permission_code,permission_name) values('sys:x:delete','删除订单');
insert into tb_permissions(permission_code,permission_name) values('sys:x:update','修改订单');
insert into tb_permissions(permission_code,permission_name) values('sys:x:find','查询订单');
insert into tb_permissions(permission_code,permission_name) values('sys:k:save','新增客户');
insert into tb_permissions(permission_code,permission_name) values('sys:k:delete','删除客户');
insert into tb_permissions(permission_code,permission_name) values('sys:k:update','修改客户');
insert into tb_permissions(permission_code,permission_name) values('sys:k:find','查询客户');
-- 用户角色表
create table tb_urs(
uid int not null,
rid int not null
-- primary key(uid,rid),
-- constraint FK_user foreign key(uid) references tb_users(user_id),
-- constraint FK_role foreign key(rid) references tb_roles(role_id)
);
insert into tb_urs(uid,rid) values(1,1);
insert into tb_urs(uid,rid) values(1,2);
insert into tb_urs(uid,rid) values(1,3);
insert into tb_urs(uid,rid) values(1,4);
insert into tb_urs(uid,rid) values(1,5);
insert into tb_urs(uid,rid) values(2,2);
insert into tb_urs(uid,rid) values(3,3);
insert into tb_urs(uid,rid) values(4,4);
insert into tb_urs(uid,rid) values(5,5);
-- 角色权限表
create table tb_rps(
rid int not null,
pid int not null
);
-- 给仓管角色分配权限
insert into tb_rps(rid,pid) values(2,1);
insert into tb_rps(rid,pid) values(2,2);
insert into tb_rps(rid,pid) values(2,3);
insert into tb_rps(rid,pid) values(2,4);
-- 给销售角色分配权限
insert into tb_rps(rid,pid) values(3,4);
insert into tb_rps(rid,pid) values(3,5);
insert into tb_rps(rid,pid) values(3,6);
insert into tb_rps(rid,pid) values(3,7);
insert into tb_rps(rid,pid) values(3,8);
insert into tb_rps(rid,pid) values(3,9);
insert into tb_rps(rid,pid) values(3,10);
insert into tb_rps(rid,pid) values(3,11);
insert into tb_rps(rid,pid) values(3,12);
-- 给客服角色分配权限
insert into tb_rps(rid,pid) values(4,11);
insert into tb_rps(rid,pid) values(4,12);
-- 给行政角色分配权限
insert into tb_rps(rid,pid) values(5,4);
insert into tb_rps(rid,pid) values(5,8);
insert into tb_rps(rid,pid) values(5,12);
4.3.2 DAO实现
Shiro进行认证需要用户信息:
- 根据用户名查询用户信息
Shiro进行授权管理需要当前用户的角色和权限
根据用户名查询当前用户的角色列表(3张表连接查询)
根据用户名查询当前用户的权限列表(5张表连接查询)
4.3.2.1 创建SpringBoot项目,整合MyBatis
4.3.2.2 根据用户名查询用户信息
- 创建BeanBean
1 |
|
- 创建DAO
1 |
|
- 映射配置
1 |
|
4.3.2.3 根据用户名查询角色名列表
- 创建DAO
1 |
|
- 映射配置
1 |
|
4.3.2.4 根据用户名查询权限列表
- 创建DAO
1 |
|
- 映射配置
1 |
|
4.3.3 整合Shiro
- 导入依赖
1 |
|
- 配置Shiro-基于Java配置方式
1 |
|
- 自定义Realm
1 |
|
五、Shiro标签的使用
当用户认证进入到主页面之后,需要显示用户信息及当前用户的权限信息;Shiro就提供了一套标签用于在页面来进行权限数据的呈现
Shiro提供了可供JSP使用的标签以及Thymeleaf中标签
JSP页面中引用:
1
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
Thymeleaf模版中引用:
- 在pom.xml文件中导入thymeleaf模版对shiro标签支持的依赖
1
2
3
4
5<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>- 在ShiroConfig中配置Shiro的
1
2
3
4
5
6
7
8
9@Configuration
public class ShiroConfig {
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
//...
}- Thymeleaf模版中引入shiro的命名空间
1
2
3
4<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
...
</html>
5.1 常用标签
guest,判断用户是否是游客身份,如果是游客身份则显示此标签内容
1
2
3<shiro:guest>
欢迎游客访问,<a href="login.html">登录</a>
</shiro:guest>user,判断用户是否是认证身份,如果是认证身份则显示此标签内容
principal,获取当前登录用户名
1
2
3<shiro:user>
用户[<shiro:principal/>]欢迎您!
</shiro:user>notAuthenticated/authenticated
hasRole
hasPermission
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
index
<hr/>
<shiro:guest>
欢迎游客访问,<a href="login.html">登录</a>
</shiro:guest>
<shiro:user>
用户[<shiro:principal/>]欢迎您!
当前用户为<shiro:hasRole name="admin">超级管理员</shiro:hasRole>
<shiro:hasRole name="cmanager">仓管人员</shiro:hasRole>
<shiro:hasRole name="xmanager">销售人员</shiro:hasRole>
<shiro:hasRole name="kmanager">客服人员</shiro:hasRole>
<shiro:hasRole name="zmanager">行政人员</shiro:hasRole>
</shiro:user>
<hr/>
仓库管理
<ul>
<shiro:hasPermission name="sys:c:save"><li><a href="#">入库</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:c:delete"><li><a href="#">出库</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:c:update"><li><a href="#">修改</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:c:find"><li><a href="#">查询</a></li></shiro:hasPermission>
</ul>
订单管理
<ul>
<shiro:hasPermission name="sys:x:save"><li><a href="#">添加订单</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:x:delete"><li><a href="#">删除订单</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:x:update"><li><a href="#">修改订单</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:x:find"><li><a href="#">查询订单</a></li></shiro:hasPermission>
</ul>
客户管理
<ul>
<shiro:hasPermission name="sys:k:save"><li><a href="#">添加客户</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:k:delete"><li><a href="#">删除客户</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:k:update"><li><a href="#">修改客户</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:k:find"><li><a href="#">查询客户</a></li></shiro:hasPermission>
</ul>
</body>
</html>
六、密码加密加盐
- 明文-----(加密规则)-----密文
- 加密规则可以自定义,在项目开发中我们通常使用BASE64和MD5编码方式
- BASE64:可反编码的编码方式(对称)
- 明文----密文
- 密文----明文
- MD5: 不可逆的编码方式(非对称)
- 明文----密文
- 如果数据库用户的密码存储的密文,Shiro该如何完成验证呢?
- 使用Shiro提供的加密功能,对输入的密码进行加密之后再进行认证。
6.1 Shiro使用加密认证
配置matcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26@Configuration
public class ShiroConfig {
//...
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//matcher就是用来指定加密规则
//加密方式
matcher.setHashAlgorithmName("md5");
//hash次数
matcher.setHashIterations(1); //此处的循环次数要与用户注册是密码加密次数一致
return matcher;
}
//自定义Realm
@Bean
public MyRealm getMyRealm( HashedCredentialsMatcher matcher ){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(matcher);
return myRealm;
}
//...
}
6.2 用户注册密码加密处理
registh.html
1
2
3
4
5<form action="/user/regist" method="post">
<p>帐号:<input type="text" name="userName"/></p>
<p>密码:<input type="text" name="userPwd"/></p>
<p><input type="submit" value="提交注册"/></p>
</form>UserController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35@Controller
@RequestMapping("user")
public class UserController {
@Resource
private UserServiceImpl userService;
@RequestMapping("/regist")
public String regist(String userName,String userPwd) {
System.out.println("------注册");
//注册的时候要对密码进行加密存储
Md5Hash md5Hash = new Md5Hash(userPwd);
System.out.println("--->>>"+ md5Hash.toHex());
//加盐加密
int num = new Random().nextInt(90000)+10000; //10000—99999
String salt = num+"";
Md5Hash md5Hash2 = new Md5Hash(userPwd,salt);
System.out.println("--->>>"+md5Hash2);
//加盐加密+多次hash
Md5Hash md5Hash3 = new Md5Hash(userPwd,salt,3);
System.out.println("--->>>"+md5Hash3);
//SimpleHash hash = new SimpleHash("md5",userPwd,num,3);
//将用户信息保存到数据库时,保存加密后的密码,如果生成的随机盐,盐也要保存
return "login";
}
}
6.3 如果密码进行了加盐处理,则Realm在返回认证数据时需要返回盐
在自定义Realm中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public class MyRealm extends AuthorizingRealm {
@Resource
private UserDAO userDAO;
@Resource
private RoleDAO roleDAO;
@Resource
private PermissionDAO permissionDAO;
public String getName() {
return "myRealm";
}
/**
* 获取认证的安全数据(从数据库查询的用户的正确数据)
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//参数authenticationToken就是传递的 subject.login(token)
// 从token中获取用户名
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
//根据用户名,从数据库查询当前用户的安全数据
User user = userDAO.queryUserByUsername(username);
// AuthenticationInfo info = new SimpleAuthenticationInfo(
// username, //当前用户用户名
// user.getUserPwd(), //从数据库查询出来的安全密码
// getName());
//如果数据库中用户的密码是加了盐的
AuthenticationInfo info = new SimpleAuthenticationInfo(
username, //当前用户用户名
user.getUserPwd(), //从数据库查询出来的安全密码
ByteSource.Util.bytes(user.getPwdSalt()),
getName());
return info;
}
}
七、退出登录
在Shiro过滤器中进行配置,配置logout对应的路径
1
filterMap.put("/exit","logout");
在页面的“退出”按钮上,跳转到logout对应的url
1
<a href="exit">退出</a>
八、授权
用户登录成功之后,要进行响应的操作就需要有对应的权限;在进行操作之前对权限进行检查—授权
权限控制通常有两类做法:
- 不同身份的用户登录,我们现在不同的操作菜单(没有权限的菜单不现实)
- 对所有用户显示所有菜单,当用户点击菜单以后再验证当前用户是否有此权限,如果没有则提示权限不足
8.1 HTML授权
在菜单页面只显示当前用户拥有权限操作的菜单
shiro标签
1
2
3<shiro:hasPermission name="sys:c:save">
<dd><a href="javascript:;">入库</a></dd>
</shiro:hasPermission>但是如果用户直接访问路径,还是可以访问到页面,权限控制不够完整,因此需要添加过滤器授权
8.2 过滤器授权
在shiro过滤器中对请求的url进行权限设置
1
2
3
4filterMap.put("/c_add.html","perms[sys:c:save]");
//设置未授权访问的页面路径—当权限不足时显示此页面
filter.setUnauthorizedUrl("/lesspermission.html");
8.3 注解授权
配置Spring对Shiro注解的支持:ShiroConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* 配置shiro的注解能够得到加载和执行
* @return
*/
@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);
// 注解的加载器,让shiro解析@RequirePermissions等注解,Spring无法解析
return advisor;
}在请求的控制器添加权限注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14@Controller
@RequestMapping("customer")
public class CustomerController {
@RequestMapping("list")
//如果没有 sys:k:find 权限,则不允许执行此方法
@RequiresPermissions("sys:k:find")
// @RequiresRoles("")
public String list(){
System.out.println("----------->查询客户信息");
return "customer_list";
}
}通过全局异常处理,指定权限不足时的页面跳转
1
2
3
4
5
6
7
8
9
10
11
12@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public String doException(Exception e){
if(e instanceof AuthorizationException){
return "lesspermission";
}
return null;
}
}
8.3.1 手动授权
在代码中进行手动的权限校验
1
2
3
4
5
6
7Subject subject = SecurityUtils.getSubject();
if(subject.isPermitted("sys:k:find")){
System.out.println("----------->查询客户信息");
return "customer_list";
}else{
return "lesspermission";
}
九、缓存使用
使用Shiro进行权限管理过程中,每次授权都会访问realm中的doGetAuthorizationInfo方法查询当前用户的角色及权限信息,如果系统的用户量比较大则会对数据库造成比较大的压力
Shiro支持缓存以降低对数据库的访问压力(缓存的是授权信息)
9.1 导入依赖
1 |
|
9.2 配置缓存策略
- 在resources目录下创建一个xml文件(ehcache.xml)
1 |
|
9.3 加入缓存管理
- ShiroConfig.java
1 |
|
十、session管理
Shiro进行认证和授权是基于session实现的,Shiro包含了对session的管理
如果我们需要对session进行管理
- 自定义session管理器
- 将自定义的session管理器设置给SecurityManager
配置自定义SessionManager:ShiroConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17@Bean
public DefaultWebSessionManager getDefaultWebSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
System.out.println("----------"+sessionManager.getGlobalSessionTimeout()); // 1800000
//配置sessionManager
sessionManager.setGlobalSessionTimeout(5*60*1000);
return sessionManager;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(getEhCacheManager());
securityManager.setSessionManager(getDefaultWebSessionManager());
return securityManager;
}
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!