基于Spring Security2与 Ext 的权限管理设计与实现

一、Spring Security介绍

 

Spring Security的前身Acegi,其配置及使用相对来说复杂一些,因为要配置的东西比较多,Sprng Security简化了以前的配置。大家有兴趣可以多多了解以前的版本,因为很多细节在前面的版本可以看得比较清楚,后面的版本需要看源代码才知道其实现的原理了。

基于角色的设计与实现是绝大部分系统中比较常见的权限管理方式,对权限进行分组进行管理有助于减少权限管理的复杂程度。

Spring Security目前提供了三种权限的管理方式:

一、    基于URL的拦截方式
二、    基于方法的AOP拦截方式
三、    基于数据的权限拦截方式

第一种是常见的权限管理方式,第二种有时也可以通过第一种去进行实现,方法级的拦截容易实现,不过要以友好的方式显示权限不足设计及实现就有些啰嗦。基于数据级的安全拦截实现上得更麻烦,在此不作介绍,我们只是针对第一种方式作设计。Spring Security在URL上已经提供了比较好的管理,不过其是以类似以下这种方式进行配置的。

 

Java代码
  1. <http auto-config='true'>  
  2.     <intercept-url pattern="/login.jsp*" filters="none"/>   
  3.     <intercept-url pattern="/**" access="ROLE_USER" />  
  4.     <form-login login-page='/login.jsp'/>  
  5. </http>  
<http auto-config='true'>
    <intercept-url pattern="/login.jsp*" filters="none"/> 
    <intercept-url pattern="/**" access="ROLE_USER" />
    <form-login login-page='/login.jsp'/>
</http>

 

这种要动态实现角色与权限的管理就显得有些不足了,因此需要进行扩展实现,我们可以把一些不需要进行安全拦截的url放在Spring的以上配置中,设置filters=”none”,如images,css,js等,提高访问的速度。


在扩展Spring Security之前,我们需要了解一下Spring Security的相关术语。

Authentication (认证)对象
   其实就是一个可以通过Spring Security的认证的身份证明。如实现该接口的类UsernamePasswordAuthenticationToken,表示可以通过username及password作为身份验证。

 Authentication对象包含了

  •  Principal  标识是哪一个对象,可以认为是用户
  •    Credentials 信任的对象,如密码。
  •  Authorities 权限的集合,在我们的系统中可以认为是角色的集合 (authorities要赋予给principal的)


 SecurityContextHolder
    是Spring Security的核心对象,是安全上下文的访问的入口。如取得当前的登录用户可以从该类中的相应的方法取得。该类中包含ThreadLocal私有属性 用于存取SecurityContext,         SecurityContext包含Authentication私有属性。如实现弹出窗口登录功能的时候,输入的用户名及密码并没有最终经过SPRING SECURITY的filter,那么如何使用得当前用户可以成功登录呢,其就是利用到这一点,通过该类拿到SecurityContext,然后设置一个认证的对象给它,SPRING SECURITY在看到该认证对象的时候,就会成功经过了身份的认证了。

其实也可以这样理解,为了处理Http请求间认证,Spring Security使用HttpSessionIntegrationFilter,HttpSessionIntegrationFilter用于在 HttpSession存储Http请求间的SecurityContext。不过我们可以通过SecurityContextHolder去拿到这个 SecurityContext
AuthenticationManager
    通过Providers 验证 在当前 ContextHolder中的Authentication对象是否合法。
AccessDecissionManager
    经过投票机制来审批是否批准操作
Interceptors(拦截器)
    拦截器(如FilterSecurityInterceptor,JoinPoint,MethodSecurityInterceptor等)用于协调授权,认证等操作。

Spring Security是Spring中 一个强大的安全管理框架,不过目前在我们系统中使用的仅是其中一部分的功能,则权限过滤安全检查的功能。如果抛开这个框架,我们实现权限管理的时候,可能 使用最多的方案还是使用Filter来进行过滤,在Filter里判断当前的用户是否为登录用户,若是登录用户,则看是否有权限访问当前的资源,若为未登 录用户,则跳至登录页面。

 

二、扩展Spring Security

 

扩展Spring Security基于角色的管理策略,通过角色分配,保证系统的安全。其安全的手段包括以下:

1.    登录时需要加上验证码
2.    所有的数据展示及访问页需要登录后才能访问
3.    用户的数据库密码存储时使用Sha-256的加密算法
4.    登录后的所有系统的访问URL均需要授权
5.    登录多少次失败后,可锁定IP,约20分钟后才能自动解锁。(尚未实现)

权限设计目前是采用基于角色控制的方式,用户需要访问系统的资源,首先必须要授予一个角色,而该角色具有访问系统资源的权限的能力,也可以认为是权限的集 合。因此,一个用户要访问系统的某个资源(如产品列表),则首先要授予一个能够访问产品列表资源的角色(如productAdmin)。只要任一个用户拥 有了该角色,即可以访问该资源。
   
