大家好,我是 V 哥。Apache Shiro 是一個強大且靈活的 Java 安全框架,專注於提供認證、授權、會話管理和加密功能。它常用於保護 Java 應用的訪問控制,特別是在 Web 應用中。相比於 Spring Security,Shiro 的設計更簡潔,適合輕量級應用,並且在許多方面具有更好的易用性和擴充套件性,今天 V 哥就來聊聊 Shiro 安全框架。
Shiro 的核心概念
按照慣例,和 V 哥一起來了解一下 Shiro 的核心概念:
- Subject
Subject 是 Shiro 框架中一個核心的介面,表示應用中的“使用者”或“實體”,用於互動和儲存認證狀態。通常透過SecurityUtils.getSubject()
獲取當前的 Subject。它代表了使用者的身份資訊和許可權資料。 - SecurityManager
SecurityManager 是 Shiro 的核心控制器,負責管理所有的安全操作和認證。透過配置 SecurityManager,可以控制使用者的認證、授權、會話等管理。 - Realm
Realm 是 Shiro 從資料來源獲取使用者、角色和許可權資訊的途徑。透過實現自定義的 Realm,可以將 Shiro 與資料庫、LDAP、檔案等資料來源整合。Shiro 會把使用者的認證和授權資料從 Realm 中獲取。 - Session
Shiro 自帶會話管理,不依賴於 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 等依賴:
<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>
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,一輩子。