SpringSecurity個人化配置

來源:互聯網
上載者:User

應用情境

現有的資料庫中包含以下幾張表格用於許可權管理

要求在此基礎上整合SpringSecurity,將表格的資料作為資料來源來完成登入和許可權校正邏輯

SpringSecurity的配置可通過兩種方式呈現,基於自身的namespace配置和傳統的基於Bean的配置。通過namespace來配置Security非常簡潔,隱藏了很多繁瑣的實現細節,但也不便於初學者進行理解,而如果要想對Security進行個人化定製(替換現有功能實現),最好還是採用傳統的基於Bean的方式進行配置,雖然結構複雜,但是細節清晰明了
以下是兩種方式的配置比較:
1.基於namespace來配置

<beans:beans xmlns="http://www.springframework.org/schema/security"    xmlns:beans="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/security                    http://www.springframework.org/schema/security/spring-security-3.1.xsd">    <http pattern="/js/**" security="none" />    <http use-expressions="true" access-denied-page="/error.html">        <intercept-url pattern="/peoplemanage/**" access="hasRole('admin')" />        <form-login login-page='/login.jsp'/>        <logout />    </http>    <authentication-manager>        <authentication-provider>            <user-service>                <user name="zhangsan" password="zhangsan" authorities="admin,user"/>                <user name="wangwu" password="wangwu" authorities="user" />            </user-service>        </authentication-provider>    </authentication-manager></beans:beans>

2.同樣的配置還原成Bean的方式

<beans xmlns="http://www.springframework.org/schema/beans"    xmlns:sec="http://www.springframework.org/schema/security"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/security                    http://www.springframework.org/schema/security/spring-security-3.1.xsd">    <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">        <constructor-arg>            <list>                <sec:filter-chain pattern="/js/**" filters="none"/>                <sec:filter-chain pattern="/**"                    filters="securityContextPersistenceFilter,authenticationFilter,exceptionTranslationFilter,filterSecurityInterceptor"/>            </list>        </constructor-arg>    </bean>    <bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">          <property name="authenticationManager" ref="authenticationManager"/>          <property name="accessDecisionManager" ref="accessDecisionManager"/>          <property name="securityMetadataSource">            <sec:filter-security-metadata-source use-expressions="true">                  <sec:intercept-url pattern="/peoplemanage/**" access="hasRole('admin')"/>            </sec:filter-security-metadata-source>          </property>    </bean>    <!-- exceptionTranslationFilter -->    <bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter">         <property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>         <property name="accessDeniedHandler" ref="accessDeniedHandler"/>    </bean>    <bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">         <property name="loginFormUrl" value="/login.jsp"/>    </bean>    <bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl">          <property name="errorPage" value="/error.html"/>    </bean>    <!-- securityContextPersistenceFilter -->    <bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>    <!-- authenticationFilter -->    <bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">          <property name="authenticationManager" ref="authenticationManager"/>          <property name="filterProcessesUrl" value="/j_spring_security_check"/>    </bean>    <!-- Core Service -->    <bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">          <property name="providers">            <list>                <ref local="daoAuthenticationProvider"/>            </list>          </property>    </bean>    <bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">        <property name="userDetailsService" ref="inMemoryDaoImpl"/>    </bean>    <bean id="inMemoryDaoImpl" class="org.springframework.security.provisioning.InMemoryUserDetailsManager">        <constructor-arg name="users">            <props>                <prop key="zhangsan">zhangsan,enabled</prop>                <prop key="wangwu">wangwu,enabled</prop>            </props>        </constructor-arg>    </bean>    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">        <property name="decisionVoters">            <list>                <bean class="org.springframework.security.web.access.expression.WebExpressionVoter"></bean>            </list>        </property>    </bean></beans>

還原成Bean的配置方式之後,在實現個人化的定製就變得清晰明了了。
一、首先需要修改userDetailsService的實現
在上述Demo配置中使用的是Spring內建的InMemoryUserDetailsManager,該類的主要作用是從設定檔載入zhangsan、wangwu等資訊來構建使用者資料來源,而我們的使用者資料是儲存在資料庫裡的,因此需要修改實現,實現方式如下:
1.自訂一個Service,實現org.springframework.security.core.userdetails.UserDetailsService介面

