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

Spring Security 2 進階配置實戰 (下):動態資源授權、自定義過濾器鏈與安全上下文訪問

     編輯:Java 相關知識

承接上篇基礎配置,本文深入實戰:啟動時緩存資源授權映射、實現 FilterInvocationDefinitionSource 動態 URL 權限決策、通過 custom-filter 注入自定義攔截器、以及安全上下文編程式訪問模式。

一、啟動時加載資源授權映射到 ServletContext

public class SecurityContextLoader implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
        ServletContext sc = event.getServletContext();
        SecurityManager mgr = WebApplicationContextUtils
            .getWebApplicationContext(sc).getBean("securityManager", SecurityManager.class);
        sc.setAttribute("urlAuthorities", mgr.loadUrlAuthorities());
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        event.getServletContext().removeAttribute("urlAuthorities");
    }
}

web.xml 註冊:

<listener>
  <listener-class>com.example.security.SecurityContextLoader</listener-class>
</listener>

二、SecurityManager 整合 UserDetailsService 與資源加載

@Service("securityManager")
public class SecurityManagerImpl extends HibernateDaoSupport 
        implements UserDetailsService, SecurityManager {

    @Autowired
    public void init(SessionFactory sf) { setSessionFactory(sf); }

    @Override
    public UserDetails loadUserByUsername(String username) {
        List users = getHibernateTemplate().find(
            "FROM User u WHERE u.name=? AND u.disabled=false", username);
        if (users.isEmpty()) throw new UsernameNotFoundException("User not found: " + username);
        return users.get(0);
    }

    @Override
    public Map loadUrlAuthorities() {
        Map map = new HashMap<>();
        List resources = getHibernateTemplate().find(
            "FROM Resource r WHERE r.type=?", "URL");
        for (Resource r : resources) {
            map.put(r.getValue(), r.getRoleAuthorities()); // 如 "/admin/**" → "ROLE_ADMIN"
        }
        return map;
    }
}

三、自定義 FilterInvocationDefinitionSource 實現動態權限決策

public class DynamicResourceFilter implements FilterInvocationDefinitionSource, InitializingBean {
    private UrlMatcher urlMatcher = new AntUrlPathMatcher(); // 支援 Ant 路徑模式
    private boolean lowercase = true;

    @Override
    public void afterPropertiesSet() {
        if (lowercase) ((AntUrlPathMatcher) urlMatcher).setRequiresLowerCaseUrl(true);
    }

    @Override
    public ConfigAttributeDefinition getAttributes(Object object) {
        FilterInvocation fi = (FilterInvocation) object;
        String requestUrl = fi.getRequestUrl();
        Map urlAuthorities = getUrlAuthorities(fi);

        for (Map.Entry entry : urlAuthorities.entrySet()) {
            if (urlMatcher.pathMatchesUrl(entry.getKey(), requestUrl)) {
                ConfigAttributeEditor editor = new ConfigAttributeEditor();
                editor.setAsText(entry.getValue()); // 解析 "ROLE_ADMIN,ROLE_USER"
                return (ConfigAttributeDefinition) editor.getValue();
            }
        }
        return null; // 無匹配 → 交由後續決策器 (如默認拒絕)
    }

    @SuppressWarnings("unchecked")
    private Map getUrlAuthorities(FilterInvocation fi) {
        return (Map) fi.getHttpRequest()
            .getSession().getServletContext().getAttribute("urlAuthorities");
    }

    // 其餘接口方法...
    @Override public Collection getConfigAttributeDefinitions() { return null; }
    @Override public boolean supports(Class clazz) { return true; }
}

四、Spring Security 配置注入自定義攔截器

<beans:beans xmlns="https://www.springframework.org/schema/security"
    xmlns:beans="https://www.springframework.org/schema/beans">

  <http access-denied-page="/403.jsp">
    <intercept-url pattern="/static/**" filters="none"/>
    <intercept-url pattern="/login.jsp" filters="none"/>
    <form-login login-page="/login.jsp" default-target-url="/index"/>
    <logout logout-success-url="/login.jsp"/>
  </http>

  <authentication-manager alias="authenticationManager"/>
  <authentication-provider user-service-ref="securityManager">
    <password-encoder hash="md5"/>
  </authentication-provider>

  <beans:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
    <beans:property name="decisionVoters">
      <beans:list>
        <beans:bean class="org.springframework.security.vote.RoleVoter"/>
        <beans:bean class="org.springframework.security.vote.AuthenticatedVoter"/>
      </beans:list>
    </beans:property>
  </beans:bean>

  <!-- 關鍵:自定義 FilterSecurityInterceptor 插入過濾器鏈末尾 -->
  <beans:bean id="resourceSecurityInterceptor" 
      class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
    <beans:property name="authenticationManager" ref="authenticationManager"/>
    <beans:property name="accessDecisionManager" ref="accessDecisionManager"/>
    <beans:property name="objectDefinitionSource" ref="dynamicResourceFilter"/>
    <beans:property name="observeOncePerRequest" value="false"/>
    <custom-filter after="LAST"/> <!-- 插入位置 -->
  </beans:bean>

  <beans:bean id="dynamicResourceFilter" 
      class="com.example.security.DynamicResourceFilter"/>

</beans:beans>

五、安全上下文編程式訪問三大模式

需求代碼模式關鍵 API
當前登錄用戶User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();SecurityContextHolder
用戶權限字符串String auths = StringUtils.join(user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(toList()), ",");UserDetails.getAuthorities()
用戶可訪問資源在 User 實體中緩存 Map> roleResources,首次遍歷 Role→Resource 構建領域模型擴展

六、User 實體擴展:權限與資源導航屬性

@Entity
public class User implements UserDetails {
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name="user_role", ...)
    private Set roles;

    @Transient
    private Map> roleResourcesCache;

    public Map> getRoleResources() {
        if (roleResourcesCache == null) {
            roleResourcesCache = new HashMap<>();
            for (Role role : roles) {
                for (Resource res : role.getResources()) {
                    String key = role.getName() + "_" + res.getType();
                    roleResourcesCache.computeIfAbsent(key, k -> new ArrayList<>()).add(res);
                }
            }
        }
        return roleResourcesCache;
    }
    
    // UserDetails 方法實現...
    public String getAuthoritiesString() {
        return roles.stream().map(Role::getName).collect(joining(","));
    }
}

七、關鍵設計總結

  1. 啟動緩存:資源映射載入 ServletContext,避免每請求查庫
  2. 動態決策:FilterInvocationDefinitionSource 運行時匹配 Ant 路徑模式
  3. 非侵入式擴展:custom-filter after="LAST" 保留默認鏈,追加業務邏輯
  4. 領域模型豐富化:User 實體封裝權限導航,控制器層零 SQL

此架構支援資源授權的熱更新:管理後台修改資源-角色映射後,僅需重新調用 SecurityManager.loadUrlAuthorities() 刷新 ServletContext 屬性,無需重啟應用。

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