SpringSecurity權限管理

登錄攔截

流程:
用戶登陸,會被AuthenticationProcessingFilter攔截,調用AuthenticationManager的實現,而且AuthenticationManager會調用ProviderManager來獲取用戶驗證信息(不同的Provider調用的服務不同,因爲這些信息可以是在數據庫上,可以是在LDAP服務器上,可以是xml配置文件上等),如果驗證通過後會將用戶的權限信息封裝一個User放到spring的全局緩存SecurityContextHolder中,以備後面訪問資源時使用。

Created with Raphaël 2.1.0loginloginAuthenticationProcessingFilterAuthenticationProcessingFilterAuthenticationManagerAuthenticationManagerProviderManagerProviderManagerUser(包含權限)User(包含權限)登陸過濾被實現調用返回用戶驗證信息驗證通過

ProvideManager 根據用戶名返回對應的角色權限信息
配置方式:

<authentication-manager alias="authenticationManager">  
  <authentication-provider user-service-ref="myUserDetailService">  
      <!--如果用戶的密碼採用加密的話 <password-encoder hash="md5" /> -->  
  </authentication-provider>  
</authentication-manager>  

代碼:

/**
 * @author PL
 * 驗證配置,認證管理器,實現用戶認證的入口
 */
public class MyUserDetailService implements UserDetailsService {
    /**
     *  登陸驗證時,通過username獲取用戶的所有權限信息,  
     *  並返回User放到spring的全局緩存SecurityContextHolder中,以供授權器使用  
     */
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();   
        //GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN");   
        GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_USER");   
        if(username.equals("admin")){   
            auths=new ArrayList<GrantedAuthority>();   
            auths.add(auth1);  
          //  auths.add(auth2);        
        }       
        User user = new User(username, "admin", true, true, true, true, auths);  
        System.out.println("--------UserDetailService:"+JSONObject.toJSONString(user));
        return user;    
    }

登錄資源訪問攔截

流程:
訪問url時,會通過AbstractSecurityInterceptor攔截器攔截,其中會調用FilterInvocationSecurityMetadataSource的方法來獲取被攔截url所需的全部權限,在調用授權管理器AccessDecisionManager,這個授權管理器會通過spring的全局緩存SecurityContextHolder獲取用戶的權限信息,還會獲取被攔截的url和被攔截url所需的全部權限,然後根據所配的策略(有:一票決定,一票否定,少數服從多數等),如果權限足夠,則返回,權限不夠則報錯並調用權限不足頁面。

加載所有URL對應的角色:

Created with Raphaël 2.1.0AbstractSecurityInterceptorAbstractSecurityInterceptorsecurityMetadataSourcesecurityMetadataSourceAccessDecisionManager授權管理器AccessDecisionManager授權管理器beforeInvocation()調用加載所有URL和權限,通過getAttibbte()獲取afterInvocation(token, null)調用;

授權管理:

Created with Raphaël 2.1.0AbstractSecurityInterceptorAbstractSecurityInterceptorAccessDecisionManagerAccessDecisionManagerSecurityContextHolder(spring全局緩存)SecurityContextHolder(spring全局緩存)afterInvocation(token, null)調用;用戶信息decide()表決,沒通過拋出異常

完整代碼:

SpringSecurity.xml:

<?xml version="1.0" encoding="UTF-8"?>
<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/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security.xsd">

    <!-- 配置不過濾的資源(靜態資源及登錄相關) -->
    <http pattern="/**/*.css" security="none"></http>
    <http pattern="/**/*.jpg" security="none"></http>
    <http pattern="/**/*.jpeg" security="none"></http>
    <http pattern="/**/*.gif" security="none"></http>
    <http pattern="/**/*.png" security="none"></http>
    <http pattern="/js/*.js" security="none"></http>
    <http pattern="/login.jsp" security="none"></http>
    <http pattern="/getCode" security="none" /><!-- 不過濾驗證碼 -->
    <http pattern="/test/**" security="none"></http><!-- 不過濾測試內容 -->

    <http auto-config='true'  >
        <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <intercept-url pattern="/main.jsp" access="ROLE_ADMIN" />
        <intercept-url pattern="/**" access="ROLE_USER" />
        <form-login login-page="/login.jsp" />
        <custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR" />
    </http>

    <!--一個自定義的filter,必須包含 authenticationManager,accessDecisionManager,securityMetadataSource三個屬性, 
        我們的所有控制將在這三個類中實現,解釋詳見具體配置 -->
    <beans:bean id="myFilter"
        class="cn.panlei.springsecurity.service.MyFilterSecurityInterceptor">
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />
        <beans:property name="securityMetadataSource" ref="securityMetadataSource" />
    </beans:bean>

    <!--驗證配置,認證管理器,實現用戶認證的入口,主要實現UserDetailsService接口即可 -->  
    <authentication-manager alias="authenticationManager">  
        <authentication-provider user-service-ref="myUserDetailService">  
            <!--如果用戶的密碼採用加密的話 <password-encoder hash="md5" /> -->  
        </authentication-provider>  
    </authentication-manager>  
    <!--在這個類中,你就可以從數據庫中讀入用戶的密碼,角色信息,是否鎖定,賬號是否過期等 -->  
    <beans:bean id="myUserDetailService" class="cn.panlei.springsecurity.service.MyUserDetailService" />  
    <!--訪問決策器,決定某個用戶具有的角色,是否有足夠的權限去訪問某個資源 -->  
    <beans:bean id="myAccessDecisionManagerBean" 
        class="cn.panlei.springsecurity.service.MyAccessDecisionManager">  
    </beans:bean>  
    <!--資源源數據定義,將所有的資源和權限對應關係建立起來,即定義某一資源可以被哪些角色訪問 -->  
    <beans:bean id="securityMetadataSource" 
        class="cn.panlei.springsecurity.service.MyInvocationSecurityMetadataSource" />   
