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

Spring AOP 實現權限控制:解決切面中獲取 Session 的核心難題

     編輯:Java 相關知識

核心挑戰

AOP 切面類是普通 Spring Bean,無法直接注入 HttpServletRequest/HttpSession。權限校驗卻必須依賴 Session 中的用戶資訊。如何在切面中優雅地獲取 Web 容器上下文對象?

解決方案:ThreadLocal + Filter 橋接

利用 ThreadLocal 實現線程隔離的上下文儲存,配合 Filter 在請求進入時將 request/response/session 綁定到當前線程,切面即可隨時取用。

核心組件實現

1. 上下文持有類

public class SysContext {
    private static final ThreadLocal<HttpServletRequest> requestLocal = new ThreadLocal<>();
    private static final ThreadLocal<HttpServletResponse> responseLocal = new ThreadLocal<>();

    public static HttpServletRequest getRequest() { return requestLocal.get(); }
    public static void setRequest(HttpServletRequest request) { requestLocal.set(request); }
    
    public static HttpServletResponse getResponse() { return responseLocal.get(); }
    public static void setResponse(HttpServletResponse response) { responseLocal.set(response); }
    
    public static HttpSession getSession() {
        HttpServletRequest request = getRequest();
        return request != null ? request.getSession() : null;
    }
    
    // 清理防止內存洩漏(建議在 Filter 最後調用)
    public static void clear() {
        requestLocal.remove();
        responseLocal.remove();
    }
}

2. 請求過濾器

@WebFilter("/*") // 或在 web.xml 配置
public class GetContextFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        try {
            SysContext.setRequest((HttpServletRequest) request);
            SysContext.setResponse((HttpServletResponse) response);
            chain.doFilter(request, response);
        } finally {
            SysContext.clear(); // 關鍵:請求結束清理 ThreadLocal
        }
    }
    // init/destroy 省略
}

3. web.xml 配置(非 Servlet 3.0+ 環境)

<filter>
    <filter-name>contextFilter</filter-name>
    <filter-class>com.example.filter.GetContextFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>contextFilter</filter-name>
    <url-pattern>/*</url-pattern> <!-- 必須放在最後,確保攔截所有請求 -->
</filter-mapping>

4. 權限切面實現

@Aspect
@Component
public class AdminAspect {
    @Before("execution(* com.example.action.AdminAction.getPrivileges())")
    public void adminPrivilegeCheck() throws Throwable {
        HttpSession session = SysContext.getSession();
        HttpServletRequest request = SysContext.getRequest();
        HttpServletResponse response = SysContext.getResponse();
        
        if (session == null) {
            throw new IllegalStateException("Session 不存在,請檢查 Filter 配置順序");
        }
        
        String userName = (String) session.getAttribute("userName");
        if (userName == null || userName.isEmpty()) {
            // 無權限:設置錯誤訊息並轉發到錯誤頁
            request.setAttribute("msg", "{\"res\":\"無權限\"}");
            request.getRequestDispatcher("/jsp/json.jsp").forward(request, response);
            return; // 終止後續執行
        }
        // 有權限:繼續執行目標方法
    }
}

5. Spring 配置啟用 AOP

<aop:aspectj-autoproxy />
<bean id="adminAspect" class="com.example.aop.AdminAspect" />

關鍵原理深度解析

ThreadLocal 為什麼可行?

  • Servlet 容器採用「每請求一線程」模型
  • ThreadLocal 提供線程獨享的變量副本,天然隔離併發請求
  • Filter 執行於同一線程,切面也在同一線程,自然共享上下文

必須注意的坑

  1. 必須清理:finally 區塊調用 SysContext.clear(),否則線程池復用導致內存洩漏、上下文串聯
  2. Filter 順序:需在所有業務 Filter 之前執行,確保上下文就緒
  3. 非 Web 環境:定時任務、測試環境無 Request,getRequest() 返回 null,切面需做非空防禦

現代替代方案

Spring 3.2+ 提供 RequestContextHolder 內建實現:

HttpServletRequest request = ((ServletRequestAttributes) 
    RequestContextHolder.getRequestAttributes()).getRequest();

原理相同(同樣基於 ThreadLocal),但框架已幫你封裝好,建議優先使用。

總結

「ThreadLocal + Filter」是 Spring AOP 訪問 Web 上下文的經典模式,理解其背後的「線程邊界」本質,能舉一反三解決類似問題(如審計日誌、多租戶上下文傳遞等)。

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