Shiro安全框架从入门到微服务JWT无状态认证

2026-02-27 02:45:29 世界杯冠军最多

大家好,我是 V 哥。Apache Shiro 是一个强大且灵活的 Java 安全框架,专注于提供认证、授权、会话管理和加密功能。它常用于保护 Java 应用的访问控制,特别是在 Web 应用中。相比于 Spring Security,Shiro 的设计更简洁,适合轻量级应用,并且在许多方面具有更好的易用性和扩展性,今天 V 哥就来聊聊 Shiro 安全框架。

Shiro 的核心概念

按照惯例,和 V 哥一起来了解一下 Shiro 的核心概念:

SubjectSubject 是 Shiro 框架中一个核心的接口,表示应用中的“用户”或“实体”,用于交互和存储认证状态。通常通过 SecurityUtils.getSubject() 获取当前的 Subject。它代表了用户的身份信息和权限数据。

SecurityManagerSecurityManager 是 Shiro 的核心控制器,负责管理所有的安全操作和认证。通过配置 SecurityManager,可以控制用户的认证、授权、会话等管理。

RealmRealm 是 Shiro 从数据源获取用户、角色和权限信息的途径。通过实现自定义的 Realm,可以将 Shiro 与数据库、LDAP、文件等数据源整合。Shiro 会把用户的认证和授权数据从 Realm 中获取。

SessionShiro 自带会话管理,不依赖于 Servlet 容器提供的会话。即使在非 Web 环境下,也可以使用 Shiro 的会话管理。Shiro 的会话管理提供了更细致的控制,比如会话超时、存储和共享等功能。

Authentication(认证)认证是指验证用户身份的过程。Shiro 提供了简单的 API 来实现认证过程,比如 subject.login(token)。在实际应用中,通常通过用户名和密码的组合进行认证,但 Shiro 也支持其他方式(如 OAuth2、JWT 等)。

Authorization(授权)授权是指验证用户是否具备某些权限或角色的过程。Shiro 支持基于角色和基于权限的授权,允许更精细的权限控制。通过 subject.hasRole 或 subject.isPermitted 方法,开发者可以检查用户的角色和权限。

Cryptography(加密)Shiro 内置了加密功能,提供对密码和敏感信息的加密和解密支持。它支持多种加密算法,并且在密码存储时支持散列和盐值。

Shiro 的主要功能和优势

V 哥总结几点Shiro 的主要功能和优势,这个在面试时吹牛逼用得到。

易于集成Shiro 的 API 设计简单,易于集成到各种 Java 应用中。开发者可以基于 Shiro 提供的默认实现快速搭建一个基本的安全架构,也可以根据需要自定义各种功能。

独立的会话管理与基于 Web 容器的会话管理不同,Shiro 提供了跨环境的会话管理,可以应用于 Web 和非 Web 的环境,增加了应用的灵活性。

权限控制简单而灵活Shiro 的权限管理可以通过配置文件、注解或代码实现,提供了细粒度的访问控制。通过权限和角色的组合,开发者可以非常灵活地控制访问权限。

支持多种数据源Shiro 可以从多种数据源(如数据库、LDAP、文件等)获取用户和权限信息,方便与各种现有系统整合。

支持 Web 和非 Web 环境Shiro 不仅可以在 Web 应用中使用,也支持在桌面应用或微服务等环境中使用。

Shiro 的基本使用示例

光讲概念不是 V 哥风格,接下来,通过一个典型的 Shiro 应用来了解一下如何使用,包含配置 SecurityManager、配置 Realm、进行认证和授权等步骤。

配置 Shiro 环境可以通过 shiro.ini 文件配置 Shiro,也可以通过代码进行配置。

[main]

# 配置 SecurityManager

securityManager = org.apache.shiro.mgt.DefaultSecurityManager

# 配置 Realm

myRealm = com.wg.MyCustomRealm