</beans:beans>

MyUserDetailService.java

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.alibaba.fastjson.JSONObject;
/**
 * @author PL
 * 驗證配置,認證管理器,實現用戶認證的入口
 */
public class MyUserDetailService implements UserDetailsService {
    /**
     *  登陸驗證時,通過username獲取用戶的所有權限信息,  
     *  並返回User放到spring的全局緩存SecurityContextHolder中,以供授權器使用  
     */
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();   
        //GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN");   
        GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_USER");   
        if(username.equals("admin")){   
            auths=new ArrayList<GrantedAuthority>();   
            auths.add(auth1);  
          //  auths.add(auth2);        
        }       
        User user = new User(username, "admin", true, true, true, true, auths);  
        System.out.println("--------UserDetailService:"+JSONObject.toJSONString(user));
        return user;    
    }
}

MyFilterSecurityInterceptor.java

/**
 * 
 * @author PL
 *
 *  資源訪問是進行攔截
 */
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    // 配置文件注入
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    // 登陸後,每次訪問資源都通過這個攔截器攔截
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public Class<? extends Object> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        // fi裏面有一個被攔截的url
        // 裏面調用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取fi對應的所有權限
        // object)這個方法獲取fi對應的所有權限
        // 再調用MyAccessDecisionManager的decide方法來校驗用戶的權限是否足夠
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            // 執行下一個攔截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
        this.securityMetadataSource = newSource;
    }

    public void destroy() {
    }

    public void init(FilterConfig arg0) throws ServletException {
    }
}

MyAccessDecisionManager .java 表決器

public class MyAccessDecisionManager implements AccessDecisionManager {

    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        System.out.println("進入AccessDecisionManager:"+authentication);
        if (configAttributes == null) {
            return;
        }

        Iterator<ConfigAttribute> ite = configAttributes.iterator();
        while (ite.hasNext()) {
            ConfigAttribute ca = ite.next();
            String needRole = ((SecurityConfig) ca).getAttribute();
            for (GrantedAuthority ga : authentication.getAuthorities()) {
                if (needRole.equals(ga.getAuthority())) {

                    return;
                }
            }
        }
        // 注意:執行這裏,後臺是會拋異常的,但是界面會跳轉到所配的access-denied-page頁面
        throw new AccessDeniedException("no right");
    }

    public boolean supports(ConfigAttribute attribute) {
        // TODO Auto-generated method stub
        return true;
    }

    public boolean supports(Class<?> clazz) {
        // TODO Auto-generated method stub
        return true;
    }

}

MyInvocationSecurityMetadataSource.java

/**
 * 
 * @author PL
 * 
 *  資源源數據定義,將所有的資源和權限對應關係建立起來,即定義某一資源可以被哪些角色訪問
 *
 */
public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    protected final Log logger = LogFactory.getLog(getClass());
    //將所有的角色和url的對應關係緩存起來  
    //private static List<RoleUrlResource> rus = null;  
    private UrlMatcher urlMatcher = new AntUrlPathMatcher();
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;

    // tomcat啓動時實例化一次
    public MyInvocationSecurityMetadataSource() {
        loadResourceDefine();
    }

    // tomcat開啓時加載一次,加載所有url和權限(或角色)的對應關係
    private void loadResourceDefine() {
        resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
        Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
        ConfigAttribute ca = new SecurityConfig("ROLE_ADMIN");
        atts.add(ca);
        resourceMap.put("/index.jsp", atts);
        Collection<ConfigAttribute> attsno = new ArrayList<ConfigAttribute>();
        ConfigAttribute cano = new SecurityConfig("ROLE_NO");
        attsno.add(cano);
        resourceMap.put("/other.jsp", attsno);
        System.out.println("---------FilterInvocationSecurityMetadataSource"+JSONObject.toJSONString(resourceMap));
    }

    // 參數是要訪問的url,返回這個url對於的所有權限(或角色)
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {

        /*String url = ((FilterInvocation) object).getRequestUrl();

        // 查詢所有的url和角色的對應關係
        if (rus == null) {
            rus = roleUrlDao.findAll();
        }

        // 匹配所有的url,並對角色去重
        Set<String> roles = new HashSet<String>();
        for (RoleUrlResource ru : rus) {
            if (urlMatcher.pathMatchesUrl(ru.getUrlResource().getUrl(), url)) {
                roles.add(ru.getRole().getRoleName());
            }
        }
        Collection<ConfigAttribute> cas = new ArrayList<ConfigAttribute>();
        for (String role : roles) {
            ConfigAttribute ca = new SecurityConfig(role);
            cas.add(ca);
        }
        return cas;*/

        // 將參數轉爲url
        //logger.info("getAttributes"+JSONObject.toJSONString(object));
        // 將參數轉爲url      
        String url = ((FilterInvocation)object).getRequestUrl();     
        Iterator<String>ite = resourceMap.keySet().iterator();   
        while (ite.hasNext()) {           
            String resURL = ite.next();    
            if (urlMatcher.pathMatchesUrl(resURL, url)) {   
                return resourceMap.get(resURL);           
                }         
            }   
        return null;
    }

    public boolean supports(Class<?> clazz) {
        return true;
    }

    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

}

UrlMatcher.java

public interface UrlMatcher {
    Object compile(String paramString);  
    boolean pathMatchesUrl(Object paramObject, String paramString);  
    String getUniversalMatchPattern();   
    boolean requiresLowerCaseUrl();  
}

獲取用戶輸入的密碼:UsernamePasswordAuthenticationFilter

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章