系统的安全涉及到两个不同的概念,认证和授权。前者是关于确认用户是否确实是他们所宣称的身份。用户进入系统的时候,首先要进行第一个操作就是进行身份认 证,即Authentication。在系统中一般表现为用户用账号跟密码登录。如果都正确了,则可以登录系统。在现实中你可以这样理解,员工在进入公司 之前,需要进行身份的确认。身份确认通过后,则可以进入公司。进入公司后,并不代表可以随便进入公司的每个办公室。这时就需要每个看当前员工具有哪些角 色,即授权。授权则是关于确认用户是否有允许执行一个特定的操作。如当前员工是总经理,则可以进入总经理办公室,并且可以进入普通员工的办公区域。是因为 总经理已经授权可以出入这些地方。
在本系统中,权限表现为功能菜单及系统访问的URL。
    如:
        添加用户,其访问的url为 /system/saveAppUser.do
        删除用户,其访问的url为/system/deleteAppUser.do
        查询用户,其访问的url为/system/listAppUser.do
因而用户、角色、权限之间的关系可以用如下的图描述:

 

Spring EXT 角色 权限

 

表设计


一个用户可以有多个角色,每个角色有多个功能菜单,每个功能菜单会对应多个系统访问的URL
表设计如下所示:
系统功能 权限设计

 

表说明:
1.    app_user系统用户表,放置系统的所有用户
2.    user_role用户角色,放置系统的所有角色
3.    app_role角色表,放置用户角色
4.    role_fun角色对应的功能表,放置角色拥有的功能
5.    app_function系统的功能表,放置系统参与授权的所有功能
6.    fun_url系统的功能对应的权限URL表

目前我们需要扩展Spring Security的以下两部分功能
1. 身份认证
2. 授权

Spring Security是由一组的filter来进行统一的过滤,不同的filter进行相应的权限过滤功能。不过在Security跟spring集成的过程中,其是由一个代理的类进行这些filter的统一管理。可以在web.xml中进行了查看,如下所示:

Java代码
  1. <filter>  
  2.       <filter-name>springSecurityFilterChain</filter-name>  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  3.     </filter>  
  4.    <filter-mapping>  
  5.       <filter-name>springSecurityFilterChain</filter-name>  
  6.       <url-pattern>*.do</url-pattern>  
  7.    </filter-mapping>  
  8.    <filter-mapping>  
  9.       <filter-name>springSecurityFilterChain</filter-name>  
  10.       <url-pattern>/index.jsp</url-pattern>  
  11.    </filter-mapping>  
  12.    <filter-mapping>  
  13.       <filter-name>springSecurityFilterChain</filter-name>  
  14.       <url-pattern>/file-upload</url-pattern>  
  15.    </filter-mapping>  
<filter>
	  <filter-name>springSecurityFilterChain</filter-name>  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
   <filter-mapping>
	  <filter-name>springSecurityFilterChain</filter-name>
	  <url-pattern>*.do</url-pattern>
   </filter-mapping>
   <filter-mapping>
	  <filter-name>springSecurityFilterChain</filter-name>
	  <url-pattern>/index.jsp</url-pattern>
   </filter-mapping>
   <filter-mapping>
	  <filter-name>springSecurityFilterChain</filter-name>
	  <url-pattern>/file-upload</url-pattern>
   </filter-mapping>
 

所有经过springSecurityFilterChain的url,都会转到DelegatingFilterProxy类的bean去处理。而该Bean在Spring Security 2.0中,已经内置于安全管理的缺省的配置当中,我们只需要把app-security.xml加入我们系统管理中来即可。如下:

