首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Apache Shiro 權限認證完整指南:核心概念、授權方式與 Web 整合實戰

     編輯:Java 綜合教學

Apache Shiro 權限認證完整指南:核心概念、授權方式與 Web 整合實戰

在現代企業級應用開發中,權限控制是系統安全的核心防線。Apache Shiro 作為一個強大且輕量級的 Java 安全框架,提供了認證、授權、加密、會話管理等完整的安全解決方案。本文將深入解析 Shiro 的權限認證核心要素、三種授權方式、編程式授權實戰,以及與 Web 應用的整合配置,幫助開發者快速掌握 Shiro 在實際項目中的應用。

一、權限認證的三大核心要素

權限認證的本質是控制「誰能訪問什麼資源」。在 Shiro 架構中,這圍繞著三個核心概念展開:

  • 權限:操作資源的具體權利,例如訪問特定 URL、對數據執行增刪改查操作。權限是授權的最小單位,通常以「資源:操作」格式表示(如 user:select、user:delete)。
  • 角色:權限的集合,一種角色可包含多個權限。例如「操作員」角色可能包含「查看賬單」、「結賬操作」等多種權限。角色簡化了權限分配,實現基於角色的訪問控制(RBAC)。
  • 用戶:即身份認證中的 Subject 主體,代表當前與應用交互的實體(可能是人、第三方服務等)。

Shiro 權限認證核心要素關係圖

二、Shiro 三種授權方式詳解

Shiro 提供了三種授權方式,開發者可根據場景靈活選擇:

1. 編程式授權

在 Java 代碼中通過 API 進行授權判斷,支持基於角色和基於權限兩種模式。適用於複雜業務邏輯中需要動態判斷權限的場景。

2. 註解式授權

使用 @RequiresRoles、@RequiresPermissions 等註解標注在方法或類上,聲明式地實現授權。代碼侵入性小,適合 Spring 等框架整合環境。

3. JSP 標籤授權

通過 <shiro:guest>、<shiro:user>、<shiro:hasRole> 等標籤,在頁面層面直接控制內容顯示。這是 Shiro 靈活性的體現,能大幅減少後台邏輯判斷,前後端分離架構中同樣適用於模板引擎。

三、編程式授權實戰:從配置到代碼

1. INI 配置文件定義用戶與角色

[users]
java1234=123456,role1,role2
jack=123,role1

上述配置表示:用戶 java1234jack 僅擁有 role1 角色。

2. Shiro 工具類封裝登錄邏輯

public class ShiroUtil {
    public static Subject login(String configFile, String userName, String password) {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFile);
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        try {
            currentUser.login(token);
            System.out.println("身份認證成功!");
        } catch (AuthenticationException e) {
            e.printStackTrace();
            System.out.println("身份認證失敗!");
        }
        return currentUser;
    }
}

3. 角色授權驗證測試

public class RoleTest {
    @Test
    public void testHasRole() {
        Subject currentUser = ShiroUtil.login("classpath:shiro_role.ini", "java1234", "123456");
        System.out.println(currentUser.hasRole("role1") ? "有role1角色" : "無role1角色");
        
        boolean[] results = currentUser.hasRoles(Arrays.asList("role1", "role2", "role3"));
        System.out.println(results[0] ? "有role1" : "無role1");
        System.out.println(results[1] ? "有role2" : "無role2");
        System.out.println(results[2] ? "有role3" : "無role3");
        System.out.println(currentUser.hasAllRoles(Arrays.asList("role1", "role2")) ? "同時擁有role1,role2" : "不全擁有");
        currentUser.logout();
    }
    
    @Test
    public void testCheckRole() {
        Subject currentUser = ShiroUtil.login("classpath:shiro_role.ini", "java1234", "123456");
        currentUser.checkRole("role1");                    // 無角色拋出異常
        currentUser.checkRoles(Arrays.asList("role1", "role2"));
        currentUser.checkRoles("role1", "role2", "role3");
        currentUser.logout();
    }
}

關鍵區別:hasRole* 返回 boolean,適合條件判斷;checkRole* 無權限時拋出 AuthorizationException,適合強制攔截。

4. 基於權限的細粒度授權

[users]
java1234=123456,role1,role2
jack=123,role1

