動態

詳情 返回 返回

適合才最美:Shiro安全框架使用心得 - 動態 詳情

大家好,我是 V 哥。Apache Shiro 是一個強大且靈活的 Java 安全框架,專注於提供認證、授權、會話管理和加密功能。它常用於保護 Java 應用的訪問控制,特別是在 Web 應用中。相比於 Spring Security,Shiro 的設計更簡潔,適合輕量級應用,並且在許多方面具有更好的易用性和擴展性,今天 V 哥就來聊聊 Shiro 安全框架。

Shiro 的核心概念

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

  1. Subject
    Subject 是 Shiro 框架中一個核心的接口,表示應用中的“用户”或“實體”,用於交互和存儲認證狀態。通常通過 SecurityUtils.getSubject() 獲取當前的 Subject。它代表了用户的身份信息和權限數據。
  2. SecurityManager
    SecurityManager 是 Shiro 的核心控制器,負責管理所有的安全操作和認證。通過配置 SecurityManager,可以控制用户的認證、授權、會話等管理。
  3. Realm
    Realm 是 Shiro 從數據源獲取用户、角色和權限信息的途徑。通過實現自定義的 Realm,可以將 Shiro 與數據庫、LDAP、文件等數據源整合。Shiro 會把用户的認證和授權數據從 Realm 中獲取。
  4. Session
    Shiro 自帶會話管理,不依賴於 Servlet 容器提供的會話。即使在非 Web 環境下,也可以使用 Shiro 的會話管理。Shiro 的會話管理提供了更細緻的控制,比如會話超時、存儲和共享等功能。
  5. Authentication(認證)
    認證是指驗證用户身份的過程。Shiro 提供了簡單的 API 來實現認證過程,比如 subject.login(token)。在實際應用中,通常通過用户名和密碼的組合進行認證,但 Shiro 也支持其他方式(如 OAuth2、JWT 等)。
  6. Authorization(授權)
    授權是指驗證用户是否具備某些權限或角色的過程。Shiro 支持基於角色和基於權限的授權,允許更精細的權限控制。通過 subject.hasRolesubject.isPermitted 方法,開發者可以檢查用户的角色和權限。
  7. Cryptography(加密)
    Shiro 內置了加密功能,提供對密碼和敏感信息的加密和解密支持。它支持多種加密算法,並且在密碼存儲時支持散列和鹽值。

Shiro 的主要功能和優勢

V 哥總結幾點Shiro 的主要功能和優勢,這個在面試時吹牛逼用得到。

  1. 易於集成
    Shiro 的 API 設計簡單,易於集成到各種 Java 應用中。開發者可以基於 Shiro 提供的默認實現快速搭建一個基本的安全架構,也可以根據需要自定義各種功能。
  2. 獨立的會話管理
    與基於 Web 容器的會話管理不同,Shiro 提供了跨環境的會話管理,可以應用於 Web 和非 Web 的環境,增加了應用的靈活性。
  3. 權限控制簡單而靈活
    Shiro 的權限管理可以通過配置文件、註解或代碼實現,提供了細粒度的訪問控制。通過權限和角色的組合,開發者可以非常靈活地控制訪問權限。
  4. 支持多種數據源
    Shiro 可以從多種數據源(如數據庫、LDAP、文件等)獲取用户和權限信息,方便與各種現有系統整合。
  5. 支持 Web 和非 Web 環境
    Shiro 不僅可以在 Web 應用中使用,也支持在桌面應用或微服務等環境中使用。

Shiro 的基本使用示例

光講概念不是 V 哥風格,接下來,通過一個典型的 Shiro 應用來了解一下如何使用,包含配置 SecurityManager、配置 Realm、進行認證和授權等步驟。

  1. 配置 Shiro 環境
    可以通過 shiro.ini 文件配置 Shiro,也可以通過代碼進行配置。
   [main]
   # 配置 SecurityManager
   securityManager = org.apache.shiro.mgt.DefaultSecurityManager

   # 配置 Realm
   myRealm = com.wg.MyCustomRealm
   securityManager.realms = $myRealm
  1. 創建自定義 Realm

    自定義 Realm 通過繼承 AuthorizingRealm 並實現 doGetAuthenticationInfodoGetAuthorizationInfo 方法來提供用户和權限數據。

   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;
       }
   }
  1. 使用 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 的安全認證和授權,比如一個訂單管理系統為例。這個系統包含兩個主要服務:

  1. 用户服務:負責用户的註冊、登錄、認證等操作。
  2. 訂單服務:允許用户創建、查看、刪除訂單,並限制訪問權限。

咱們來看一下,這個應該怎麼設計呢?

微服務案例設計

在這個場景中,我們需要以下幾項核心功能:

  1. 用户認證:用户通過用户名和密碼登錄。
  2. 權限控制:僅管理員能刪除訂單,普通用户只能查看和創建訂單。
  3. Token機制:使用 JWT Token(JSON Web Token)來管理用户的登錄狀態,實現無狀態認證,使得在分佈式環境下不依賴於單一會話。
  4. 跨服務認證:訂單服務在接收到請求時,檢查並驗證用户的身份和權限。

架構和技術選型

  • 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,一輩子。

user avatar yqyx36 頭像 chenjiabing666 頭像 wqjiao 頭像 axuicn 頭像 yelongyang 頭像 wls1036 頭像 jrainlau 頭像 xiayifeifandewudongmian 頭像 lizeze 頭像
點贊 9 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.