Java代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <b:beans xmlns="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3.              xmlns:b="http://www.springframework.org/schema/beans"  
  4.              xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
  5.               http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">   
  6.     <http auto-config="true" access-denied-page="/403.jsp" lowercase-comparisons="true"   
  7.         access-decision-manager-ref="accessDecisionManager">  
  8.         <intercept-url pattern="/images/**" filters="none"/>  
  9.         <intercept-url pattern="/css/**" filters="none"/>  
  10.         <intercept-url pattern="/js/**" filters="none"/>  
  11.         <intercept-url pattern="/403*" filters="none"/>  
  12.         <intercept-url pattern="/404*" filters="none"/>  
  13.         <intercept-url pattern="/500*" filters="none"/>   
  14.         <intercept-url pattern="/ext3/**" filters="none"/>  
  15.         <intercept-url pattern="/fckeditor/**" filters="none"/>    
  16.         <intercept-url pattern="/jsonStruts**" filters="none"/>  
  17.         <form-login default-target-url="/index.jsp" login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" login-processing-url="/j_security_check" />  
  18.         <logout logout-url="/j_logout.do" logout-success-url="/login.jsp"/>   
  19.         <remember-me key="RememberAppUser"/>  
  20.     </http>  
  21.       
  22.      <b:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">  
  23.         <b:property name="allowIfAllAbstainDecisions" value="false"/>  
  24.         <b:property name="decisionVoters">  
  25.             <b:list>  
  26.                 <b:bean class="org.springframework.security.vote.RoleVoter">  
  27.                     <b:property name="rolePrefix" value="" />  
  28.                 </b:bean>  
  29.                 <b:bean class="org.springframework.security.vote.AuthenticatedVoter" />  
  30.             </b:list>  
  31.         </b:property>  
  32.     </b:bean>  
  33.   
  34.     <authentication-manager alias="authenticationManager"/>  
  35.           
  36.     <authentication-provider user-service-ref="appUserDao">  
  37.         <password-encoder hash="sha-256" base64="true"/>  
  38.     </authentication-provider>  
  39.   
  40.     <b:bean id="securityInterceptorFilter" class="com.htsoft.core.web.filter.SecurityInterceptorFilter" >  
  41.         <custom-filter after="FILTER_SECURITY_INTERCEPTOR" />  
  42.         <b:property name="securityDataSource" ref="securityDataSource"/>  
  43.     </b:bean>  
  44.       
  45.     <b:bean id="securityDataSource" class="com.htsoft.core.security.SecurityDataSource">  
  46.         <b:property name="appRoleService" ref="appRoleService"/>  
  47.         <b:property name="anonymousUrls">  
  48.             <b:set>  
  49.                 <b:value>/login.do</b:value>  
  50.                 <b:value>/check.do</b:value>  
  51.             </b:set>  
  52.         </b:property>  
  53.         <b:property name="publicUrls">  
  54.             <b:set>  
  55.                 <b:value>/modelsMenu.do</b:value>  
  56.                 <b:value>/itemsMenu.do</b:value>  
  57.                 <b:value>/file-upload</b:value>  
  58.                 <b:value>/index.jsp</b:value>  
  59.                   
  60.                 <b:value>/communicate/listPhoneBook.do</b:value>  
  61.                 <b:value>/communicate/listPhoneGroup.do</b:value>  
  62.                 <b:value>/communicate/moveMail.do</b:value>  
  63.                 <b:value>/communicate/listMailFolder.do</b:value>  
  64.                 <b:value>/communicate/removeMailFolder.do</b:value>  
  65.                 <b:value>/communicate/searchMail.do</b:value>  
  66.                   
  67.                 <b:value>/system/getAppUser.do</b:value>  
  68.                 <b:value>/system/checkDiary.do</b:value>  
  69.                 <b:value>/system/selectDepartment.do</b:value>  
  70.                 <b:value>/system/listAppRole.do</b:value>  
  71.             </b:set>  
  72.         </b:property>  
  73.     </b:bean>  
  74. </b:beans>  
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:b="http://www.springframework.org/schema/beans"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
              http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"> 
    <http auto-config="true" access-denied-page="/403.jsp" lowercase-comparisons="true" 
     	access-decision-manager-ref="accessDecisionManager">
        <intercept-url pattern="/images/**" filters="none"/>
        <intercept-url pattern="/css/**" filters="none"/>
        <intercept-url pattern="/js/**" filters="none"/>
        <intercept-url pattern="/403*" filters="none"/>
        <intercept-url pattern="/404*" filters="none"/>
        <intercept-url pattern="/500*" filters="none"/> 
        <intercept-url pattern="/ext3/**" filters="none"/>
        <intercept-url pattern="/fckeditor/**" filters="none"/>  
        <intercept-url pattern="/jsonStruts**" filters="none"/>
        <form-login default-target-url="/index.jsp" login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" login-processing-url="/j_security_check" />
        <logout logout-url="/j_logout.do" logout-success-url="/login.jsp"/> 
   		<remember-me key="RememberAppUser"/>
    </http>
	
	 <b:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
	 	<b:property name="allowIfAllAbstainDecisions" value="false"/>
        <b:property name="decisionVoters">
            <b:list>
                <b:bean class="org.springframework.security.vote.RoleVoter">
                    <b:property name="rolePrefix" value="" />
                </b:bean>
                <b:bean class="org.springframework.security.vote.AuthenticatedVoter" />
            </b:list>
        </b:property>
    </b:bean>

	<authentication-manager alias="authenticationManager"/>
		
    <authentication-provider user-service-ref="appUserDao">
    	<password-encoder hash="sha-256" base64="true"/>
    </authentication-provider>

    <b:bean id="securityInterceptorFilter" class="com.htsoft.core.web.filter.SecurityInterceptorFilter" >
		<custom-filter after="FILTER_SECURITY_INTERCEPTOR" />
		<b:property name="securityDataSource" ref="securityDataSource"/>
	</b:bean>
	
	<b:bean id="securityDataSource" class="com.htsoft.core.security.SecurityDataSource">
		<b:property name="appRoleService" ref="appRoleService"/>
		<b:property name="anonymousUrls">
			<b:set>
				<b:value>/login.do</b:value>
				<b:value>/check.do</b:value>
			</b:set>
		</b:property>
		<b:property name="publicUrls">
			<b:set>
				<b:value>/modelsMenu.do</b:value>
				<b:value>/itemsMenu.do</b:value>
				<b:value>/file-upload</b:value>
				<b:value>/index.jsp</b:value>
				
				<b:value>/communicate/listPhoneBook.do</b:value>
				<b:value>/communicate/listPhoneGroup.do</b:value>
				<b:value>/communicate/moveMail.do</b:value>
				<b:value>/communicate/listMailFolder.do</b:value>
				<b:value>/communicate/removeMailFolder.do</b:value>
				<b:value>/communicate/searchMail.do</b:value>
				
				<b:value>/system/getAppUser.do</b:value>
				<b:value>/system/checkDiary.do</b:value>
				<b:value>/system/selectDepartment.do</b:value>
				<b:value>/system/listAppRole.do</b:value>
			</b:set>
		</b:property>
	</b:bean>