public class MyUserDetailsService implements UserDetailsService {    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {    /**     * TODO 從資料庫中載入使用者資訊,並封裝成UserDetails對象     */    }}

2.替換Demo中的對應的配置

<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">      <property name="userDetailsService" ref="myUserDetailsService"/></bean><bean id="myUserDetailsService" class="com.youcompany.MyUserDetailsService"/>

二、修改filterSecurityInterceptor中securityMetadataSource屬性的注入方式
在Demo配置中securityMetadataSource屬性的配置是靜態,將每一個資源和資源對應的角色封裝到<sec:intercept-url>標籤裡
而我們的需求情境是資源資訊儲存在資料庫裡,因此不能通過這種靜態方式去描述,修改方式如下:
1.聲明一個Service實現org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource介面

public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {    private Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;    public MyFilterInvocationSecurityMetadataSource(){        requestMap=new HashMap<RequestMatcher, Collection<ConfigAttribute>>();        loadMetadataInfo();//將資料庫中的資源和角色實體封裝到requestMap裡    }    private void loadMetadataInfo() {        List<Resource> resources=...//TODO 擷取資料庫中所有的資源實體        for(Resource res:resources){            Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();            List<Role> roles=...//TODO 擷取該資源對應的訪問角色            for(Role role:roles){                allAttributes.add(new SecurityConfig(role.getRoleName()));            }            RequestMatcher key=new AntPathRequestMatcher(res.getUrl()+"/**");            requestMap.put(key, allAttributes);        }    }    public Collection<ConfigAttribute> getAllConfigAttributes() {        Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();        List<Role> roles...//TODO 擷取庫中所有的角色實體        for(Role role:roles){            allAttributes.add(new SecurityConfig(role.getRoleName()));        }        return allAttributes;    }    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {        HttpServletRequest request = ((FilterInvocation) object).getRequest();        for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {            if (entry.getKey().matches(request)) {                return entry.getValue();            }        }        return null;    }    public boolean supports(Class<?> clazz) {        return FilterInvocation.class.isAssignableFrom(clazz);    }}

2.修改Demo中相應的配置

<bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">      <property name="authenticationManager" ref="authenticationManager"/>      <property name="accessDecisionManager" ref="accessDecisionManager"/>      <property name="securityMetadataSource" ref="myFilterInvocationSecurityMetadataSource"/></bean><bean id="myFilterInvocationSecurityMetadataSource" class="com.youcompany.MyFilterInvocationSecurityMetadataSource"/>

三、修改accessDecisionManager中decisionVoters的實現邏輯
SpringSecurity預設使用AffirmativeBased來進行存取權限控制,該類封裝了很多AccessDecisionVoter對象,基於投票的機制來決定訪問是否通過
AccessDecisionVoter之間是OR的邏輯(只要有一個AccessDecisionVoter判斷許可權通過,使用者便可訪問介面)。
在Demo配置裡,使用的是WebExpressionVoter基於運算式的許可權認證邏輯(hasRole('admin')),而我們的需求是將使用者的角色和訪問資源需要的角色進行對比,來判斷該使用者是否具有訪問介面的許可權,因此需要進行以下修改:
1.聲明一個Service實現org.springframework.security.access.AccessDecisionVoter介面

public class MyAccessDecisionVoter implements AccessDecisionVoter<Object> {    public boolean supports(ConfigAttribute attribute) {        return true;    }    public boolean supports(Class<?> clazz) {        return true;    }    public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {        int result = ACCESS_DENIED;        for (ConfigAttribute attribute : attributes) {//可訪問該頁面的角色            for (GrantedAuthority authority : authentication.getAuthorities()) {//登入使用者具備的角色                 if (attribute.getAttribute().equals(authority.getAuthority())) {//判斷使用者是否具有相應角色                     return ACCESS_GRANTED;                 }            }        }        return result;    }}

2.修改Demo中對應的配置

<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">    <property name="decisionVoters">        <list>            <bean class="com.youcompany.MyAccessDecisionVoter"></bean>        </list>    </property></bean>

至此,SpringSecurity個人化定製修改完成。有點長,部分代碼加了TODO,有不理解的可與我聯絡,需要源碼的網友可留郵箱

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.