Apache Shiro: Simple Java Security
官方文档: 传送门
中文文档:传送门
推荐学习视频: 传送门
概念:
备注:
该图来源官方文档,官网有详细解释 [传送门]
- Realm
领域是可以访问特定于应用程序的安全性数据(例如用户,角色和权限)的组件。可以将其视为特定于安全性的 DAO(数据访问对象)。 Realm 将此特定于应用程序的数据转换为 Shiro 可以理解的格式,因此 Shiro 可以反过来提供单个易于理解的 Subject 编程 API,无论存在多少数据源或您的数据有多少特定于应用程序。 - SecurityManager
是Shiro架构的核心。它主要是一个“伞形”对象,用于协调其托管组件以确保它们顺利地协同工作。它还管理Shiro对每个应用程序用户的视图,因此它知道如何对每个用户执行安全操作。 - Session
会话是与一段时间内与软件系统进行交互的单个用户/主题关联的有状态数据上下文。当主题使用应用程序时,可以在会话中添加/读取/删除数据,并且以后应用程序可以在必要时使用此数据。当用户/主题退出应用程序或由于不活动而超时时,会话将终止。 - Cryptography
密码学是一种通过隐藏信息或将其转换为废话来保护信息免遭不希望的访问的做法,其他人都无法阅读。 Shiro 专注于密码学的两个核心要素:使用公钥或私钥对诸如电子邮件之类的数据进行加密的密码,以及不可逆地对诸如密码之类的数据进行加密的哈希(即消息摘要)。 - Subject
只是花哨的安全性术语,它基本上表示应用程序用户的特定于安全性的“视图”。不过,主题不一定总是需要反映一个人-它可以代表调用您的应用程序的外部进程,也可以代表在一段时间内间歇执行某项任务的守护程序系统帐户(例如 cron 作业)。它基本上是任何正在对应用程序执行操作的实体的表示。
快速开始
- 导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
- 简单测试
/**
* @ClassName TestAuthencitaor
* @author: fangwenjun
* @date: Created in 2021/5/24 14:19
* @description:
* @version: 1.0
*/
public class TestAuthenticator {
public static void main(String[] args) {
// 获取默认的安全管理器
DefaultSecurityManager manager = new DefaultSecurityManager();
// 设置认证信息来源
manager.setRealm(new IniRealm("classpath:shiro.ini"));
// 使用安全工具类设置安全管理器
SecurityUtils.setSecurityManager(manager);
// 获取当前需要认证的主体(用户)
Subject subject = SecurityUtils.getSubject();
// 封装用户信息,获取token
UsernamePasswordToken token = new UsernamePasswordToken("fangwenjun", "1234");
System.out.println("认证状态:"+subject.isAuthenticated());
try {
// 进行登录, 失败则抛出对应异常
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
}catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
}
}
// 以下信息是认证信息来源,在项目资源路径下创建以.ini结尾的文件,该文件只在单独学习时使用,节省了读取数据库获取用户信息的操作
[users]
fangwenjun=1234
Shiro认证流程源码
登录方法进行登录
实际使用的还是安全管理器的登录方法
获取Realm(可以将其视为特定于安全性的 DAO(数据访问对象))
先从缓存管理器中获取认证信息,没有缓存则是第一次登录,使用Realm进行认证,然后进行缓存
进行身份认证,解析传入的参数token与数据库中的真实信息进行比较,然后返回
身份认证完成后,会自动进行密码的校验, 最终不论是用户名还是密码错误,login方法都会抛出对应的异常,通过自定义继承AuthorizingRealm
类, 重写doGetAuthenticationInfo
方法和doGetAuthorizationInfo
方法,对用户进行身份认证及授权
自定义CustomRealm继承AuthorizingRealm,实现身份认证
/**
* @ClassName CustomRealm
* @author: fangwenjun
* @date: Created in 2021/5/24 16:23
* @description:
* @version: 1.0
*/
public class CustomRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
if ("fangwenjun".equals(principal)){
return new SimpleAuthenticationInfo("fangwenjun", "1234", this.getName());
}
return null;
}
}
/**
* @ClassName TestCustomRealm
* @author: fangwenjun
* @date: Created in 2021/5/24 16:26
* @description:
* @version: 1.0
*/
public class TestCustomRealm {
public static void main(String[] args) {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 使用自定义的Realm进行认证
securityManager.setRealm(new CustomRealm());
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("fangwenjun", "123");
try {
System.out.println("认证状态:"+subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
}catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
}
}
结合上面的自定义方式使用MD5 + Salt + Hash 方式进行身份认证
/**
* @ClassName CustomeMD5Realm
* @author: fangwenjun
* @date: Created in 2021/5/25 8:35
* @description:
* @version: 1.0
*/
public class CustomerMd5Realm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
if ("fangwenjun".equals(principal)){
// 模拟注册时加密密码 加密方式使用 md5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash("1234","123",1024);
// 参数1: 用户名 参数2: 注册时加密后的密码, 参数3:该密码使用的盐, 参数4:realm的名称
// 返回加密后的密码和salt shiro会自动匹配密码
return new SimpleAuthenticationInfo("fangwenjun",md5Hash.toHex(),ByteSource.Util.bytes("123"),this.getName());
}
return null;
}
}
/**
* @ClassName TestCustomRealm
* @author: fangwenjun
* @date: Created in 2021/5/24 16:26
* @description:
* @version: 1.0
*/
public class TestCustomRealm {
public static void main(String[] args) {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 使用自定义的Realm进行认证
CustomerMd5Realm realm = new CustomerMd5Realm();
// 使用hash方式设置md5加密 + 1024次hash
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1024);
realm.setCredentialsMatcher(matcher);
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("fangwenjun", "1234");
try {
System.out.println("认证状态:"+subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
}catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
}
}
授权
授权方式:
- 基于角色的访问控制RBAC(Role-Based Access Control)
- 基于资源的访问控制RBAC(Resource-Based Access Control)
Shiro中权限字符串
权限字符串的规则是资源标识符:操作:资源实例标识符
,对那个资源的那个实例具有什么操作,权限可以精确到每个实例,也可以使用*
作为通配符
shiro在代码授权方式:
- 代码中直接判断权限
- 使用注解方式
- 标签方式
基于用户认证成功后对用户的权限进行授权
/**
* @ClassName CustomeMD5Realm
* @author: fangwenjun
* @date: Created in 2021/5/25 8:35
* @description:
* @version: 1.0
*/
public class CustomerMd5Realm extends AuthorizingRealm {
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取用户名
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
System.out.println("身份信息:"+primaryPrincipal);
if ("fangwenjun".equals(primaryPrincipal)){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 模拟从DB获取角色和权限数据
info.addRole("admin");
info.addRole("user");
info.addStringPermission("user:add:01");
info.addStringPermission("user:update:01");
return info;
}
return null;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
if ("fangwenjun".equals(principal)){
// 模拟注册时加密密码 加密方式使用 md5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash("1234","123",1024);
// 参数1: 用户名 参数2: 注册时加密后的密码, 参数3:该密码使用的盐, 参数4:realm的名称
// 返回加密后的密码和salt shiro会自动匹配密码
return new SimpleAuthenticationInfo("fangwenjun",md5Hash.toHex(),ByteSource.Util.bytes("123"),this.getName());
}
return null;
}
}
/**
* @ClassName TestCustomRealm
* @author: fangwenjun
* @date: Created in 2021/5/24 16:26
* @description:
* @version: 1.0
*/
public class TestCustomRealm {
public static void main(String[] args) {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 使用自定义的Realm进行认证
CustomerMd5Realm realm = new CustomerMd5Realm();
// 使用hash方式设置md5加密 + 1024次hash
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1024);
realm.setCredentialsMatcher(matcher);
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("fangwenjun", "1234");
try {
// 进行登录
subject.login(token);
// 判断认证状态
if (subject.isAuthenticated()){
// 根据角色访问控制
// 判断当前主体是否具有某个角色
System.out.println(subject.hasRole("admin"));
// 判断当前主体是否同时具有多个角色
System.out.println(subject.hasAllRoles(Arrays.asList("admin","user","super")));
// 根据资源访问控制
// 判断当前主体是否具有某个权限
System.out.println(subject.isPermitted("user:add:01"));
// 判断当前主体是否同时具有多个权限
System.out.println(subject.isPermittedAll("user:add:01","user:update:*"));
}
}catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
}
}