securityManager.realms = $myRealm

创建自定义 Realm

自定义 Realm 通过继承 AuthorizingRealm 并实现 doGetAuthenticationInfo 和 doGetAuthorizationInfo 方法来提供用户和权限数据。

public class MyCustomRealm extends AuthorizingRealm {

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

// 获取用户名和密码等信息,查询数据库进行认证

return new SimpleAuthenticationInfo(username, password, getName());

}

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

// 获取用户角色和权限信息

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

info.addRole("admin");

info.addStringPermission("user:read");

return info;

}

}

使用 Shiro 进行认证和授权

Subject currentUser = SecurityUtils.getSubject();

if (!currentUser.isAuthenticated()) {

UsernamePasswordToken token = new UsernamePasswordToken("username", "password");

try {

currentUser.login(token);

System.out.println("认证成功");

} catch (AuthenticationException ae) {

System.out.println("认证失败");

}

}

// 检查权限

if (currentUser.hasRole("admin")) {

//用输出模拟一下哈

System.out.println("用户拥有 admin 角色");

}

if (currentUser.isPermitted("user:read")) {

//用输出模拟一下哈

System.out.println("用户具有 user:read 权限");

}

通过这个简单的案例学习,咱们可以了解 Shiro 的基本使用,但这不是全部,听V哥继续慢慢道来。

场景案例

这点很重要,强调一下哈,Shiro 适合需要简洁易用、安全控制要求灵活的 Java 应用,如中小型 Web 应用、桌面应用、分布式微服务等。对于大型企业应用或需要集成多种认证方式(如 OAuth2、JWT 等)的项目,Spring Security 可能会更合适。

要在微服务架构中实现基于 Apache Shiro 的安全认证和授权,比如一个订单管理系统为例。这个系统包含两个主要服务:

用户服务:负责用户的注册、登录、认证等操作。

订单服务:允许用户创建、查看、删除订单,并限制访问权限。

咱们来看一下,这个应该怎么设计呢?

微服务案例设计

在这个场景中,我们需要以下几项核心功能:

用户认证:用户通过用户名和密码登录。

权限控制:仅管理员能删除订单,普通用户只能查看和创建订单。

Token机制:使用 JWT Token(JSON Web Token)来管理用户的登录状态,实现无状态认证,使得在分布式环境下不依赖于单一会话。

跨服务认证:订单服务在接收到请求时,检查并验证用户的身份和权限。

架构和技术选型

Spring Boot:用于快速搭建微服务。

Shiro:实现认证、授权。

JWT:生成和验证 Token,保持无状态认证。

Spring Data JPA:访问数据库存储用户和订单数据。

系统结构

+------------------+ +---------------------+

| 用户服务 | | 订单服务 |

| | | |

| 用户注册、登录 | | 查看、创建、删除订单|

+------------------+ +---------------------+

| |

|----用户 Token ------|

步骤:实现微服务中的认证和授权

1. 引入必要的依赖

在 pom.xml 文件中,添加 Shiro、JWT 和 Spring Data JPA 等依赖:

org.apache.shiro

shiro-spring

1.8.0

io.jsonwebtoken

jjwt

0.9.1

org.springframework.boot

spring-boot-starter-data-jpa

org.springframework.boot

spring-boot-starter-web

com.h2database

h2

2. 配置 Shiro 与 JWT 过滤器

使用 Shiro 的自定义 JWT 过滤器实现无状态认证,通过 Token 验证用户。

public class JwtFilter extends BasicHttpAuthenticationFilter {

@Override

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

HttpServletRequest httpServletRequest = (HttpServletRequest) request;

String token = httpServletRequest.getHeader("Authorization");

if (StringUtils.isBlank(token)) {

return false;

}

try {

// 解析 JWT token

JwtToken jwtToken = new JwtToken(token);

getSubject(request, response).login(jwtToken);

return true;

} catch (Exception e) {

return false;

}

}

}