</b:beans>

 

身份认证

 

说明:当用户登录时,会根据用户账号及密码进行验证,验证由authenticationManager来进,其会调用实现UserDetailsService接口实现类完成,在本系统,是由appUserDaoImpl类来实现。

而我们的用户及角色实体要成为安全框架识别的安全实体,需要相应实现不同的接口,如下所示:


访问授权

授权的管理是通过Filter来进行的,用户访问URL时,均需要经过Spring Security的URL进行授权。在本系统中,这个功能是通过SecurityInterceptorFilter来进行。

系统启动时,会把所有的权限以[角色—URL列表]的形式放置在一个全局的Map中,用户访问系统的url时,就会根据当前用户所拥有的角色是否包含此 URL。这个全局的权限匹配源则由SecurityDataSource来提供。由于登录用户在进入系统后,都会具备一些常用的功能,所以每个用户均有一 个PUBLIC_ROLE的角色,代表可以访问系统的公告资源。该角色对应的可访问的URL,则配置在SecurityDataSource Bean中的publicUrls属性中。

SecurityInterceptorFilter代码如下所示:

Java代码
  1. package com.htsoft.core.web.filter;  
  2. /* 
  3.  *  广州宏天软件有限公司 OA办公管理系统   -- http://www.jee-soft.cn 
  4.  *  Copyright (C) 2008-2009 GuangZhou HongTian Software Company 
  5. */  
  6. import java.io.IOException;  
  7. import java.util.HashMap;  
  8. import java.util.Set;  
  9. import javax.servlet.FilterChain;  
  10. import javax.servlet.ServletException;  
  11. import javax.servlet.http.HttpServletRequest;  
  12. import javax.servlet.http.HttpServletResponse;  
  13.   
  14. import org.springframework.security.AccessDeniedException;  
  15. import org.springframework.security.Authentication;  
  16. import org.springframework.security.GrantedAuthority;  
  17. import org.springframework.security.context.SecurityContextHolder;  
  18. import org.springframework.web.filter.OncePerRequestFilter;  
  19.   
  20. import com.htsoft.core.security.SecurityDataSource;  
  21. /** 
  22.  * 权限拦载器 
  23.  * @author csx 
  24.  */  
  25. public class SecurityInterceptorFilter extends OncePerRequestFilter {  
  26.       
  27.     /** 
  28.      * 角色权限映射列表源,用于权限的匹配 
  29.      */  
  30.     private HashMap<String, Set<String>> roleUrlsMap=null;  
  31.       
  32.     private SecurityDataSource securityDataSource;  
  33.   
  34.     public void setSecurityDataSource(SecurityDataSource securityDataSource) {  
  35.         this.securityDataSource = securityDataSource;  
  36.     }  
  37.   
  38.     @Override  
  39.     protected void doFilterInternal(HttpServletRequest request,  
  40.             HttpServletResponse response, FilterChain chain) throws ServletException, IOException {  
  41.         if(logger.isDebugEnabled()){  
  42.             logger.debug("...enter the SecurityInterceptorFilter doFilterInternal here...");  
  43.         }  
  44.         String url=request.getRequestURI();  
  45.         //若有contextPath,则切出来  
  46.         if(org.springframework.util.StringUtils.hasLength(request.getContextPath())){  
  47.             String contextPath=request.getContextPath();  
  48.             int index=url.indexOf(contextPath);  
  49.             if(index!=-1){  
  50.                 url=url.substring(index+contextPath.length());  
  51.             }  
  52.         }  
  53.           
  54.         Authentication auth= SecurityContextHolder.getContext().getAuthentication();//取得认证器  
  55.           
  56.         boolean isSuperUser=false;  
  57.         for(int i=0;i<auth.getAuthorities().length;i++){  
  58.             //logger.info("角色名称:"+auth.getAuthorities()[i].getAuthority());  
  59.             if("超级管理员".equals(auth.getAuthorities()[i].getAuthority())){  
  60.                 isSuperUser=true;  
  61.             }  
  62.         }  
  63.         if(!isSuperUser){//非超级管理员  
  64.             if(!isUrlGrantedRight(url,auth)){//如果未授权  
  65.                 logger.info("ungranted url:" + url);  
  66.                 throw new AccessDeniedException("Access is denied! Url:" + url + " User:" + SecurityContextHolder.getContext().getAuthentication().getName());  
  67.             }  
  68.         }  
  69.         if(logger.isInfoEnabled()){  
  70.             logger.info("pass the url:" + url);  
  71.         }  
  72.         //进行下一个Filter  
  73.         chain.doFilter(request, response);  
  74.     }  
  75.       
  76.     /** 
  77.      * 检查该URL是否授权访问 
  78.      * @param url 
  79.      * @return 
  80.      */  
  81.     private boolean isUrlGrantedRight(String url,Authentication auth){  
  82.           
  83.         //遍历该用户下所有角色对应的URL,看是否有匹配的  
  84.         for(GrantedAuthority ga:auth.getAuthorities()){  
  85.             Set<String> urlSet=roleUrlsMap.get(ga.getAuthority());  
  86.             //TODO AntPathMatcher here  
  87.             if(urlSet!=null && urlSet.contains(url)){  
  88.                 return true;  
  89.             }  
  90.         }  
  91.         return false;  
  92.     }  
  93.       
  94.     public void loadDataSource(){  
  95.         roleUrlsMap=securityDataSource.getDataSource();  
  96.     }  
  97.       
  98.     @Override  
  99.     public void afterPropertiesSet() throws ServletException {  
  100.         loadDataSource();  
  101.         if(roleUrlsMap==null){  
  102.             throw new RuntimeException("没有进行设置系统的权限匹配数据源");  
  103.         }  
  104.     }  
  105. }  