[roles]
role1=user:select
role2=user:add,user:update,user:delete
public class PermissionTest {
    @Test
    public void testIsPermitted() {
        Subject currentUser = ShiroUtil.login("classpath:shiro_permission.ini", "java1234", "123456");
        System.out.println(currentUser.isPermitted("user:select") ? "有查詢權限" : "無查詢權限");
        System.out.println(currentUser.isPermitted("user:update") ? "有更新權限" : "無更新權限");
        
        boolean[] results = currentUser.isPermitted("user:select", "user:update", "user:delete");
        System.out.println(results[0] ? "有select" : "無select");
        System.out.println(results[1] ? "有update" : "無update");
        System.out.println(results[2] ? "有delete" : "無delete");
        System.out.println(currentUser.isPermittedAll("user:select", "user:update") ? "同時擁有select,update" : "不全擁有");
        currentUser.logout();
    }
}

四、Shiro 與 Web 應用整合實戰

1. 依賴引入

核心依賴:shiro-core、shiro-web;日誌:slf4j-api、log4j、commons-logging;Web 支持:javax.servlet-api、jstl。

2. web.xml 配置 Shiro Filter

<listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

3. shiro.ini 核心配置解析

[main]
authc.loginUrl=/login
roles.unauthorizedUrl=/unauthorized.jsp
perms.unauthorizedUrl=/unauthorized.jsp

[users]
java1234=123456,admin
jack=123,teacher
marry=234
json=345

[roles]
admin=user:*
teacher=student:*

[urls]
/login=anon
/admin=authc
/student=roles[teacher]
/teacher=perms["user:create"]

配置要點:

  • anon:匿名訪問,無需認證(如登錄頁)
  • authc:需認證後訪問,未登錄自動跳轉至 loginUrl
  • roles[roleName]:需具備指定角色
  • perms["permission"]:需具備指定權限
  • 未授權統一跳轉 unauthorized.jsp

4. 登錄 Servlet 實現

public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String userName = req.getParameter("userName");
        String password = req.getParameter("password");
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        try {
            subject.login(token);
            resp.sendRedirect("success.jsp");
        } catch (Exception e) {
            req.setAttribute("errorInfo", "用戶名或密碼錯誤");
            req.getRequestDispatcher("login.jsp").forward(req, resp);
        }
    }
}

5. 兩種 Realm 實現方案對比

Text Realm(INI 文件配置)

適用於簡單演示、測試環境。用戶、角色、權限直接寫在 shiro.ini 中,修改需重啟應用,不適合生產環境。

自定義 JDBC Realm(生產環境推薦)

核心步驟:

  1. 建立用戶表、角色表、權限表,主外鍵關聯
  2. 配置 shiro.ini 指定自定義 Realm
  3. 實現 AuthorizingRealm 重寫兩個核心方法
[main]
myRealm=com.java.realm.MyRealm
securityManager.realms=$myRealm
public class MyRealm extends AuthorizingRealm {
    private UserDao userDao = new UserDao();
    private DbUtil dbUtil = new DbUtil();

    /** 授權:為當前用戶賦予角色與權限 */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String userName = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        try (Connection con = dbUtil.getCon()) {
            info.setRoles(userDao.getRoles(con, userName));
            info.setStringPermissions(userDao.getPermissions(con, userName));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return info;
    }

    /** 認證:驗證用戶身份 */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String userName = (String) token.getPrincipal();
        try (Connection con = dbUtil.getCon()) {
            User user = userDao.getByUserName(con, userName);
            if (user != null) {
                return new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

流程解析:訪問受保護資源 → Shiro Filter 攔截 → 未登錄跳轉登錄頁 → 登錄成功創建 Subject → 訪問控制器 → 執行 subject.login() → 進入 Realm 認證/授權方法 → 查詢數據庫 → 返回封裝信息 → Shiro 完成權限判定。

JDBC Realm 架構圖

五、最佳實踐與避坑指南

  • 密碼加密:生產環境必須配置 CredentialsMatcher 使用 MD5/SHA + Salt 加密,不可明文存儲
  • 緩存優化:整合 Ehcache/Redis 緩存授權信息,減少數據庫查詢
  • 會話管理:配置 SessionManager 實現分布式會話共享
  • URL 路徑匹配順序:[urls] 配置按順序匹配,通配符路徑放最後
  • 異常處理:自定義 UnauthorizedException 處理器,返回友好錯誤頁或 JSON

總結

Apache Shiro 以「簡單、靈活、強大」著稱,其核心優勢在於:

  1. 領域模型清晰(Subject、Realm、SecurityManager)
  2. 三種授權方式覆蓋不同場景需求
  3. 與 Web、Spring、Spring Boot 無縫整合
  4. 支持多種數據源

掌握 Shiro 權限認證的關鍵在於理解「認證與授權分離」、「Realm 作為數據橋樑」、「Filter 鏈路攔截機制」。建議從簡單的 INI 配置入手,逐步遷移到 JDBC Realm,再結合緩存、加密、會話管理構建企業級安全體系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved