Apache Shiro安全框架詳解:認(rèn)證、授權(quán)與加密功能

2024-12-27 14:11 更新

大家好,我是 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 安全框架。

Shiro 的核心概念

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

  1. Subject
    Subject 是 Shiro 框架中一個(gè)核心的接口,表示應(yīng)用中的“用戶”或“實(shí)體”,用于交互和存儲(chǔ)認(rèn)證狀態(tài)。通常通過 SecurityUtils.getSubject() 獲取當(dāng)前的 Subject。它代表了用戶的身份信息和權(quán)限數(shù)據(jù)。

  1. SecurityManager
    SecurityManager 是 Shiro 的核心控制器,負(fù)責(zé)管理所有的安全操作和認(rèn)證。通過配置 SecurityManager,可以控制用戶的認(rèn)證、授權(quán)、會(huì)話等管理。

  1. Realm
    Realm 是 Shiro 從數(shù)據(jù)源獲取用戶、角色和權(quán)限信息的途徑。通過實(shí)現(xiàn)自定義的 Realm,可以將 Shiro 與數(shù)據(jù)庫、LDAP、文件等數(shù)據(jù)源整合。Shiro 會(huì)把用戶的認(rèn)證和授權(quán)數(shù)據(jù)從 Realm 中獲取。

  1. Session
    Shiro 自帶會(huì)話管理,不依賴于 Servlet 容器提供的會(huì)話。即使在非 Web 環(huán)境下,也可以使用 Shiro 的會(huì)話管理。Shiro 的會(huì)話管理提供了更細(xì)致的控制,比如會(huì)話超時(shí)、存儲(chǔ)和共享等功能。

  1. Authentication(認(rèn)證)
    認(rèn)證是指驗(yàn)證用戶身份的過程。Shiro 提供了簡單的 API 來實(shí)現(xiàn)認(rèn)證過程,比如 subject.login(token)。在實(shí)際應(yīng)用中,通常通過用戶名和密碼的組合進(jìn)行認(rèn)證,但 Shiro 也支持其他方式(如 OAuth2、JWT 等)。

  1. Authorization(授權(quán))
    授權(quán)是指驗(yàn)證用戶是否具備某些權(quán)限或角色的過程。Shiro 支持基于角色和基于權(quán)限的授權(quán),允許更精細(xì)的權(quán)限控制。通過 subject.hasRolesubject.isPermitted 方法,開發(fā)者可以檢查用戶的角色和權(quán)限。

  1. Cryptography(加密)
    Shiro 內(nèi)置了加密功能,提供對密碼和敏感信息的加密和解密支持。它支持多種加密算法,并且在密碼存儲(chǔ)時(shí)支持散列和鹽值。

Shiro 的主要功能和優(yōu)勢

V 哥總結(jié)幾點(diǎn)Shiro 的主要功能和優(yōu)勢,這個(gè)在面試時(shí)吹牛逼用得到。

  1. 易于集成
    Shiro 的 API 設(shè)計(jì)簡單,易于集成到各種 Java 應(yīng)用中。開發(fā)者可以基于 Shiro 提供的默認(rèn)實(shí)現(xiàn)快速搭建一個(gè)基本的安全架構(gòu),也可以根據(jù)需要自定義各種功能。

  1. 獨(dú)立的會(huì)話管理
    與基于 Web 容器的會(huì)話管理不同,Shiro 提供了跨環(huán)境的會(huì)話管理,可以應(yīng)用于 Web 和非 Web 的環(huán)境,增加了應(yīng)用的靈活性。

  1. 權(quán)限控制簡單而靈活
    Shiro 的權(quán)限管理可以通過配置文件、注解或代碼實(shí)現(xiàn),提供了細(xì)粒度的訪問控制。通過權(quán)限和角色的組合,開發(fā)者可以非常靈活地控制訪問權(quán)限。

  1. 支持多種數(shù)據(jù)源
    Shiro 可以從多種數(shù)據(jù)源(如數(shù)據(jù)庫、LDAP、文件等)獲取用戶和權(quán)限信息,方便與各種現(xiàn)有系統(tǒng)整合。

  1. 支持 Web 和非 Web 環(huán)境
    Shiro 不僅可以在 Web 應(yīng)用中使用,也支持在桌面應(yīng)用或微服務(wù)等環(huán)境中使用。