package com.htsoft.core.web.filter;
/*
 *  广州宏天软件有限公司 OA办公管理系统   -- http://www.jee-soft.cn
 *  Copyright (C) 2008-2009 GuangZhou HongTian Software Company
*/
import java.io.IOException;
import java.util.HashMap;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import com.htsoft.core.security.SecurityDataSource;
/**
 * 权限拦载器
 * @author csx
 */
public class SecurityInterceptorFilter extends OncePerRequestFilter {
	
	/**
	 * 角色权限映射列表源,用于权限的匹配
	 */
	private HashMap<String, Set<String>> roleUrlsMap=null;
	
	private SecurityDataSource securityDataSource;

	public void setSecurityDataSource(SecurityDataSource securityDataSource) {
		this.securityDataSource = securityDataSource;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
		if(logger.isDebugEnabled()){
			logger.debug("...enter the SecurityInterceptorFilter doFilterInternal here...");
		}
		String url=request.getRequestURI();
		//若有contextPath,则切出来
		if(org.springframework.util.StringUtils.hasLength(request.getContextPath())){
			String contextPath=request.getContextPath();
			int index=url.indexOf(contextPath);
			if(index!=-1){
				url=url.substring(index+contextPath.length());
			}
		}
		
		Authentication auth= SecurityContextHolder.getContext().getAuthentication();//取得认证器
		
		boolean isSuperUser=false;
		for(int i=0;i<auth.getAuthorities().length;i++){
			//logger.info("角色名称:"+auth.getAuthorities()[i].getAuthority());
			if("超级管理员".equals(auth.getAuthorities()[i].getAuthority())){
				isSuperUser=true;
			}
		}
		if(!isSuperUser){//非超级管理员
			if(!isUrlGrantedRight(url,auth)){//如果未授权
				logger.info("ungranted url:" + url);
				throw new AccessDeniedException("Access is denied! Url:" + url + " User:" + SecurityContextHolder.getContext().getAuthentication().getName());
			}
		}
		if(logger.isInfoEnabled()){
			logger.info("pass the url:" + url);
		}
		//进行下一个Filter
		chain.doFilter(request, response);
	}
	
	/**
	 * 检查该URL是否授权访问
	 * @param url
	 * @return
	 */
	private boolean isUrlGrantedRight(String url,Authentication auth){
		
		//遍历该用户下所有角色对应的URL,看是否有匹配的
		for(GrantedAuthority ga:auth.getAuthorities()){
			Set<String> urlSet=roleUrlsMap.get(ga.getAuthority());
			//TODO AntPathMatcher here
			if(urlSet!=null && urlSet.contains(url)){
				return true;
			}
		}
		return false;
	}
	
	public void loadDataSource(){
		roleUrlsMap=securityDataSource.getDataSource();
	}
	
	@Override
	public void afterPropertiesSet() throws ServletException {
		loadDataSource();
		if(roleUrlsMap==null){
			throw new RuntimeException("没有进行设置系统的权限匹配数据源");
		}
	}
}

 三、EXT的扩展实现

至此,我们完成了对Spring Security的权限扩展,但是EXT访问的时候,我们的应用程序都是在一个页面上进行,也就是我们之前说的One Application One Page,几乎所有的请求都是通过Ajax的请求来时行,页面没有刷新,当权限不足的时候,我们如何提示用户呢?另外我们的功能菜单又是如何来根据用户的 角色来显示出来呢?在此,我们把需要把角色、功能、权限URL需要进行统一管理。我们从以下几个方面来进行扩展。


当用户权限不足时,我们需要提示用户无限访问该URL,在app-sercurity.xml中,我们配置了以下:

Java代码
  1. <http auto-config="true" access-denied-page="/403.jsp" lowercase-comparisons="true"   
  2.         access-decision-manager-ref="accessDecisionManager">  
  3.         <intercept-url pattern="/images/**" filters="none"/>  
  4.         <intercept-url pattern="/css/**" filters="none"/>  
  5.         <intercept-url pattern="/js/**" filters="none"/>  
  6.         <intercept-url pattern="/403*" filters="none"/>  
  7.         <intercept-url pattern="/404*" filters="none"/>  
  8.         <intercept-url pattern="/500*" filters="none"/>   
  9.         <intercept-url pattern="/ext3/**" filters="none"/>  
  10.         <intercept-url pattern="/fckeditor/**" filters="none"/>    
  11.         <intercept-url pattern="/jsonStruts**" filters="none"/>  
  12.         <form-login default-target-url="/index.jsp" login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" login-processing-url="/j_security_check" />  
  13.         <logout logout-url="/j_logout.do" logout-success-url="/login.jsp"/>   
  14.         <remember-me key="RememberAppUser"/>  
  15.     </http>  
<http auto-config="true" access-denied-page="/403.jsp" lowercase-comparisons="true" 
     	access-decision-manager-ref="accessDecisionManager">
        <intercept-url pattern="/images/**" filters="none"/>
        <intercept-url pattern="/css/**" filters="none"/>
        <intercept-url pattern="/js/**" filters="none"/>
        <intercept-url pattern="/403*" filters="none"/>
        <intercept-url pattern="/404*" filters="none"/>
        <intercept-url pattern="/500*" filters="none"/> 
        <intercept-url pattern="/ext3/**" filters="none"/>
        <intercept-url pattern="/fckeditor/**" filters="none"/>  
        <intercept-url pattern="/jsonStruts**" filters="none"/>
        <form-login default-target-url="/index.jsp" login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" login-processing-url="/j_security_check" />
        <logout logout-url="/j_logout.do" logout-success-url="/login.jsp"/> 
   		<remember-me key="RememberAppUser"/>
    </http>
 

即当用户权限不足时,会跳至403页,因此我们可以在此上作文章,当跳至403页时,我们往response的头写一个标识,在ext的connection中获取返回结果时,我们根据这个标识来给用户提示一个友好的信息,如:

 

403.jsp的代码实现:

 

Java代码
  1. <%@ page pageEncoding="UTF-8"%><%   
  2.     response.addHeader("__forbidden","true");  
  3.     out.println("{success:false,msg:'访问被拒绝!'}");  
  4. %>  
<%@ page pageEncoding="UTF-8"%><% 
	response.addHeader("__forbidden","true");
	out.println("{success:false,msg:'访问被拒绝!'}");
%>

 处理该标识:

 

Java代码
  1. App.init = function() {  
  2.     Ext.util.Observable.observeClass(Ext.data.Connection);  
  3.     Ext.data.Connection.on('requestcomplete', function(conn, resp,options ){  
  4.         if (resp && resp.getResponseHeader){  
  5.             if(resp.getResponseHeader('__timeout')) {  
  6.                 Ext.ux.Toast.msg('操作提示:','操作已经超时,请重新登录!');  
  7.                 window.location.href=__ctxPath+'/index.jsp?randId=' + parseInt(1000*Math.random());  
  8.             }  
  9.             if(resp.getResponseHeader('__forbidden')){  
  10.                 Ext.ux.Toast.msg('系统访问权限提示:','你目前没有权限访问:{0}',options.url);  
  11.             }  
  12.         }  
  13.     });  
  14. …  
  15. }  
