- 定义
- Shiro 是一个强大的、易用的 Java 安全框架,它可以帮助开发者处理身份验证(Authentication)、授权(Authorization)、加密(Cryptography)和会话管理(Session Management)等应用程序安全相关的功能。Shiro 的设计理念是简单、灵活且功能强大,它可以在 Java SE 和 Java EE 环境中使用。
- 主要功能
- 身份验证
- 这是确定用户身份的过程。Shiro 提供了多种身份验证方式,例如通过用户名 / 密码组合来验证用户身份。它支持多种数据源,如关系数据库、LDAP(轻型目录访问协议)等。例如,在一个 Web 应用中,用户在登录页面输入用户名和密码,Shiro 可以获取这些信息并与存储在数据库中的用户凭证进行比对,以确定用户是否是合法用户。
- 授权
- 授权是确定已认证用户是否有权访问特定资源的过程。Shiro 支持基于角色(Role - Based Access Control,RBAC)和基于权限(Permission - Based Access Control,PBAC)的授权方式。以一个企业资源管理系统为例,管理员角色的用户可能有权限访问和修改所有员工的信息,而普通员工角色的用户可能只能访问自己的信息。Shiro 可以根据用户的角色或权限来控制用户对系统不同功能模块的访问。
- 加密
- Shiro 提供了简单易用的加密功能,用于保护数据的安全性。它支持多种加密算法,如 MD5、SHA - 1 等。在存储用户密码时,不应该以明文形式存储,Shiro 可以对密码进行加密处理,然后将加密后的密码存储在数据库中。当用户登录验证时,对输入的密码进行同样的加密操作,再与存储的加密密码进行比对,这样可以防止密码泄露导致的安全风险。
- 会话管理
- Shiro 提供了自己的会话管理机制,它可以独立于 Web 容器的会话管理。这对于一些分布式系统或者需要精细控制会话的场景非常有用。例如,在一个分布式的微服务架构中,不同的服务可能需要共享用户的会话信息,Shiro 可以帮助管理这些会话,确保用户在不同服务之间的交互过程中会话的一致性和安全性。
- 身份验证
- 架构组成
- Subject:这是 Shiro 的核心概念之一,它代表了当前与软件系统进行交互的用户、设备或其他系统主体。可以把 Subject 看作是一个门面,通过它可以方便地进行身份验证、授权等操作。例如,在一个 Web 应用中,当一个用户访问一个受保护的资源时,这个用户就对应一个 Subject。
- SecurityManager:它是 Shiro 的核心管理组件,负责协调和管理 Shiro 的各个模块,如身份验证、授权、会话管理等。SecurityManager 就像是一个指挥官,指挥着整个安全系统的运作。所有 Subject 的操作都会委托给 SecurityManager 来执行。
- Realms:Realms 是 Shiro 与应用程序安全数据(如用户、角色和权限)的数据源进行交互的桥梁。它可以从不同的数据源(如数据库、LDAP 服务器等)获取安全相关的数据,以支持身份验证和授权操作。例如,如果用户信息存储在关系数据库中,就可以创建一个数据库 Realm 来从数据库中读取用户凭证、角色和权限等信息。
Shiro的运行流程:Shiro 运行的典型流程
- 身份验证流程
- Subject 发起认证请求:当 Subject(比如用户在登录页面输入用户名和密码后点击登录)需要进行身份验证时,它会调用
SecurityManager.login()
方法,并将包含用户凭证(如用户名和密码)的AuthenticationToken
传递进去。 - SecurityManager 处理请求:SecurityManager 接收到认证请求后,会将任务委托给内部的
Authenticator
组件。 - Authenticator 与 Realm 交互:Authenticator 会调用配置的一个或多个 Realms 来获取用户身份信息进行验证。Realm 会根据传入的
AuthenticationToken
中的信息(如用户名)从数据源(如数据库)中查找对应的用户记录,并比较密码等凭证是否匹配。 - 返回认证结果:如果 Realm 验证通过,Authenticator 会返回一个成功的认证信息给 SecurityManager,SecurityManager 再将认证成功的信息返回给 Subject,此时 Subject 就被标记为已认证状态。如果验证失败,会抛出相应的异常,如
UnknownAccountException
(账号不存在)、IncorrectCredentialsException
(密码错误)等。
- Subject 发起认证请求:当 Subject(比如用户在登录页面输入用户名和密码后点击登录)需要进行身份验证时,它会调用
shiro如何对密码继续加密
- 使用 Shiro 自带的加密工具
- Shiro 提供了
SimpleHash
类来进行密码加密。它支持多种哈希算法,如 MD5、SHA - 1、SHA - 256 等。 - 示例代码:
- Shiro 提供了
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
public class ShiroPasswordEncryption {public static void main(String[] args) {// 原始密码String password = "123456";// 哈希算法名称,这里使用MD5String algorithmName = "MD5";// 盐值(可以是随机生成的字符串),增强安全性String salt = "random_salt";// 哈希次数int hashIterations = 1024;// 使用SimpleHash进行加密SimpleHash hash = new SimpleHash(algorithmName, password, ByteSource.Util.bytes(salt), hashIterations);System.out.println("加密后的密码: " + hash.toHex());}
}
- 在这个示例中,
SimpleHash
构造函数接收哈希算法名称、原始密码、盐值(字节数组形式)和哈希次数。ByteSource.Util.bytes(salt)
是将盐值转换为字节数组的操作。最后,hash.toHex()
将加密后的哈希值转换为十六进制字符串方便存储和比较。
- 加盐(Salt)机制在 Shiro 中的应用
- 为什么要加盐:加盐可以增加密码破解的难度。如果没有盐值,攻击者可以使用预先计算好的彩虹表(包含常见密码和对应的哈希值)来快速找到密码。而加盐后,即使密码相同,由于盐值不同,哈希值也不同,使得彩虹表攻击失效。
- 在 Shiro 中配置盐值:可以在自定义的
Realm
(Shiro 用于获取安全数据的组件)实现中设置盐值。例如,在AuthenticatingRealm
的子类中:
public class MyRealm extends AuthenticatingRealm {@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 假设从数据库或其他数据源获取用户信息String username = (String) token.getPrincipal();User user = userDao.findUserByUsername(username);if (user == null) {throw new UnknownAccountException("用户不存在");}// 获取盐值,假设用户对象中有盐值属性String salt = user.getSalt();// 创建SimpleAuthenticationInfo对象,传入盐值SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(salt), getName());return info;}
}
- 在这个
MyRealm
类的doGetAuthenticationInfo
方法中,从数据源获取用户的盐值,然后在创建SimpleAuthenticationInfo
对象时,将盐值(字节数组形式)传入。这样在验证密码时,Shiro 会使用相同的盐值来重新计算哈希值并与存储的密码哈希值进行比较。
- 与其他加密方式集成(如 BCrypt)
- 使用 BCrypt 加密器:BCrypt 是一种更安全的密码加密方式。虽然 Shiro 本身有加密工具,但也可以将 BCrypt 集成进来。
- 示例代码(使用 Spring Boot 和 Shiro 集成 BCrypt):
- 首先添加
spring - security - crypto
依赖(因为 BCrypt 加密工具在这个库中):
- 首先添加
<dependency><groupId>org.springframework.security</groupId><artifactId>spring - security - crypto</artifactId>
</dependency>
- 然后在自定义的
Realm
或者密码处理类中使用 BCrypt:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordEncryptionWithBCrypt {public static void main(String[] args) {BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();String password = "123456";String encryptedPassword = encoder.encode(password);System.out.println("加密后的密码: " + encryptedPassword);boolean matches = encoder.matches("123456", encryptedPassword);System.out.println("密码匹配结果: " + matches);}
}
- 这种方式利用了 Spring Security 提供的 BCrypt 加密工具,通过
BCryptPasswordEncoder
类来进行密码加密和验证。在 Shiro 的Realm
中,可以在doGetAuthenticationInfo
方法等地方使用类似的加密方式来处理密码,以增强密码的安全性。
Shiro怎么使用?
- 引入 Shiro 依赖
- 如果你使用的是 Maven 项目,需要在
pom.xml
文件中添加 Shiro 依赖。例如:
- 如果你使用的是 Maven 项目,需要在
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro - core</artifactId><version>1.10.0</version>
</dependency>
- 对于 Gradle 项目,则在
build.gradle
文件中添加:
implementation 'org.apache.shiro:shiro - core:1.10.0'
- 创建自定义 Realm(数据源)
- Realm 是 Shiro 获取安全数据(如用户、角色和权限)的地方,相当于数据源。
- 自定义 Realm 需要继承
AuthorizingRealm
类,并重写doGetAuthenticationInfo
和doGetAuthorizationInfo
方法。 - 示例代码如下:
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.HashSet;
import java.util.Set;
public class MyRealm extends AuthorizingRealm {// 模拟用户数据存储(实际应用中可能是数据库等)private static final Set<String> userCredentials = new HashSet<>();private static final Set<String> userRoles = new HashSet<>();private static final Set<String> userPermissions = new HashSet<>();static {userCredentials.add("user1:password1");userRoles.add("user1:role1");userPermissions.add("user1:permission1");}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String) token.getPrincipal();String password = null;for (String userCredential : userCredentials) {String[] parts = userCredential.split(":");if (parts[0].equals(username)) {password = parts[1];break;}}if (password == null) {throw new AuthenticationException("用户不存在");}return new SimpleAuthenticationInfo(username, password, getName());}@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = (String) principals.getPrimaryPrincipal();SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();for (String userRole : userRoles) {String[] parts = userRole.split(":");if (parts[0].equals(username)) {authorizationInfo.addRole(parts[1]);}}for (String userPermission : userPermissions) {String[] parts = userPermission.split(":");if (parts[0].equals(username)) {authorizationInfo.addStringPermission(parts[1]);}}return authorizationInfo;}
}
- 在
doGetAuthenticationInfo
方法中,根据传入的用户名(从AuthenticationToken
获取)查找对应的密码,如果找不到用户则抛出异常,找到则返回包含用户名、密码和 Realm 名称的SimpleAuthenticationInfo
。 - 在
doGetAuthorizationInfo
方法中,根据用户名查找对应的角色和权限,将它们添加到SimpleAuthorizationInfo
中并返回
- 配置 SecurityManager
- SecurityManager 是 Shiro 的核心组件,用于管理安全操作。
- 示例代码如下:
import org.apache.shiro.SecurityManager;
import org.apache.shiro.mgt.DefaultSecurityManager;
public class ShiroConfig {public static SecurityManager getSecurityManager() {DefaultSecurityManager securityManager = new DefaultSecurityManager();securityManager.setRealm(new MyRealm());return securityManager;}
}
- 这里创建了一个
DefaultSecurityManager
,并将自定义的MyRealm
设置给它。
- 使用 Shiro 进行身份验证和授权
- 身份验证示例:
import org.apache.shiro.SecurityManager;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import static com.example.shiro.ShiroConfig.getSecurityManager;
public class ShiroAuthenticationExample {public static void main(String[] args) {SecurityManager securityManager = getSecurityManager();// 将SecurityManager设置到当前线程环境org.apache.shiro.util.ThreadContext.bind(securityManager);Subject subject = org.apache.shiro.subject.Subject.Builder.create().build();UsernamePasswordToken token = new UsernamePasswordToken("user1", "password1");try {subject.login(token);System.out.println("身份验证成功");} catch (Exception e) {System.out.println("身份验证失败: " + e.getMessage());}}
}
- 在这个示例中,首先获取
SecurityManager
并绑定到当前线程环境,然后创建Subject
,通过UsernamePasswordToken
传入用户名和密码进行登录操作。如果登录成功,说明身份验证通过。 - 授权示例:
import org.apache.shiro.SecurityManager;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import static com.example.shiro.ShiroConfig.getSecurityManager;
public class ShiroAuthorizationExample {public static void main(String[] args) {SecurityManager securityManager = getSecurityManager();// 将SecurityManager设置到当前线程环境org.apache.shiro.util.ThreadContext.bind(securityManager);Subject subject = org.apache.shiro.subject.Subject.Builder.create().build();UsernamePasswordToken token = new UsernamePasswordToken("user1", "password1");try {subject.login(token);if (subject.hasRole("role1")) {System.out.println("用户具有role1角色");}if (subject.isPermitted("permission1")) {System.out.println("用户具有permission1权限");}} catch (Exception e) {System.out.println("身份验证或授权失败: " + e.getMessage());}}
}
- 这里在身份验证成功后,通过
subject.hasRole
和subject.isPermitted
方法来检查用户是否具有特定的角色和权限。