Shiro 的基本使用示例

光講概念不是 V 哥風(fēng)格,接下來,通過一個(gè)典型的 Shiro 應(yīng)用來了解一下如何使用,包含配置 SecurityManager、配置 Realm、進(jìn)行認(rèn)證和授權(quán)等步驟。

  1. 配置 Shiro 環(huán)境 可以通過 shiro.ini 文件配置 Shiro,也可以通過代碼進(jìn)行配置。

   [main]
   # 配置 SecurityManager
   securityManager = org.apache.shiro.mgt.DefaultSecurityManager


   # 配置 Realm
   myRealm = com.wg.MyCustomRealm
   securityManager.realms = $myRealm

  1. 創(chuàng)建自定義 Realm

自定義 Realm 通過繼承 AuthorizingRealm 并實(shí)現(xiàn) doGetAuthenticationInfodoGetAuthorizationInfo 方法來提供用戶和權(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;
       }
   }

  1. 使用 Shiro 進(jìn)行認(rèn)證和授權(quán)

   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ù):

  1. 用戶服務(wù):負(fù)責(zé)用戶的注冊、登錄、認(rèn)證等操作。
  2. 訂單服務(wù):允許用戶創(chuàng)建、查看、刪除訂單,并限制訪問權(quán)限。

咱們來看一下,這個(gè)應(yīng)該怎么設(shè)計(jì)呢?

微服務(wù)案例設(shè)計(jì)

在這個(gè)場景中,我們需要以下幾項(xiàng)核心功能:

  1. 用戶認(rèn)證:用戶通過用戶名和密碼登錄。
  2. 權(quán)限控制:僅管理員能刪除訂單,普通用戶只能查看和創(chuàng)建訂單。
  3. Token機(jī)制:使用 JWT Token(JSON Web Token)來管理用戶的登錄狀態(tài),實(shí)現(xiàn)無狀態(tài)認(rèn)證,使得在分布式環(huán)境下不依賴于單一會(huì)話。
  4. 跨服務(wù)認(rèn)證:訂單服務(wù)在接收到請求時(shí),檢查并驗(yàn)證用戶的身份和權(quán)限。

架構(gòu)和技術(shù)選型

  • Spring Boot:用于快速搭建微服務(wù)。
  • Shiro:實(shí)現(xiàn)認(rèn)證、授權(quán)。
  • JWT:生成和驗(yàn)證 Token,保持無狀態(tài)認(rèn)證。
  • Spring Data JPA:訪問數(shù)據(jù)庫存儲(chǔ)用戶和訂單數(shù)據(jù)。

系統(tǒng)結(jié)構(gòu)

+------------------+      +---------------------+
|   用戶服務(wù)        |      |     訂單服務(wù)        |
|                  |      |                     |
| 用戶注冊、登錄    |      |   查看、創(chuàng)建、刪除訂單|
+------------------+      +---------------------+
            |                    |
            |----用戶 Token ------|

步驟:實(shí)現(xiàn)微服務(wù)中的認(rèn)證和授權(quán)

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 過濾器實(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;
        }
    }
}

3. 實(shí)現(xiàn)自定義 Realm

自定義 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;
    }
}

4. 創(chuàng)建 JWT 工具類

編寫一個(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());
    }
}

5. 編寫用戶服務(wù)和訂單服務(wù)接口

用戶服務(wù)接口

用戶服務(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ù)接口

訂單服務(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,一輩子。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號