App.init = function() {
	Ext.util.Observable.observeClass(Ext.data.Connection);
	Ext.data.Connection.on('requestcomplete', function(conn, resp,options ){
		if (resp && resp.getResponseHeader){
		    if(resp.getResponseHeader('__timeout')) {
		    	Ext.ux.Toast.msg('操作提示:','操作已经超时,请重新登录!');
	        	window.location.href=__ctxPath+'/index.jsp?randId=' + parseInt(1000*Math.random());
	    	}
	    	if(resp.getResponseHeader('__forbidden')){
	    		Ext.ux.Toast.msg('系统访问权限提示:','你目前没有权限访问:{0}',options.url);
	    	}
		}
	});
…
}
 

Connection的这个requestcomplete事件是所有的Ajax请求都必须触发的,所以把它作为总的入口。
另外,用户登录后,其功能的菜单如何来配置呢,因此应用程序是通过一个全局的menu.xml文件来进行功能菜单的管理,同时也包括其功能与URL的配置。

Java代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <Menus>  
  3.     <Items id="SystemSetting" text="系统设置" iconCls="menu-system">  
  4.         ...  
  5.         <Item id="AppRoleView" iconCls="menu-role" text="角色设置">  
  6.             <Function id="_AppRoleList" text="查看角色" iconCls="menu-list">  
  7.                 <url>/system/listAppRole.do</url>  
  8.             </Function>  
  9.             <Function id="_AppRoleAdd" text="添加角色" iconCls="menu-add">  
  10.                 <url>/system/listAppRole.do</url>  
  11.                 <url>/system/saveAppRole.do</url>  
  12.             </Function>  
  13.             <Function id="_AppRoleEdit" text="编辑角色" iconCls="menu-add">  
  14.                 <url>/system/listAppRole.do</url>  
  15.                 <url>/system/saveAppRole.do</url>  
  16.             </Function>  
  17.             <Function id="_AppRoleDel" text="删除角色" iconCls="menu-del">  
  18.                 <url>/system/listAppRole.do</url>  
  19.                 <url>/system/mulDelAppRole.do</url>  
  20.             </Function>  
  21.             <Function id="_AppRoleGrant" text="授权角色">  
  22.                 <url>/system/listAppRole.do</url>  
  23.                 <url>/system/grantAppRole.do</url>  
  24.             </Function>  
  25.         </Item>  
  26.         ...  
  27.         <Item id="ReportTemplateView" iconCls="menu-report" text="报表管理">  
  28.             ...  
  29.         </Item>  
  30.     </Items>  
  31. </Menus>  
<?xml version="1.0" encoding="UTF-8"?>
<Menus>
	<Items id="SystemSetting" text="系统设置" iconCls="menu-system">
		...
		<Item id="AppRoleView" iconCls="menu-role" text="角色设置">
			<Function id="_AppRoleList" text="查看角色" iconCls="menu-list">
				<url>/system/listAppRole.do</url>
			</Function>
			<Function id="_AppRoleAdd" text="添加角色" iconCls="menu-add">
				<url>/system/listAppRole.do</url>
				<url>/system/saveAppRole.do</url>
			</Function>
			<Function id="_AppRoleEdit" text="编辑角色" iconCls="menu-add">
				<url>/system/listAppRole.do</url>
				<url>/system/saveAppRole.do</url>
			</Function>
			<Function id="_AppRoleDel" text="删除角色" iconCls="menu-del">
				<url>/system/listAppRole.do</url>
				<url>/system/mulDelAppRole.do</url>
			</Function>
			<Function id="_AppRoleGrant" text="授权角色">
				<url>/system/listAppRole.do</url>
				<url>/system/grantAppRole.do</url>
			</Function>
		</Item>
		...
		<Item id="ReportTemplateView" iconCls="menu-report" text="报表管理">
			...
		</Item>
	</Items>
</Menus>
 

这个XML文件会在应用程序启动添加至系统的全局变量中,以“角色”对应“URL”的Map提供数据源来进行。

那么角色对应的URL是如何来构造的,这个相对简单一些,以上的功能及菜单,其均存在一个Id,如角色设置(AppRoleView),添加角色“_AppRoleAdd”。
每个角色就保存这些ID,所以加载这些ID,就有办法把其下的URL全部加载出来,从而形成角色与URL的映射关系。

另外,我们还可以把用户所拥有的权限,通过该用户拥有哪些角色,每个角色包括哪些权限的ID,从而构造出该用户的权限集合。如下所示,当用户登录后,我们 把所有的ID集中放在用户的rights字段中,这样就可以通过ID来决定用户是否有权限访问某个功能按钮,从而达到功能级别的控制,如:

 

Java代码
  1. //加载权限  
  2.     Ext.Ajax.request({  
  3.             url:__ctxPath+'/system/getCurrentAppUser.do',  
  4.             method:'Get',  
  5.             success:function(response,options){  
  6.                 var object=Ext.util.JSON.decode(response.responseText);  
  7.                 //取得当前登录用户的相关信息,包括权限  
  8.                 curUserInfo=new UserInfo(object.user.userId,object.user.fullname,object.user.rights);  
  9.             }  
  10.     });  
//加载权限
	Ext.Ajax.request({
			url:__ctxPath+'/system/getCurrentAppUser.do',
			method:'Get',
			success:function(response,options){
				var object=Ext.util.JSON.decode(response.responseText);
				//取得当前登录用户的相关信息,包括权限
				curUserInfo=new UserInfo(object.user.userId,object.user.fullname,object.user.rights);
			}
	});


 以下为user.rights的构造,是在用户登录的时候进行配置实现,为AppUserDaoImpl.java的部分代码

 

Java代码
  1. public UserDetails loadUserByUsername(final String username)  
  2.             throws UsernameNotFoundException, DataAccessException {  
  3.         return (UserDetails) getHibernateTemplate().execute(  
  4.                 new HibernateCallback() {  
  5.                     public Object doInHibernate(Session session)  
  6.                             throws HibernateException, SQLException {  
  7.                         String hql = "from AppUser ap where ap.username=? and ap.delFlag = ?";  
  8.                         Query query = session.createQuery(hql);  
  9.                         query.setString(0, username);  
  10.                         query.setShort(1, Constants.FLAG_UNDELETED);  
  11.                         AppUser user = null;  
  12.                         try {  
  13.                             user = (AppUser) query.uniqueResult();  
  14.                             if (user != null) {  
  15.                                 Hibernate.initialize(user.getRoles());  
  16.                                 Hibernate.initialize(user.getDepartment());  
  17.                                   
  18.                                 //进行合并权限的处理  
  19.                                 Set<AppRole> roleSet=user.getRoles();  
  20.                                 Iterator<AppRole> it=roleSet.iterator();  
  21.                                   
  22.                                 while(it.hasNext()){  
  23.                                     AppRole role=it.next();  
  24.                                     if(role.getRoleId().equals(AppRole.SUPER_ROLEID)){//具有超级权限  
  25.                                         user.getRights().clear();  
  26.                                         user.getRights().add(AppRole.SUPER_RIGHTS);  
  27.                                         break;  
  28.                                     }else{  
  29.                                         if(StringUtils.isNotEmpty(role.getRights())){  
  30.                                             String[]items=role.getRights().split("[,]");  
  31.                                             for(int i=0;i<items.length;i++){  
  32.                                                 if(!user.getRights().contains(items[i])){  
  33.                                                     user.getRights().add(items[i]);  
  34.                                                 }  
  35.                                             }  
  36.                                         }  
  37.                                     }  
  38.                                 }  
  39.                                   
  40.                             }  
  41.                         } catch (Exception ex) {  
  42.                             logger.warn("user:" + username  
  43.                                     + " can't not loding rights:"  
  44.                                     + ex.getMessage());  
  45.                         }  
  46.                         return user;  
  47.                     }  
  48.                 });  
  49.     }  
public UserDetails loadUserByUsername(final String username)
			throws UsernameNotFoundException, DataAccessException {
		return (UserDetails) getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException, SQLException {
						String hql = "from AppUser ap where ap.username=? and ap.delFlag = ?";
						Query query = session.createQuery(hql);
						query.setString(0, username);
						query.setShort(1, Constants.FLAG_UNDELETED);
						AppUser user = null;
						try {
							user = (AppUser) query.uniqueResult();
							if (user != null) {
								Hibernate.initialize(user.getRoles());
								Hibernate.initialize(user.getDepartment());
								
								//进行合并权限的处理
								Set<AppRole> roleSet=user.getRoles();
								Iterator<AppRole> it=roleSet.iterator();
								
								while(it.hasNext()){
									AppRole role=it.next();
									if(role.getRoleId().equals(AppRole.SUPER_ROLEID)){//具有超级权限
										user.getRights().clear();
										user.getRights().add(AppRole.SUPER_RIGHTS);
										break;
									}else{
										if(StringUtils.isNotEmpty(role.getRights())){
											String[]items=role.getRights().split("[,]");
											for(int i=0;i<items.length;i++){
												if(!user.getRights().contains(items[i])){
													user.getRights().add(items[i]);
												}
											}
										}
									}
								}
								
							}
						} catch (Exception ex) {
							logger.warn("user:" + username
									+ " can't not loding rights:"
									+ ex.getMessage());
						}
						return user;
					}
				});
	}

 

而其最终的实现效果可以参见我另一篇博客:

http://man1900.javaeye.com/blog/517248

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值