Shiro 一款简单易用,功能强大的安全框架,帮助我们安全高效的构建企业级应用。之前几个项目都用到过 Shiro,最近抽空梳理了一下,分享一些经验。
本文demo:戳这里
本文demo选型:thymeleaf, springboot 2, shiro, ehcache
PS:如果我不拖延的话,估计还是会有后续的 :)
Shiro 能做什么
- 认证:登录用户的认证
- 权限:基于角色和权限的访问权限(url权限),以及颗粒化权限控制(按钮权限)
- 加密技术:Shiro的crypto包中包含了一系列的易于理解和使用的加密、哈希(aka摘要)辅助类
- session管理:可在web容器以及 EJB容器中使用 session,可扩展 (例如我们可以通过重写 sessionDao 将 session 存储到数据库中)
- RememberMe:基于cookie的记住我服务
Shiro 常用组件介绍
Subject:Subject其实代表的就是当前正在执行操作的用户,只不过因为“User”一般指代人,但是一个“Subject”可以是人,也可以是任何的第三方系统,服务账号等任何其他正在和当前系统交互的第三方软件系统。
所有的Subject实例都被绑定到一个SecurityManager,如果你和一个Subject交互,所有的交互动作都会被转换成Subject与SecurityManager的交互SecurityManager:Shiro的核心,他主要用于协调Shiro内部各种安全组件,不过我们一般不用太关心SecurityManager,对于应用程序开发者来说,主要还是使用Subject的API来处理各种安全验证逻辑
Realm:这是用于连接Shiro和客户系统的用户数据的桥梁。一旦Shiro真正需要访问各种安全相关的数据(比如使用用户账户来做用户身份验证以及权限验证)时,他总是通过调用系统配置的各种Realm来读取数据
Shiro 是如何工作的
简单来讲的话,在Spring项目中
- Shiro 会将他的所有组件注册到 SecurityManager中
- 再通过将 SecurityManager 注册到 ShiroFilterFactoryBean(这个类实现了Spring 的BeanPostProcessor会预先加载) 中,
- 最后以 filter 的形式注册到Spring容器(实现了Spring的FactoryBean,构造一个 filter 注册到 Spring 容器中),实现用户权限的管理。
Shiro 如何集成
shiro 所需依赖,完整见demo源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<!-- 基于thymeleaf的shiro扩展 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>ShiroConfig
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public class ShiroConfig {
private static final Logger log = LoggerFactory.getLogger(ShiroConfig.class);
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
Map<String, String> chainDefinition = new LinkedHashMap<>();
// 静态资源与登录请求不拦截
chainDefinition.put("/js/**", "anon");
chainDefinition.put("/css/**", "anon");
chainDefinition.put("/img/**", "anon");
chainDefinition.put("/layui/**", "anon");
chainDefinition.put("/login", "anon");
chainDefinition.put("/login.html", "anon");
// 用户为授权通过认证 && 包含'admin'角色
chainDefinition.put("/admin/**", "authc, roles[super_admin]");
// 用户为授权通过认证或者RememberMe && 包含'document:read'权限
chainDefinition.put("/docs/**", "user, perms[document:read]");
// 用户访问所有请求 授权通过 || RememberMe
chainDefinition.put("/**", "user");
shiroFilter.setFilterChainDefinitionMap(chainDefinition);
// 当 用户身份失效时重定向到 loginUrl
shiroFilter.setLoginUrl("/login.html");
// 用户登录后默认重定向请求
shiroFilter.setSuccessUrl("/index.html");
return shiroFilter;
}
public Realm realm() {
ShiroRealm realm = new ShiroRealm();
realm.setCredentialsMatcher(credentialsMatcher());
realm.setCacheManager(ehCacheManager());
return realm;
}
public CacheManager ehCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}
public CredentialsMatcher credentialsMatcher() {
AuthCredentialsMatcher credentialsMatcher = new AuthCredentialsMatcher(ehCacheManager());
credentialsMatcher.setHashAlgorithmName(AuthCredentialsMatcher.HASH_ALGORITHM_NAME);
credentialsMatcher.setHashIterations(AuthCredentialsMatcher.HASH_ITERATIONS);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
public DefaultWebSecurityManager securityManager() {
log.debug("--------------shiro已经加载----------------");
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setCacheManager(ehCacheManager());
manager.setRealm(realm());
manager.setRememberMeManager(rememberMeManager());
return manager;
}
public RememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
public SimpleCookie rememberMeCookie(){
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
/**
* Shiro生命周期处理器:
* 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
* 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
*/
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 启用shrio授权注解拦截方式,AOP式方法级权限检查
*/
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* thymeleaf的shiro扩展
*
* @return
*/
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
以上基本是Spring项目集成 Shiro 的通用配置,下面针对上述的几个Bean 聊一聊
1. ShiroFilterFactoryBean:用于定义 请求的拦截规则, Shiro为我们默认提供了一些选项,常用如下
anon
: 请求不拦截authc
: 要求用户必须认证通过user
: 要求用户为记住我状态roles[xxx]
: 要求用户必须满足 xxx 角色perms[xxx]
: 要求用户必须满足 xxx 权限
其实上述每一个都对应了一个 Shiro 过滤器
Filter Name | Class |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter |
noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port| org.apache.shiro.web.filter.authz.PortFilter
rest| org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles| org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl| org.apache.shiro.web.filter.authz.SslFilter
user| org.apache.shiro.web.filter.authc.UserFilter
- 我们也可以自定义 过滤器来实现拦截
2. Realm:上面提到过Realm是用于连接Shiro和客户系统的用户数据的桥梁, 我们通过实现AuthorizingRealm
来提供用户认证和授权两个API
1 | public class ShiroRealm extends AuthorizingRealm { |
doGetAuthenticationInfo : 认证方法,在执行 subject.login(token);后,Shiro认证器会读取 Realm 中的该方法获取
AuthenticationInfo
对象(认证信息),包含principal(我们存储在shiro subject中的对象),credentials (密码)。doGetAuthorizationInfo: 授权方法,在需要校验用户访问权限的时候,Shiro授权器会读取 Realm 中的该方法获取
AuthorizationInfo
对象(授权信息)读取DB后,可以通过addRoles(roleCollection)
和addStringPermissions(permCollection)
设置当前用户的角色和权限。Shiro 在拿到这个权限信息后,会去找缓存管理器,以当前 subject 的 principal 作为key 缓存起来。
3. CredentialsMatcher: 密码匹配器,用于匹配 doGetAuthenticationInfo 方法返回的 credentials 和 subject.login(token);时的 token 中的 password是否一致。常用的实现有 SimpleCredentialsMatcher(默认是该实现)、HashedCredentialsMatcher (该实现可以进行加密匹配)
4. DefaultWebSecurityManager:如上述,用于协调Shiro内部各种安全组件,我们需要将我们扩展的bean 注册到 SecurityManager 中
5. RememberMeManager:开启该组件后使用记住我服务, token 中 rememberMe 为 true 时,登录成功之后会创建RememberMe cookie。
其余参考上文代码注释
关于 thymeleaf-extras-shiro
Shiro 默认支持在 jsp 中使用 shiro标签。但是想在 thymeleaf 中使用 Shiro 标签呢?
使用 thymeleaf-extras-shiro 完美解决 thymeleaf 颗粒化权限控制1
2
3
4
5
6
7
8
9
10
11你好, <span th:text="${principal}"></span><br>
<p shiro:hasRole="super_admin">当前角色超级管理员</p>
<button shiro:hasPermission="'sys:user:add'">添加</button>
<button shiro:hasPermission="'sys:user:update'">编辑</button>
<button shiro:hasPermission="'sys:user:lock'">冻结</button>
<div shiro:hasAllPermissions="'sys:user:add, sys:user:update, sys:user:lock'">
<span>满足所有权限时显示</span>
</div>
<div shiro:hasAnyPermissions="'sys:user:add, sys:user:update, sys:user:lock'">
<span>满足一个权限即可显示</span>
</div>
更多用法参考
Github 文档:https://github.com/theborakompanioni/thymeleaf-extras-shiro
本文demo
https://gitee.com/yintianwen7/taven-springboot-learning/tree/master/springboot-shiro
如果你发现我的文章或者demo中存在问题,请联系我