3. 实现自定义 Realm

自定义 Realm,从数据库获取用户和角色信息,并使用 JWT Token 进行无状态认证。

public class JwtRealm extends AuthorizingRealm {

@Override

public boolean supports(AuthenticationToken token) {

return token instanceof JwtToken;

}

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

String jwtToken = (String) token.getPrincipal();

// 验证 Token

String username = JwtUtil.getUsernameFromToken(jwtToken);

if (username == null) {

throw new AuthenticationException("Token 无效");

}

// 查询用户

User user = userService.findByUsername(username);

if (user == null) {

throw new AuthenticationException("用户不存在");

}

return new SimpleAuthenticationInfo(jwtToken, jwtToken, getName());

}

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

String username = JwtUtil.getUsernameFromToken(principals.toString());

SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

User user = userService.findByUsername(username);

// 添加角色和权限

authorizationInfo.addRole(user.getRole());

authorizationInfo.addStringPermission(user.getPermission());

return authorizationInfo;

}

}

4. 创建 JWT 工具类

编写一个工具类,用于生成和解析 JWT Token。

public class JwtUtil {

//这里替换一下你自己的secret_key

private static final String SECRET_KEY = "这里打码了";

public static String generateToken(String username) {

return Jwts.builder()

.setSubject(username)

.setIssuedAt(new Date())

.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour

.signWith(SignatureAlgorithm.HS256, SECRET_KEY)

.compact();

}

public static String getUsernameFromToken(String token) {

Claims claims = Jwts.parser()

.setSigningKey(SECRET_KEY)

.parseClaimsJws(token)

.getBody();

return claims.getSubject();

}

public static boolean isTokenExpired(String token) {

Claims claims = Jwts.parser()

.setSigningKey(SECRET_KEY)

.parseClaimsJws(token)

.getBody();

return claims.getExpiration().before(new Date());

}

}

5. 编写用户服务和订单服务接口

用户服务接口

用户服务提供注册和登录 API。

@RestController

@RequestMapping("/user")

public class UserController {

@PostMapping("/register")

public ResponseEntity register(@RequestBody User user) {

userService.save(user);

return ResponseEntity.ok("用户注册成功");

}

@PostMapping("/login")

public ResponseEntity login(@RequestBody User user) {

User dbUser = userService.findByUsername(user.getUsername());

if (dbUser != null && dbUser.getPassword().equals(user.getPassword())) {

String token = JwtUtil.generateToken(user.getUsername());

return ResponseEntity.ok(token);

}

return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("登录失败");

}

}

订单服务接口

订单服务在操作订单时会验证用户的角色和权限。

@RestController

@RequestMapping("/order")

public class OrderController {

@GetMapping("/{orderId}")

public ResponseEntity getOrder(@PathVariable Long orderId) {

Subject currentUser = SecurityUtils.getSubject();

if (currentUser.isPermitted("order:read")) {

// 查询订单

return ResponseEntity.ok("订单详情");

}

return ResponseEntity.status(HttpStatus.FORBIDDEN).body("无权限查看订单");

}

@DeleteMapping("/{orderId}")

public ResponseEntity deleteOrder(@PathVariable Long orderId) {

Subject currentUser = SecurityUtils.getSubject();

if (currentUser.hasRole("admin")) {

// 删除订单

return ResponseEntity.ok("订单已删除");

}

return ResponseEntity.status(HttpStatus.FORBIDDEN).body("无权限删除订单");

}

}

最后

这个案例中咱们通过如何使用 Shiro、JWT 和 Spring Boot 来构建一个无状态的微服务认证授权机制。通过 Shiro 实现用户认证和权限控制,使用 JWT 实现无状态 Token 验证。在轻量级的分布式微服务应用中,是不是使用 Shiro 感觉更加清爽呢,欢迎评论区一起讨论,关注威哥爱编程,爱上Java,一辈子。