- 引入依赖: 添加Shiro、JWT和Redis的依赖项。
- 配置Shiro: 配置Shiro的安全管理器、Realm等。
- 实现JWT工具类: 生成JWT Token、验证Token等功能。
- Controller层: 实现登录接口和受保护资源的访问接口。
首先,确保在pom.xml
文件中添加以下依赖:
<!-- Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.8.0</version> <!-- 替换为最新版本 -->
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version> <!-- 替换为最新版本 -->
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
接下来,我们按照步骤逐步实现。
1. 配置Shiro
在Spring Boot的配置类中配置Shiro,主要是配置安全管理器、Realm等。
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
shiroFilter.setLoginUrl("/login"); // 登录URL
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 配置不需要权限认证的路径,比如登录接口
filterChainDefinitionMap.put("/login", "anon");
// 其他路径都需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilter;
}
@Bean
public SecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
// 注册ShiroDialect,用于Thymeleaf中使用Shiro标签
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
2. 实现JWT工具类
编写JWT工具类来生成Token、验证Token,并结合Redis进行存储管理。
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 生成Token
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
// 从Token中获取用户名
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
// 验证Token是否有效
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
// Token是否过期
public boolean isTokenExpired(String token) {
Date expiration = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getExpiration();
return expiration.before(new Date());
}
// 保存Token到Redis
public void saveTokenToRedis(String username, String token) {
redisTemplate.opsForValue().set(username, token, expiration, TimeUnit.MILLISECONDS);
}
// 从Redis中删除Token
public void deleteTokenFromRedis(String username) {
redisTemplate.delete(username);
}
// 从Redis中获取Token
public String getTokenFromRedis(String username) {
return redisTemplate.opsForValue().get(username);
}
}
3. Controller层
实现登录接口和需要认证的资源接口。
@RestController
public class AuthController {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@PostMapping("/login")
public ResponseEntity<String> login(@RequestParam String username, @RequestParam String password) {
// TODO: 根据用户名和密码验证用户身份,省略具体实现
// 假设验证通过,生成Token并保存到Redis
String token = jwtUtil.generateToken(username);
jwtUtil.saveTokenToRedis(username, token);
return ResponseEntity.ok(token);
}
@GetMapping("/protected/resource")
public ResponseEntity<String> protectedResource(@RequestHeader("Authorization") String authorizationHeader) {
String token = authorizationHeader.substring(7); // 去除Bearer
// 验证Token是否有效,并从Token中获取用户名
String username = jwtUtil.getUsernameFromToken(token);
// TODO: 根据用户名获取受保护资源,省略具体实现
return ResponseEntity.ok("Protected resource accessed by " + username);
}
}