目录
- 基本功能
- 核心组件
- 应用场景
- 优势
- Shiro 核心工作流程(以 Web 应用登录为例)
- 一个例子【验证,授权]:
Shiro 是一个强大且易用的 Java 安全框架,提供了 身份验证、授权、加密和会话管理等功能,可帮助开发人员轻松确保应用程序的安全,以下是对它的详细介绍:
基本功能
- 身份验证:也称为 “登录”,用于验证用户身份。Shiro 支持多种身份验证方式,如用户名 / 密码验证、基于证书的验证等。 比如在一个 Web 应用中,用户输入用户名和密码登录,Shiro 会对输入的凭证与存储在数据源(如数据库、文件系统)中的凭证进行比对,以确认用户身份的合法性。
- 授权: 确定 “已认证的用户” 被允许做什么,即访问控制。Shiro 支持基于角色的访问控制(RBAC), 也支持更细粒度的基于资源和权限的访问控制。例如,在一个企业管理系统中,普通员工角色只能查看自己的任务信息,而管理员角色可以进行系统设置、用户管理等操作,Shiro 可以通过配置角色和权限来实现这种访问控制。
- 加密:对数据进行加密处理,以保护数据的保密性和完整性。Shiro 提供了常用的加密算法支持,如 MD5、SHA 等, 并且还支持加盐(salt)加密,增加破解难度。比如存储用户密码时,使用 Shiro 的加密功能对密码进行加密存储,当用户登录输入密码时,再对输入的密码进行加密并与存储的加密密码进行比对。
- 会话管理:管理用户的会话,即使在无状态的应用(如 Web 应用)中也能跟踪用户的活动。Shiro 的会话可以脱离 Web 容器独立存在,这使得它不仅适用于 Web 应用,也适用于非 Web 的 Java 应用,如桌面应用等。例如,在一个电商应用中,用户在浏览商品、添加购物车等操作过程中,Shiro 的会话管理可以记录用户的操作信息,保证用户在整个购物流程中的状态一致性。
核心组件
- Subject:即 “当前操作用户”,可以是一个通过浏览器请求的用户,也可以是一个运行的程序。Subject 代表了与应用交互的 “人” 或 “事物”,内部维护了与安全相关的状态,如身份认证信息、角色、权限等。
- SecurityManager:Shiro 的核心,协调 Shiro 内部各种安全组件,管理 Subject。它就像是 Shiro 的 “大管家”,负责处理 Subject 的认证、授权请求,与数据源(如数据库、文件系统)交互获取用户信息和权限信息等。
- Realms:Shiro 与应用安全数据间的桥梁,用于从数据源获取用户的身份验证和授权相关数据,如用户的凭证(用户名 / 密码)、角色、权限等。开发人员可以自定义 Realm,以适应不同的数据源,如从关系型数据库(MySQL、Oracle)、LDAP 服务器等获取安全数据。
应用场景
- Web 应用安全:对 Web 应用的页面、接口进行访问控制,确保只有经过认证和授权的用户才能访问特定资源,同时管理用户的会话,提供安全的登录、注销等功能。
- 企业级应用:在大型企业级应用中,处理不同部门、不同角色用户的复杂权限管理需求,如人力资源管理系统中不同角色的员工对员工信息、考勤信息、薪资信息等的访问权限控制。
- 移动应用后端:为移动应用的后端服务提供安全保障,确保移动客户端与服务器之间数据交互的安全性,对用户进行身份验证和授权,保护后端接口不被非法访问。
优势
- 易于集成:Shiro 可以很方便地集成到各种 Java 应用中,无论是基于 Spring、Struts 等框架的 Web 应用,还是独立的 Java SE 应用,都能快速整合 Shiro 实现安全功能。
- 功能全面:涵盖了身份验证、授权、加密、会话管理等多个方面的安全功能,开发人员无需再去整合多个不同的安全组件。
- 灵活性高:开发人员可以根据应用的实际需求,灵活自定义 Realm、授权策略等,以适应不同的业务场景和安全要求。
Shiro 核心工作流程(以 Web 应用登录为例)
Shiro 的安全逻辑围绕 Subject(用户) 和 SecurityManager(安全管理器) 展开,典型的登录认证流程如下,可清晰理解其内部协作机制:
1.用户发起登录请求:前端提交用户名 / 密码,后端接收请求后,通过 SecurityUtils.getSubject() 获取当前 Subject(此时 Subject 处于 “未认证” 状态)。
2.封装认证凭证:创建 UsernamePasswordToken 对象,将用户名 / 密码封装为 Shiro 可识别的 “认证令牌”。
3.触发认证流程:调用 subject.login(token) 方法,Shiro 会将认证请求委托给核心组件 SecurityManager。
4.SecurityManager 转发请求:SecurityManager 本身不直接处理认证,而是将请求转发给配置好的 Realm(数据桥梁)。
5.Realm 验证凭证:Realm 从数据库 / 缓存中查询该用户的真实密码(通常是加密后的密码),与 token 中的密码(加密后)进行比对:
若比对成功:Realm 返回包含用户角色 / 权限的 AuthenticationInfo 对象,SecurityManager 会将认证状态更新到 Subject,此时 subject.isAuthenticated() 结果为 true。
若比对失败:抛出 IncorrectCredentialsException(密码错误)、UnknownAccountException(账号不存在)等异常,登录失败。
认证结果处理:后端根据是否抛出异常,向前端返回登录成功 / 失败的响应,成功后 Subject 可后续进行授权校验。
一个例子【验证,授权]:
思路:
初始化:加载配置 → 创建 SecurityManager → 绑定到环境。
认证:subject.login(token) → 调用 Realm 的 doGetAuthenticationInfo → 校验用户名密码 → 认证成功。
授权:调用 hasRole/isPermitted → 触发 Realm 的 doGetAuthorizationInfo → 获取角色 / 权限 → 校验并返回结果。
//整个流程的核心是 自定义 Realm,它同时处理认证(验证用户身份)和授权(分配角色权限),是 Shiro 与业务数据的桥梁。
添加依赖:
<!-- shiro的核心依赖--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><!-- 测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!--日志--><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.3</version></dependency>
shiro-test.ini:
[main]
# 声明自定义 Realm:给类取一个别名(myClass),指向实际的类路径
myClass=com.my.MyRealm
#注册realm到securityManager中
#作用:将前面声明的 myClass(即你的 MyRealm)注册到 Shiro 的 SecurityManager 中。
#SecurityManager 是 Shiro 的核心管理器,必须知道使用哪个 Realm 来获取用户数据(认证和授权信息),
#否则 Shiro 无法完成认证授权。
securityManager.realms=$myClass
ShiroTest:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;public class ShiroTest {@Testpublic void testLogin() {//!!!这样理解更易理解//我们写的.ini文件是 “原材料清单+组装规则”;//工厂是 “按规则加工的机器”;//SecurityManager 是 “最终加工好的核心设备”;//后续 Subject(用户)的登录、权限校验,都要靠这个 “核心设备” 来协调。// 1. 根据配置文件创建「生产SecurityManager的工厂Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-test.ini");//2.通过工厂获取SecurityManagerSecurityManager securityManager = factory.getInstance();//3.将SecurityManager绑定到当前运行环境//→ 把 SecurityManager 存入 ThreadLocal(线程私有变量)//→ 目的是让当前线程中所有需要 SecurityManager 的地方(比如获取 Subject)都能拿到它SecurityUtils.setSecurityManager(securityManager);//→ 内部会先从 ThreadLocal 中取出之前存入的 SecurityManager//→ 再通过 SecurityManager 创建 / 获取当前用户的 Subject 对象//→ 所以 Subject 可以理解为:SecurityManager 根据当前交互对象(用户 / 程序)生成的 “安全操作代理”//简单说就是:先把 SecurityManager 放到 “线程全局仓库”,Subject 需要时就从这个仓库里找 SecurityManager 帮忙创建自己。//这种设计的好处是:在多线程环境下(比如 Web 应用同时处理多个用户请求),//每个线程的 SecurityManager 和 Subject 都是独立的,不会互相干扰(比如用户 A 的登录状态不会影响用户 B)。Subject subject = SecurityUtils.getSubject();String name="zqn";String password="123456";//用户输入(username/password)//→ 封装成 UsernamePasswordToken(凭证信封)//→ subject.login(token)(启动认证)//→ Subject 转发给 SecurityManager//→ SecurityManager 委托给 Authenticator//→ Authenticator 调用 Realm 的 doGetAuthenticationInfo(查数据库)//→ Shiro 比对凭证 → 认证成功/失败UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(name,password);//执行login-->realm域中的认证方法subject.login(usernamePasswordToken);//认证成功后,首次调用这些方法时,会触发 doGetAuthorizationInfoSystem.out.println(subject.hasRole("role1"));//true 检查角色 → 触发授权System.out.println(subject.hasRole("role2"));//true 检查权限 → 触发授权//检查用户是否有 user:save 权限 → 授权方法中添加了该权限,返回 true。System.out.println(subject.isPermitted("user:save"));//trueSystem.out.println(subject.isPermitted("user:update"));//trueSystem.out.println(subject.isPermitted("user:find"));//true}
}
MyRealm:
package com.my;import org.apache.shiro.authc.*;
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.ArrayList;
import java.util.List;public class MyRealm extends AuthorizingRealm {//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行授权方法");//1.获取安全数据 username,用户idString username = (String)principalCollection.getPrimaryPrincipal();// 2. 模拟查询数据库获取角色和权限(实际开发中会从数据库查询)List<String> perms = new ArrayList<String>();perms.add("user:save");perms.add("user:update");// 硬编码给用户分配四个权限perms.add("user:delete");perms.add("user:find");List<String> roles = new ArrayList<String>();roles.add("role1");roles.add("role2");// 硬编码给用户分配两个角色// 3. 封装授权信息并返回SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//设置权限集合info.addStringPermissions(perms); // 设置角色info.addRoles(roles); // 设置权限return info; //设置角色集合}//验证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行认证方法");UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;String username = upToken.getUsername();char[] passwd = upToken.getPassword();String password = new String(passwd);if ("zqn".equals(username) && "123456".equals(password)) {SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, "myRealm");//1.安全数据,2.密码。3。当前realm域名称return info;} else {//6.失败,抛出异常或返回nullthrow new RuntimeException("用户名或密码错误");}}
}
结果: