大家好,我是 V 哥。Apache Shiro 是一個(gè)強(qiáng)大且靈活的 Java 安全框架,專注于提供認(rèn)證、授權(quán)、會(huì)話管理和加密功能。它常用于保護(hù) Java 應(yīng)用的訪問控制,特別是在 Web 應(yīng)用中。相比于 Spring Security,Shiro 的設(shè)計(jì)更簡潔,適合輕量級應(yīng)用,并且在許多方面具有更好的易用性和擴(kuò)展性,今天 V 哥就來聊聊 Shiro 安全框架。
按照慣例,和 V 哥一起來了解一下 Shiro 的核心概念:
SecurityUtils.getSubject()
獲取當(dāng)前的 Subject。它代表了用戶的身份信息和權(quán)限數(shù)據(jù)。subject.login(token)
。在實(shí)際應(yīng)用中,通常通過用戶名和密碼的組合進(jìn)行認(rèn)證,但 Shiro 也支持其他方式(如 OAuth2、JWT 等)。subject.hasRole
或 subject.isPermitted
方法,開發(fā)者可以檢查用戶的角色和權(quán)限。V 哥總結(jié)幾點(diǎn)Shiro 的主要功能和優(yōu)勢,這個(gè)在面試時(shí)吹牛逼用得到。
光講概念不是 V 哥風(fēng)格,接下來,通過一個(gè)典型的 Shiro 應(yīng)用來了解一下如何使用,包含配置 SecurityManager、配置 Realm、進(jìn)行認(rèn)證和授權(quán)等步驟。
shiro.ini
文件配置 Shiro,也可以通過代碼進(jìn)行配置。 [main]
# 配置 SecurityManager
securityManager = org.apache.shiro.mgt.DefaultSecurityManager
# 配置 Realm
myRealm = com.wg.MyCustomRealm
securityManager.realms = $myRealm
自定義 Realm 通過繼承 AuthorizingRealm
并實(shí)現(xiàn) doGetAuthenticationInfo
和 doGetAuthorizationInfo
方法來提供用戶和權(quán)限數(shù)據(jù)。
public class MyCustomRealm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 獲取用戶名和密碼等信息,查詢數(shù)據(jù)庫進(jìn)行認(rèn)證
return new SimpleAuthenticationInfo(username, password, getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 獲取用戶角色和權(quán)限信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("admin");
info.addStringPermission("user:read");
return info;
}
}
Subject currentUser = SecurityUtils.getSubject();
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
try {
currentUser.login(token);
System.out.println("認(rèn)證成功");
} catch (AuthenticationException ae) {
System.out.println("認(rèn)證失敗");
}
}
// 檢查權(quán)限
if (currentUser.hasRole("admin")) {
//用輸出模擬一下哈
System.out.println("用戶擁有 admin 角色");
}
if (currentUser.isPermitted("user:read")) {
//用輸出模擬一下哈
System.out.println("用戶具有 user:read 權(quán)限");
}
通過這個(gè)簡單的案例學(xué)習(xí),咱們可以了解 Shiro 的基本使用,但這不是全部,聽V哥繼續(xù)慢慢道來。
這點(diǎn)很重要,強(qiáng)調(diào)一下哈,Shiro 適合需要簡潔易用、安全控制要求靈活的 Java 應(yīng)用,如中小型 Web 應(yīng)用、桌面應(yīng)用、分布式微服務(wù)等。對于大型企業(yè)應(yīng)用或需要集成多種認(rèn)證方式(如 OAuth2、JWT 等)的項(xiàng)目,Spring Security 可能會(huì)更合適。
要在微服務(wù)架構(gòu)中實(shí)現(xiàn)基于 Apache Shiro 的安全認(rèn)證和授權(quán),比如一個(gè)訂單管理系統(tǒng)為例。這個(gè)系統(tǒng)包含兩個(gè)主要服務(wù):
咱們來看一下,這個(gè)應(yīng)該怎么設(shè)計(jì)呢?
在這個(gè)場景中,我們需要以下幾項(xiàng)核心功能:
+------------------+ +---------------------+
| 用戶服務(wù) | | 訂單服務(wù) |
| | | |
| 用戶注冊、登錄 | | 查看、創(chuàng)建、刪除訂單|
+------------------+ +---------------------+
| |
|----用戶 Token ------|
在 pom.xml
文件中,添加 Shiro、JWT 和 Spring Data JPA 等依賴:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
使用 Shiro 的自定義 JWT 過濾器實(shí)現(xiàn)無狀態(tài)認(rèn)證,通過 Token 驗(yàn)證用戶。
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;
}
}
}
自定義 Realm
,從數(shù)據(jù)庫獲取用戶和角色信息,并使用 JWT Token 進(jìn)行無狀態(tài)認(rèn)證。
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();
// 驗(yàn)證 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);
// 添加角色和權(quán)限
authorizationInfo.addRole(user.getRole());
authorizationInfo.addStringPermission(user.getPermission());
return authorizationInfo;
}
}
編寫一個(gè)工具類,用于生成和解析 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());
}
}
用戶服務(wù)提供注冊和登錄 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("登錄失敗");
}
}
訂單服務(wù)在操作訂單時(shí)會(huì)驗(yàn)證用戶的角色和權(quán)限。
@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("無權(quán)限查看訂單");
}
@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("無權(quán)限刪除訂單");
}
}
這個(gè)案例中咱們通過如何使用 Shiro、JWT 和 Spring Boot 來構(gòu)建一個(gè)無狀態(tài)的微服務(wù)認(rèn)證授權(quán)機(jī)制。通過 Shiro 實(shí)現(xiàn)用戶認(rèn)證和權(quán)限控制,使用 JWT 實(shí)現(xiàn)無狀態(tài) Token 驗(yàn)證。在輕量級的分布式微服務(wù)應(yīng)用中,是不是使用 Shiro 感覺更加清爽呢,歡迎評論區(qū)一起討論,關(guān)注威哥愛編程,愛上Java,一輩子。
更多建議: