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

使用 Acegi (Spring Security) 保護 JSF 應用:零代碼配置實現認證授權全流程

     編輯:J2EE

核心理念:安全性配置化,而非代碼化

Acegi (現 Spring Security) 核心承諾:無需編寫 Java 安全代碼,透過 XML 配置即可實現:

  • 未認證用戶訪問受保護資源 → 自動導向登入頁。
  • 認證成功 → 自動重導向至原請求資源。
  • 授權失敗 → 顯示拒絕訪問頁面。

誤區澄清: 登入頁面由 Acegi 提供,而非 JSF。若 JSF 提供登入頁,需手工將憑證傳遞給 Acegi,違背「零代碼配置」初衷,引發整合複雜度爆炸。

web.xml 完整部署描述符


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="https://java.sun.com/xml/ns/javaee">
    <!-- 1. Spring 容器啟動監聽器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/acegi-config.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 2. JSF 配置 -->
    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>server</param-value>
    </context-param>
    <context-param>
        <param-name>javax.faces.CONFIG_FILES</param-name>
        <param-value>/WEB-INF/faces-config.xml</param-value>
    </context-param>
    <listener>
        <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
    </listener>

    <!-- 3. FacesServlet 映射 -->
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.faces</url-pattern>
    </servlet-mapping>

    <!-- 4. Acegi 過濾器鏈 -->
    <filter>
        <filter-name>Acegi Filter Chain Proxy</filter-name>
        <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
        <init-param>
            <param-name>targetClass</param-name>
            <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Acegi Filter Chain Proxy</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

關鍵組件解析

組件角色關鍵點
ContextLoaderListener啟動 Spring 根容器,載入 acegi-config.xmlSpring Bean 定義、事務、數據源在此初始化
ConfigureListener啟動 JSF 配置,掃描 Managed Bean、導航規則、轉換器JSF 運行時環境建立
FacesServletJSF 前端控制器,執行生命週期映射 *.faces,load-on-startup=1 優先初始化
FilterToBeanProxy橋接 Servlet Filter 與 Spring Bean代理 FilterChainProxy,從 Spring 容器獲取真實 Filter 鏈
FilterChainProxy安全過濾器鏈核心包含 AuthenticationProcessingFilter、ExceptionTranslationFilter、FilterSecurityInterceptor 等
filter-mapping /*攔截所有請求確保 JSF 頁面 (*.faces) 先經過安全過濾

啟動序列與請求處理流程

  1. 容器啟動:
    • ContextLoaderListener.contextInitialized() → 建立 Spring 根容器 → 載入 acegi-config.xml 定義的認證管理器、用戶詳情服務、存取決策管理器、過濾器鏈。
    • ConfigureListener.contextInitialized() → 建立 JSF Application 實例、掃描 faces-config.xml、註冊渲染器、驗證器。
    • FacesServlet.init() → 初始化生命週期工廠、視圖處理器。
  2. 用戶請求 /app/page.faces:
    • FilterToBeanProxy.doFilter() → 從 Spring 容器獲取 FilterChainProxy → 執行安全過濾器鏈。
    • AuthenticationProcessingFilter:檢測登入表單提交 (j_security_check),委派 AuthenticationManager 認證。
    • ExceptionTranslationFilter:捕獲 AccessDeniedException、AuthenticationException,觸發登入頁重導向或 403 頁面。
    • FilterSecurityInterceptor:根據 objectDefinitionSource (URL 模式 → 角色) 進行授權決策。
    • 通過安全檢查 → chain.doFilter() → FacesServlet.service() → 執行 JSF 生命週期渲染頁面。

acegi-config.xml 核心配置範例


<beans>
    <!-- 1. 認證管理器 -->
    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref bean="daoAuthenticationProvider"/>
            </list>
        </property>
    </bean>

    <!-- 2. DAO 認證提供者 (資料庫/LDAP) -->
    <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
        <property name="userDetailsService"><ref bean="jdbcUserDetailsService"/></property>
        <property name="passwordEncoder"><ref bean="passwordEncoder"/></property>
    </bean>

    <!-- 3. 用戶詳情服務 -->
    <bean id="jdbcUserDetailsService" class="org.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
        <property name="dataSource"><ref bean="dataSource"/></property>
        <property name="usersByUsernameQuery">SELECT username, password, enabled FROM users WHERE username = ?</property>
        <property name="authoritiesByUsernameQuery">SELECT username, authority FROM authorities WHERE username = ?</property>
    </bean>

    <!-- 4. 密碼編碼器 -->
    <bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/>

    <!-- 5. 過濾器鏈代理 -->
    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /login.jsp*=authenticationProcessingFilter
                /login.faces*=authenticationProcessingFilter
                /**=httpSessionContextIntegrationFilter,exceptionTranslationFilter,filterSecurityInterceptor
            </value>
        </property>
    </bean>

    <!-- 6. 關鍵過濾器 Bean 定義 -->
    <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
        <property name="authenticationManager"><ref bean="authenticationManager"/></property>
        <property name="authenticationFailureUrl">/login.jsp?error=true</property>
        <property name="defaultTargetUrl">/dashboard.faces</property>
        <property name="filterProcessesUrl">/j_security_check</property>
    </bean>
    
    <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
        <property name="authenticationEntryPoint">
            <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
                <property name="loginFormUrl">/login.jsp</property>
                <property name="forceHttps">false</property>
            </bean>
        </property>
        <property name="accessDeniedHandler">
            <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
                <property name="errorPage">/accessDenied.jsp</property>
            </bean>
        </property>
    </bean>
    
    <bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
        <property name="authenticationManager"><ref bean="authenticationManager"/></property>
        <property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
        <property name="objectDefinitionSource">
            <value>
                PATTERN_TYPE_APACHE_ANT
                /admin/**=ROLE_ADMIN
                /user/**=ROLE_USER
                /dashboard.faces=ROLE_USER,ROLE_ADMIN
            </value>
        </property>
    </bean>
    
    <bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
        <property name="decisionVoters">
            <list><bean class="org.acegisecurity.vote.RoleVoter"/></list>
        </property>
    </bean>
    
    <bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
</beans>

JSF 登入頁面 (login.jsp) — 非 JSF 頁面,純 HTML/Form


<form action="j_security_check" method="post">
    帳號: <input type="text" name="j_username" /><br/>
    密碼: <input type="password" name="j_password" /><br/>
    <input type="submit" value="登入" />
    <c:if test="${param.error}"><font color="red">帳號或密碼錯誤</font></c:if>
</form>
  • Action 必須為 j_security_check (對應 filterProcessesUrl)。
  • 欄位名固定:j_username、j_password。
  • 非 JSF 頁面,避免生命週期干擾,純 Servlet 處理。

JSF 頁面中獲取當前用戶資訊


<h:outputText value="歡迎, #{facesContext.externalContext.userPrincipal.name}" />
<h:commandButton value="登出" action="#{authBean.logout}" />

@Named
@RequestScoped
public class AuthBean {
    public String logout() {
        FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
        return "/login.jsp?faces-redirect=true";
    }
}

整合最佳實踐

  1. 登入頁脫離 JSF:純 HTML/Form,避免 JSF 狀態管理干擾認證流程。
  2. URL 模式區分:*.faces 受保護,/login.jsp、/j_security_check、靜態資源 (/css/**, /js/**) 放行。
  3. 角色基於 URL 授權:objectDefinitionSource 使用 Ant 路徑模式,清晰可維護。
  4. CSRF 保護:Spring Security 3.2+ 內建 CSRF Filter,確保 JSF 表單 (h:form) 自動包含 Token。
  5. 叢集 Session 複製:配置 HttpSessionContextIntegrationFilter 配合容器 Session 複製或 Spring Session (Redis)。
  6. 現代化遷移:Acegi → Spring Security 5+,配置改為 Java Config (@EnableWebSecurity + WebSecurityConfigurerAdapter),功能更強 (OAuth2、JWT、Reactive)。

總結

Acegi/Spring Security 以「過濾器鏈 + 配置化安全」架構,實現了 JSF 應用的零代碼安全防護。核心在於 web.xml 正確串聯 ContextLoaderListener、ConfigureListener、FacesServlet、FilterToBeanProxy,並精心設計 acegi-config.xml 中的認證提供者、過濾器鏈順序、URL 授權規則。遵循「登入頁非 JSF、認證交給 Filter、授權基於角色、會話整合 Spring」原則,可構建企業級安全基線。現代專案建議直接採用 Spring Security 5+ Java Config,享受更豐富的認證協議支援與響應式編程模型。

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