Shiro
# 概述
Shiro是Apache 下开源的安全框架 , 相当于 Spring Security更加轻量灵活 , 解决身份验证 , 授权 , 会话加密等
官网 : https://shiro.apache.org (opens new window)
特点
认证
用户行为,指定登录认证
授权
访问控制,指定谁可以访问
会话管理
管理用户特定会话,即使在非 Web/JEB 应用程序中
加密
加密算法保证数据安全
Shiro开发团队称为 应用程序安全的四个基石
# 术语
Shiro的术语较多,它包含有很多常用的知识,因此特意说明
官方文档 : https://shiro.apache.org (opens new window)
- Authentication 认证 用户行为,指定登录认证
- Authorization 授权 访问控制,指定谁可以访问
- Cryptography密码 密码是用于 加密/解密 的算法。 算法通常依赖于密钥的一条信息
- Credential 凭证 证书是验证用户/主体的身份的一条信息。
- Cryptography 加密 将有效信息 转化为 看不懂且不受欢迎的信息,因此达到无人能读取的目的
- 哈希 哈希函数是输入源且单向不可逆的,转化为 编码的哈希值。一般称为 消息
- 权限 应用程序的初始功能,在当中用户需要凭证才有权能进行应用这些功能
- Subject 主体 用户的标识属性,用户相关的都包含在内
- Realm 领域 领域是可以访问特定于应用程序的安全数据具有1对1关联
- Role角色 偏向人们思维理解,去定义角色进行区分权限。不同角色有不同的权限
- Session 会话 一段时间内与软件交互的单个用户的过程
- 主题 用户特定的视图。可以看做是一个 外部运行的进程/守护进程
# Shiro API
Shiro API手册 : https://tool.oschina.net (opens new window)
# Subject
org.apache.shiro.subject
接口,用户对象的描述 , 这个用户不一定是一个具体的人 , 与当前应用交互的任何东西都是Subject
主要提供了用户认证的操作入口 , 包括身份验证(登录/注销)、授权(访问控制)和会话访问
Subject 原意是和 User 一样,由于很多应用程序又有 用户User类 相同的名称,因此 Shiro 尽可能的排除这些冲突,把类名设为 Subject。此外在 Subject 是安全世界中公认的命名存在
常用方法
返回 | 方法 | 说明 |
---|---|---|
PrincipalCollection | ==getPrincipals()== | 获取凭证集合 |
boolean | ==isPermitted(String permission)== | 是否有指定权限 (多方式 |
boolean | ==hasRole(String roleIdentifier)== | 是否为指定角色(多方式 |
boolean | ==isAuthenticated()== | 是否通过身份认证 |
boolean | ==isRemembered()== | 是否被记住 |
Session | ==getSession()== | 获取 会话Session |
void | ==login(AuthenticationToken token)== | 认证登录 |
void | ==logout()== | 退出登录 |
login()方法是登录主要入口,认证失败 可能抛出以下异常:
- UnknownAccountException 用户名异常(不存在
- IncorrectCredentialsException 密码不匹配异常
- LockedAccountException 用户名被锁定,不能登录
# SecurityManager
org.apache.shiro.mgt.SecurityManager
接口 , 安全管理器 , Shiro框架的核心 , 负责协调应用与Subject之间的交互 , 负责进行认证、授权、会话及缓存等控制
为单个应用程序中的所有主体(用户)执行所有安全操作
SecurityManager
接口 扩展了Authenticator
、Authorizer
、SessionManager
接口三个接口,将三个接口整合到一个接口中进行简化应用,三个接口作用分别是:
org.apache.shiro.authc.Authenticator
:账号身份认证(也是登录的主要入口org.apache.shiro.authz.Authorizer
:为指定主体 执行授权 (权限控制)操作org.apache.shiro.session.mgt.SessionManager
:管理程序Session的 创建、维护、清理
步骤实例:单元测试
在通过以上的单元测试可得知,一旦
SecurityManager
执行完毕,在此之后我们只需关心 ==SecurityUtils.getSubject()== 即可(实质上就是SecurityManager
保证了Subject
的安全操作
# Realm
org.apache.shiro.realm
接口 , 安全组件 , 可以访问特定的应用程序的安全实体(如:用户账号、角色、权限)信息来确认 认证/授权 的操作 (通常涉及数据库的操作)(也是受 SecurityManager
安全管理器 控制的
一般 Realm 会通过 JDBC数据源、ini配置源、等其他源,进行获取需要匹配的认证信息
Realm通常与数据库源进行打交道,进行认证信息。实现信息 认证/授权,一般会通过
AuthenticatingRealm
/AuthorizingRealm
进行操作
# AuthenticatingRealm
org.apache.shiro.realm.AuthenticatingRealm
抽象类,身份认证缓存,在用户登录有将用户信息缓存起来,防止频繁认证!
实现需要重写 ==doGetAuthenticationInfo(AuthenticationToken token)==方法 实现用户认证
/**
* 身份信息认证
* @param token 用户在身份验证期间提交主体和支持凭据的合并(包含有用户输入的凭证信息 (常见的 用户名/密码)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = (String) token.getPrincipal();
User user = null;
try {
// 业务查询库账号数据
user = sysService.findUserByUserName(userName);
if (user == null) return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
/** 说明:应用了MD5加密
参数
* 1. Object principal 首要(主体
* 2. Object hashedCredentials 凭证(库密码
* 3. ByteSource credentialsSalt 盐(秘钥
* 4. String realmName 领域名 (自定义Realm名称
*/
return new SimpleAuthenticationInfo(activeUser, user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), "CustomRealm");
}
==doGetAuthenticationInfo()== 实现返回是
AuthenticationInfo
实例,该实例支持密码的加密(加密算法可以自行选择!
# AuthorizingRealm
org.apache.shiro.realm.AuthorizingRealm
抽象类,实现自动执行角色和权限的自动检查(每次执行检查相关都会跑一边检查权限
该抽象类继承了
AuthenticatingRealm
抽象类 ,整合了 角色权限分配/认证的功能
实现需要重写 ==doGetAuthorizationInfo(PrincipalCollection collection)==方法 分配权限
/**
* 获取角色信息授权
* @param collection 标识主体的对象,包含主体的相关信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection collection) {
// ActiveUser封装了 Permissions权限、Role角色
ActiveUser activeUser = (ActiveUser) collection.getPrimaryPrincipal();
// 角色列表
List<String> permissions = new ArrayList<>();
for (SysPermission sysPermission : activeUser.getPermissions()) {
permissions.add(sysPermission.getPercode());
}
// 授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissions);
info.addRole(activeUser.getUsercode());
return info;
}
# Spring配置API
# ShiroFilterFactoryBean
org.apache.shiro.spring.web.ShiroFilterFactoryBean
类,基于Spring的Web程序的Shiro主过滤器
主要属性
类型 | 属性名 | 说明 |
---|---|---|
SecurityManager | securityManager | 安全管理配置 |
Map<String, Filter> | filters | 自定义/重写的 过滤器配置 |
Map<String, String> | filterChainDefinitionMap | Shiro过滤器链 |
String | loginUrl | 认证提交入口的URL配置 |
String | successUrl | 认证成功入口的URL配置 |
String | unauthorizedUrl | 无权访问从定向URL配置 |
步骤:
在
web.xml
文件 先声明过滤器<!-- Shiro过滤器 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 设置true由servlet容器控制filter的生命周期 --> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean (保险使用防止不同过滤器名称--> <init-param> <param-name>targetBeanName</param-name> <param-value>shiroFilter</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
注意 : 这里的 过滤器名称 与 Bean id 匹配,否则spring找不到匹配的Bean。 过滤器的
targetBeanName
属性可以指定过滤器名称 进行修正与Bean id的匹配在 Spring配置中配置
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<-- 以上的配置属性根据个人需求进行配置值即可 -->
....
</bean>
# DefaultWebSecurityManager
org.apache.shiro.web.mgt.DefaultWebSecurityManager
类,安全管理器(和 SecurityManager 是关联配置
基于Web实现任何程序需要HTTP连接的应用,一般是搭配其他管理器进行应用(其他管理器有点多自行查询
如图配置:
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
<property name="cacheManager" ref="cacheManager"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
# CredentialsMatcher
org.apache.shiro.authc.credential.CredentialsMatcher
接口,凭证适配器(用于加密适配密码等
如果自定义Realm,配置了凭证适配器,认证方式会按照凭证适配器的逻辑进行匹配账号
步骤
添加 凭证适配器Bean (以下 凭证适配器应用了MD5算法的加密
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5"/> <property name="hashIterations" value="2"/> </bean>
自定义领域添加凭证适配器Bean
<!-- 自定义领域 --> <bean name="customRealm" class="com.sans.shiro.CustomRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean>
SpringBoot用法
注入自定义Bean 实现 SimpleCredentialsMatcher类 , 重写 doCredentialsMatch() 方法 , 添加密码适配器Bean和加密算法
/**
* 密码适配器
*/
@Component
public class PasswordCredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
if (token instanceof UsernamePasswordToken) {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String password = new String(upToken.getPassword());
return BCrypt.checkpw(password, info.getCredentials().toString());
} else {
BearerToken bearerToken = (BearerToken) token;
return JwtUtils.validTimeout(bearerToken.getToken());
}
}
}
# AuthorizationAttributeSourceAdvisor
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
类,权限管理器
Shiro默认是不会有 处理/捕获 异常,因此该类主要是给 授权/认证 失败所抛出的异常给与对应的处理(友善的回复
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
**注意:**配置主要配置在Spring整体的上下文中。如果不是在Spring上下文的整体中,则需要配置在 SpringMVC的配置中 ,这一回复过程是交给SpringMVC进行处理的!
# 配置文件 *.ini
ini 基本上是一种文本配置,由唯一命名的部分组织的 键/值对组成。每个部分都可以被视为单个定义Properties
官方配置文档 : https://shiro.apache.org (opens new window)
Shiro ini主要配置:
# [main]
配置应用程序的实例依赖项(例如:Realms配置
可以理解为是低配版的 spring loc容器配置
为了能够更直观的理解,以下配置库连接的示例:
[main]
newUser = com.pojo.MyUser
myRealm = com.shiro.DatabaseRealm
myRealm.connectionTimeout = 30000
myRealm.username = jsmith
myRealm.password = secret
myRealm.user = $newUser
securityManager.sessionManager.globalSessionTimeout = 1800000
定义对象
[main]
## 实例化新对象,并为对象进行配置
myRealm = com.shiro.DatabaseRealm
设置对象属性值
...
myRealm.connectionTimeout = 30000
myRealm.username = jsmith
myRealm.password = secret
...
本质的调用 setter()方法进行 赋予值的
属性值如果为对象则需要通过 $
美元符号 作为前缀定义
...
newUser = com.pojo.MyUser
...
## 实体属性形式 : MyUser user;
myRealm.user = $newUser
...
属性对象也是通过 setter()方法 进行赋予值的。==myRealm.setUser(newUser)==
嵌套属性
为最终要到达的属性设置值
...
securityManager.sessionManager.globalSessionTimeout = 1800000
...
换个角度可以理解为:==securityManager.getSessionManager().setGlobalSessionTimeout(1800000)==
# [users]
一组静态用户账户(一般在 测试 或 运行时不需要创建用户 的环境下配置应用
[users]
admin = sans
lonestarr = vespa, goodguy, useroperation
darkhelmet = ludicrousspeed, badguy, schwartz
每行格式
每行都要满足的格式形式:
==username = password , rolename1 , rolename2 , ...==
- username :用户名
- password :用户密码
- 密码后的
,
逗号分隔 值都是分配给该用户角色的名称 (可选角色
# [roles]
权限 和 用户 定义关联(一般在 测试 或 运行时不需要创建用户 的环境下配置应用
## 'admin' 角色具有所有权限,由通配符 '' 指示
admin = *
## 'useroperation' 角色 可以用 user的任何功能
useroperation = user:*
## 'usercr' 角色 只能用 user的create和delete功能
usercr = user:create,user:delete
每行格式
每行都要满足的格式形式:
==rolename = permissionDefinition1 , permissionDefinition2 , ...==
- rolename : 角色名称
- permissionDefinition : 权限
# [urls]
JavaWeb请求地址配置
[urls]
/index.html = anon
/user/create = anon
/user/** = authc
/rest/** = authc, rest
每行格式
==uri = filter1 , filter2 , ...==
- uri :路由
- filter :过滤器 (Shiro有套默认的过滤器
DIY过滤器
[main]
...
myFilter = com.sans.filters.MyFilter
...
[urls]
...
/some/path/** = myFilter
# 首次应用
# 单元测试
# 实例1
Realm通过配置 .ini 实现
引入jar
配置文件
.ini
(shiro-first.ini)#用户信息配置 [users] #用户帐号和密码 zhangsan = 111111 lishi = 333333
单元测试 测试前提需要shiro环境!
初始化
Security Manager
环境==Factory<SecurityManager> factory = new IniSecurityManagerFactory(<配置文件>)==
配置安全管理环境
// 实例安全管理看空间 SecurityManager instance = factory.getInstance(); // 设置安全管理 SecurityUtils.setSecurityManager(instance);
获取认证主体
==Subject subject = SecurityUtils.getSubject();==
主题认证 (toke封装
==UsernamePasswordToken token = new UsernamePasswordToken()==
认证信息 (该方法可能会抛出登录可能出现的异常
==subject.login(token);==
验证登录成功
==subject.isAuthenticated()==
示例
@Test
public void defaultTest() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-first.ini");
// 实例安全管理看空间
SecurityManager instance = factory.getInstance();
// 设置安全管理
SecurityUtils.setSecurityManager(instance);
// 获取认证主体
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("lishi", "333333");
subject.login(token);
boolean authenticated = subject.isAuthenticated();
System.out.println("是否可行?" + authenticated);
}
/* 控制台结果
是否可行?true
*/
# 实例2
// 1. 创建SecurityManager工厂
DefaultSecurityManager factory = new DefaultSecurityManager();
// 2. 给SecurityManager设置Realm
factory.setRealm(new MemoryRealm());
// 3. 给SecurityManager设置subject工厂
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
factory.setSubjectDAO(subjectDAO);
// 4. 给SecurityManager设置session管理
DefaultSessionManager sessionManager = new DefaultSessionManager();
factory.setSessionManager(sessionManager);
// 5. 创建Subject
Subject subject = factory.createSubject(null);
// 6. 登陆
subject.login(null);
// 7. 授权
subject.checkRoles("admin");
subject.checkPermissions("user:create");
# SpringBoot应用
**集成SpringBoot:**https://shiro.apache.org/spring-boot.html#web_applications
引入依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.7.1</version> </dependency>
Shiro 配置
@Configuration public class ShiroConfig { // 主要配置 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 设置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 登录入口页 shiroFilterFactoryBean.setLoginUrl("/login"); // 未授权跳转页 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // 指定路径过滤量 , 对应过滤器的约束 LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/login", "anon"); // 无需检查 filterChainDefinitionMap.put("/**", "authc"); // 身份认证 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } // 安全管理器 SecurityManager Bean环境 @Bean public DefaultWebSecurityManager securityManager(ShiroServiceImpl shiroService) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(shiroService); return securityManager; } }
权限认证和授权 (大概用法)
public class UserRealm extends AuthorizingRealm { //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); String password = ""; if (upToken.getPassword() != null) { password = new String(upToken.getPassword()); } SysUser user = null; // 最好捕获下异常 user = loginService.login(username, password); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); return info; } //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SysUser user = ShiroUtils.getSysUser(); // 角色列表 Set<String> roles = new HashSet<String>(); // 功能列表 Set<String> menus = new HashSet<String>(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 管理员拥有所有权限 if (user.isAdmin()) { info.addRole("admin"); info.addStringPermission("*:*:*"); } else { roles = roleService.selectRoleKeys(user.getUserId()); menus = menuService.selectPermsByUserId(user.getUserId()); // 角色加入AuthorizationInfo认证对象 info.setRoles(roles); // 权限加入AuthorizationInfo认证对象 info.setStringPermissions(menus); } return info; } }
# 执行流程
# 认证
- 通过 ini配置文件 创建
securityManager
安全管理器 - 调用 subject.login()方法 执行提交认证(带参数的token
securityManager
认证(最终由 ModularRealmAuthenticator 进行认证处理- ModularRealmAuthenticator 调用 IniRealm 去ini配置文件查询用户信息
- IniRealm根据 token 中,查询用户信息(账号密码 查到的返回 用户信息,否则null
- ModularRealmAuthenticator 接收 IniRealm返回Authentication认证信息
//获取Subject
Subject subject = SecurityUtils.getSubject();
//封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123456");
//执行登录
subject.login(token);
# 授权
- 对subject进行授权,调用方法 isPermitted()方法 (指定权限key
- SecurityManager执行授权,通过ModularRealmAuthorizer进行授权
- ModularRealmAuthorizer执行 Realm (从数据库查询权限数据 Realm授权方法:doGetAuthorizationInfo()
- Realm从数据库查询权限数据,返回ModularRealmAuthorizer
- ModularRealmAuthorizer调用PermissionResolver进行权限串比对
- 通过比对permission权限进行赋予权限
// 获取Subject
Subject subject = SecurityUtils.getSubject();
// 判断是否具有某个角色
subject.hasRole("admin");
// 判断是否具有某个权限
subject.isPermitted("user:create");
# 过滤器
# 自定义过滤器
自定义可以根据个人设定进行功能实现
//Shiro的过滤器链
FilterChainDefinitionMap filterChainDefinitionMap = new FilterChainDefinitionMap();
// 登录请求过滤器
filterChainDefinitionMap.put("/login", "authc");
// 其他请求过滤器,必须已登录
filterChainDefinitionMap.put("/**", "authc");
// 创建realm (Jdbc中的获取的权限...)
JdbcRealm jdbcRealm = new JdbcRealm();
// 创建SecurityManager
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm
securityManager.setRealm(jdbcRealm);
// 创建过滤器工厂
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置过滤器链
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 设置SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);