Spring Security3实践总结

  在线项目最近要对管理系统进行细粒度的权限控制,细化到URL级别。Spring Security3在这个时候引入到了系统总来。Spring Security3的学习曲线并不是非常的平坦。现在将使用场景和使用方法总结如下。

一、需求

       做项目肯定要从项目背景和需求谈起。这个在线项目的背景和需求如下:

  1. 该项目为一个对外网开发的管理系统,系统功能丰富,需要将系统的功能进行切分,不同用户拥有不同的权限。该系统为JAVA开发,使用了Spring3。
  2. 权限的粒度细化到最小功能级别。
  3. 根据用户权限展现菜单。
  4. 根据用户权限显示每个页面的功能按钮等。
  5. 用户必须登录才能操作。
  6. 系统有超级管理员,其拥有最大的权限,可以操作任何功能。
  7. 系统存在公共页面,登录用户均可查看和使用。

二、方案

       有了需求肯定就要有解决方案,针对上面的背景和需求。我们的解决方案和办法如下:

  1. 既然已经用了Spring3,那就直接上Spring Security3,这里为了保证和系统版本统一,使用3.1.2.RELEASE。
  2. URL请求就是最小的用需要的最小权限粒度对应物,所以将URL作为Spring Security中的资源。
  3. 根据用户权限情况加载用户菜单树。
  4. 获取登陆用户的资源权限,来展现页面功能按钮。SpringSecurity有标签可以控制资源的显示,由于本文涉及的项目中需要向下兼容,而且存在白名单,所以做了自定义标签处理。
  5. 增加用户操作拦截,判定用户登陆状态。
  6. 增加用户白名单来实现超级管理员操作权限最大化,实质是登陆的超级管理员用户不需要过Spring Security的资源授权功能。
  7. 增加公共资源白名单,使任何登陆用户均可访问公共页面,实质是在用户已经登陆的情况下公共资源不需要过Spring Security的资源授权功能。

 Spring Security3对(用户登录)验证和(安全资源)授权的基本流程如下图:

 SpringSecurity3验证和授权流程图

注意:第5步的白名单是自定义操作。

 

三、实现

      使用Spring Security3的实践过程如下:

 

  1. 增加Spring Security3的MAVEN坐标依赖:

Xml代码   收藏代码
  1.   
  2. <dependency>  
  3.   <groupId>org.springframework.securitygroupId>  
  4.   <artifactId>spring-security-webartifactId>  
  5.   <version>3.1.2.RELEASEversion>  
  6. dependency>  
  7. <dependency>  
  8.   <groupId>org.springframework.securitygroupId>  
  9.   <artifactId>spring-security-taglibsartifactId>  
  10.   <version>3.1.2.RELEASEversion>  
  11. dependency>  
  12. <dependency>  
  13.   <groupId>org.springframework.securitygroupId>  
  14.   <artifactId>spring-security-coreartifactId>  
  15.   <version>3.1.2.RELEASEversion>  
  16. dependency>  
  17. <dependency>  
  18.   <groupId>org.springframework.securitygroupId>  
  19.   <artifactId>spring-security-configartifactId>  
  20.   <version>3.1.2.RELEASEversion>  
  21. dependency>  
  22.   

 

 2. 设置项目的web.xml,Spring Security3的过滤器:

Xml代码   收藏代码
  1. <filter>  
  2.  <filter-name>springSecurityFilterChainfilter-name>  
  3.  <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>  
  4. filter>  
  5.  <filter-mapping>  
  6.  <filter-name>springSecurityFilterChainfilter-name>  
  7.  <url-pattern>/*url-pattern>  
  8. filter-mapping>  

  

3. 增加applicationContext-security.xml,整体配置在附件代码中。

 

    3.1) 验证管理器配置,用户登陆的时候进行的验证,这里采用JDBC的模式,如果不用开启hideUserNotFoundExceptions,则可以采用简单的标签配置。

Xml代码   收藏代码
  1.   
  2. <authentication-manager alias="authenticationManagerBean">  
  3.  <authentication-provider ref="authenticationProvider">  
  4.     
  5.    
  6. <b:bean id="passwordEncoder"   
  7.  class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">  
  8.  <b:constructor-arg index="0" value="256" />  
  9.  <b:property name="encodeHashAsBase64" value="true" />  
  10. b:bean>  

 

    3.2) 授权流程中安全资源加载器和决策器的配置:

Xml代码   收藏代码
  1.   
  2. <b:bean id="accessDecisionManagerBean"   
  3.     class="demo.security.CustomizedAccessDecisionManager" >  
  4.    
  5.  <b:property name="allowIfAllAbstainDecisions" value="false"/>  
  6. b:bean>  
  7.   
  8.   
  9. <b:bean id="securityMetadataSourceBean"   
  10.     class="demo.security.CustomizedInvocationSecurityMetadataSource">  
  11.  <b:constructor-arg index="0" ref="commonDao" />  
  12.  <b:property name="rejectPublicInvocations" value="true" />  
  13. b:bean>  

 

    3.3) 指定过滤器,作为验证和授权的自定义处理入口

Xml代码   收藏代码
  1.    
  2. <b:bean id="customizedFilter" class="demo.security.CustomizedFilterSecurityInterceptor">  
  3.  <b:property name="authenticationManager" ref="authenticationManagerBean" />  
  4.  <b:property name="accessDecisionManager" ref="accessDecisionManagerBean" />  
  5.  <b:property name="securityMetadataSource" ref="securityMetadataSourceBean" />  
  6. b:bean>  

 

    3.4) 同时增加异常处理配置,针对验证过程中的异常记性处理

Xml代码   收藏代码
  1.   
  2. <b:bean id="exceptionMappingAuthenticationFailureHandler"   
  3.     class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">  
  4.  <b:property name="exceptionMappings">  
  5.   <b:map>  
  6.      
  7.    <b:entry key="org.springframework.security.core.userdetails.UsernameNotFoundException"    
  8.     value="/login.jsp?sign=No User" />  
  9.      
  10.    <b:entry key="org.springframework.security.authentication.BadCredentialsException"    
  11.     value="/login.jsp?sign=Bad Credentials" />  
  12.      
  13.    <b:entry key="org.springframework.security.authentication.DisabledException"    
  14.     value="/login.jsp?sign=User is disabled" />  
  15.      
  16.    <b:entry key="org.springframework.security.core.AuthenticationException"    
  17.     value="/login.jsp?sign=Authentication Failure" />  
  18.   b:map>  
  19.  b:property>  
  20. b:bean>  

 

    3.5) 现在将上述的整体将上述配置整体配置到SpringSecurity3的过滤器链中:

Xml代码   收藏代码
  1.   
  2. <http access-denied-page="/WEB-INF/error/403.jsp" pattern="/*.htm*">  
  3.     
  4.   <form-login login-page="/login.jsp" username-parameter="loginName"   
  5.     password-parameter="password" login-processing-url="/login.htm"  
  6.     authentication-failure-url="/login.jsp?sign=BadCredentials"   
  7.     default-target-url="/index.htm" always-use-default-target="true"   
  8.     authentication-failure-handler-ref="exceptionMappingAuthenticationFailureHandler" />  
  9.     
  10.   <anonymous enabled="false" />  
  11.     
  12.   <logout logout-success-url="/login.jsp" logout-url="/logout.htm" />  
  13.   <http-basic />  
  14.     
  15.   <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="customizedFilter" />  
  16. http>  

 

    3.6) 同时增加自身项目需要白名单设置:

Xml代码   收藏代码
  1.   
  2. <b:bean id="securityMetadataSourceTrustListHolder"   
  3.     class="demo.security.util.SecurityMetadataSourceTrustListHolder" >  
  4.     <b:property name="trustList">  
  5.         <b:list>  
  6.             <b:value>/index.htmb:value>  
  7.             <b:value>/hello.htmb:value>  
  8.         b:list>  
  9.     b:property>  
  10. b:bean>  
  11.   
  12.   
  13. <b:bean id="securityUserTrustListHolder"   
  14.     class="demo.security.util.SecurityUserTrustListHolder" >  
  15.     <b:property name="trustList">  
  16.         <b:list>  
  17.             <b:value>administratorb:value>  
  18.         b:list>  
  19.     b:property>  
  20. b:bean>  

 

    3.7) 开启SpringSecurity3日志:

Xml代码   收藏代码
  1.   
  2. <b:bean class="org.springframework.security.authentication.event.LoggerListener"/>  
  3. <b:bean class="org.springframework.security.access.event.LoggerListener"/>  

 

  4. 自定义拦截器代码。最核心的代码就是invoke方法中的InterceptorStatusToken token = super.beforeInvocation(fi);这一句,即在执行doFilter之前,进行权限的检查,而具体的实现已经交给accessDecisionManager了。

Java代码   收藏代码
  1. /** 
  2.  * 权限拦截器 
  3.  *  
  4.  * @author Watson Xu 
  5.  * @since 1.0.7 

    2013-7-10 下午4:12:18

     
  6.  */  
  7. public class CustomizedFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {  
  8.   
  9.     private FilterInvocationSecurityMetadataSource securityMetadataSource;  
  10.     private static Log logger = LogFactory.getLog(CustomizedFilterSecurityInterceptor.class);  
  11.   
  12.     // ~ Methods  
  13.     // ========================================================================================================  
  14.   
  15.     /** 
  16.      * Method that is actually called by the filter chain. Simply delegates to 
  17.      * the {@link #invoke(FilterInvocation)} method. 
  18.      *  
  19.      * @param request  the servlet request 
  20.      * @param response   the servlet response 
  21.      * @param chain  the filter chain 
  22.      * @throws IOException   if the filter chain fails 
  23.      * @throws ServletException if the filter chain fails 
  24.      */  
  25.     public void doFilter(ServletRequest request, ServletResponse response,  
  26.             FilterChain chain) throws IOException, ServletException {  
  27.         //@1  
  28.         HttpServletRequest httpRequest = (HttpServletRequest)request;  
  29.         HttpServletResponse httpResponse = (HttpServletResponse)response;  
  30.         String url = httpRequest.getRequestURI().replaceFirst(httpRequest.getContextPath(), "");  
  31.           
  32.         //  1.1)判断登陆状态:如果未登陆则要求登陆  
  33.         if(!SessionUserDetailsUtil.isLogined()) {  
  34.             httpResponse.sendRedirect(httpRequest.getContextPath() + SecurityConstants.LOGIN_URL);  
  35.             logger.info("未登陆用户,From IP:" + SecutiryRequestUtil.getRequestIp(httpRequest) + "访问 :URI" + url);  
  36.             return;  
  37.         }  
  38.           
  39.         //  1.2)过用户白名单:如果为超级管理员,则直接执行  
  40.         if(SecurityUserTrustListHolder.isTrustUser(SessionUserDetailsUtil.getLoginUserName())) {  
  41.             chain.doFilter(request, response);  
  42.             return;  
  43.         }  
  44.           
  45.         //  1.3)过资源(URL)白名单:如果为公共页面,直接执行  
  46.         if(SecurityMetadataSourceTrustListHolder.isTrustSecurityMetadataSource(url)){  
  47.             chain.doFilter(request, response);  
  48.             return;  
  49.         }  
  50.           
  51.         FilterInvocation fi = new FilterInvocation(request, response, chain);  
  52.         invoke(fi);  
  53.     }  
  54.   
  55.     public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {  
  56.         return this.securityMetadataSource;  
  57.     }  
  58.   
  59.     public Classextends Object> getSecureObjectClass() {  
  60.         return FilterInvocation.class;  
  61.     }  
  62.   
  63.     public void invoke(FilterInvocation fi) throws IOException,  
  64.             ServletException {  
  65.         //@2,进行安全验证  
  66.         InterceptorStatusToken token = null;  
  67.         try {  
  68.             token = super.beforeInvocation(fi);  
  69.         } catch (IllegalArgumentException e) {  
  70.             HttpServletRequest httpRequest = fi.getRequest();  
  71.             HttpServletResponse httpResponse = fi.getResponse();  
  72.             String url = httpRequest.getRequestURI().replaceFirst(httpRequest.getContextPath(), "");  
  73.             logger.info("用户 " + SessionUserDetailsUtil.getLoginUserName() + ",From IP:" + SecutiryRequestUtil.getRequestIp(httpRequest) + "。尝试访问未授权(或者) URI:" + url);  
  74.               
  75.             httpResponse.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);  
  76.             RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(SecurityConstants.NOT_ACCEPTABLE);  
  77.             dispatcher.forward(httpRequest, httpResponse);  
  78.             return;  
  79.         }  
  80.           
  81.         try {  
  82.             fi.getChain().doFilter(fi.getRequest(), fi.getResponse());  
  83.         } finally {  
  84.             super.afterInvocation(token, null);  
  85.         }  
  86.     }  
  87.   
  88.     public SecurityMetadataSource obtainSecurityMetadataSource() {  
  89.         return this.securityMetadataSource;  
  90.     }  
  91.   
  92.     public void setSecurityMetadataSource(  
  93.             FilterInvocationSecurityMetadataSource newSource) {  
  94.         this.securityMetadataSource = newSource;  
  95.     }  
  96.   
  97.     @Override  
  98.     public void destroy() {  
  99.     }  
  100.   
  101.     @Override  
  102.     public void init(FilterConfig arg0) throws ServletException {  
  103.     }  
  104.   
  105. }  

 

  5. 自定义资源的访问权限的定义加载器

Java代码   收藏代码
  1. /** 
  2.  * 安全资源(URL)和角色映射关系处理器 
  3.  *  
  4.  * @author Watson Xu 
  5.  * @since 1.0.7 

    2013-7-9 下午3:25:09

     
  6.  */  
  7. public class CustomizedInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {  
  8.       
  9.     private boolean rejectPublicInvocations = false;  
  10.     private CommonDao dao;  
  11.   
  12.     private static Map resources = new HashMap();  
  13.       
  14.     public CustomizedInvocationSecurityMetadataSource(CommonDao dao) {  
  15.         this.dao = dao;  
  16.         loadSecurityMetadataSource();  
  17.     }  
  18.       
  19.     // According to a URL, Find out permission configuration of this URL.  
  20.     // 根据URL来查找所有能够访问该资源的角色。  
  21.     public Collection getAttributes(Object object) throws IllegalArgumentException {  
  22.         // guess object is a URL.  
  23.         //@3  
  24.         String url = ((FilterInvocation)object).getRequestUrl();  
  25.           
  26.         if(resources.isEmpty()) return null;  
  27.         Integer resourceId = resources.get(url);  
  28.         if(rejectPublicInvocations && resourceId == null) {  
  29.             throw new IllegalArgumentException("Secure object invocation " + object +  
  30.                     " was denied as public invocations are not allowed via this interceptor. ");//请求不存在  
  31.         }  
  32.         return getRolesByResouceId(resourceId);  
  33.     }  
  34.   
  35.     private Collection getRolesByResouceId(Integer resourceId) {  
  36.         List  roles = dao.getRoleByResourceId(resourceId);  
  37.           
  38.         Collection atts = null;  
  39.         if(roles != null) {  
  40.             atts = new ArrayList();  
  41.             for (String role : roles) {  
  42.                 atts.add(new SecurityConfig(role));  
  43.             }  
  44.         }  
  45.           
  46.         return atts;  
  47.     }  
  48.       
  49.     //加载所有安全资源(URL)  
  50.     private void loadSecurityMetadataSource() {  
  51.         List> resourceDtos = dao.getAllResource();  
  52.         if(resourceDtos != null) {  
  53.             resources.clear();  
  54.             for (Map dto : resourceDtos) {  
  55.                 resources.put(dto.get("url").toString(), Integer.parseInt(dto.get("id").toString()));   
  56.             }  
  57.         }  
  58.     }  
  59.       
  60.     public boolean supports(Class clazz) {  
  61.         return true;  
  62.     }  
  63.       
  64.     public Collection getAllConfigAttributes() {  
  65.         return null;  
  66.     }  
  67.   
  68.     public void setDao(CommonDao dao) {  
  69.         this.dao = dao;  
  70.     }  
  71.   
  72.     public void setRejectPublicInvocations(boolean rejectPublicInvocations) {  
  73.         this.rejectPublicInvocations = rejectPublicInvocations;  
  74.     }  
  75.   
  76. }  
   6. 自定义决策器。在这个类中,最重要的是decide方法,如果不存在对该资源的定义,直接放行;否则,如果找到正确的角色,即认为拥有权限,并放行,否则throw new AccessDeniedException("no right");这样,就会进入上面提到的403.jsp页面。
Java代码   收藏代码
  1. /** 
  2.  * 安全资源(URL)权限决策器 
  3.  *  
  4.  * @author Watson Xu 
  5.  * @since 1.0.7 

    2013-7-10 下午4:08:42

     
  6.  */  
  7. public class CustomizedAccessDecisionManager implements AccessDecisionManager {  
  8.   
  9.     private boolean allowIfAllAbstainDecisions = false;  
  10.       
  11.     public boolean isAllowIfAllAbstainDecisions() {  
  12.         return allowIfAllAbstainDecisions;  
  13.     }  
  14.   
  15.     public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {  
  16.         this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;  
  17.     }  
  18.   
  19.     protected final void checkAllowIfAllAbstainDecisions() {  
  20.         if (!this.isAllowIfAllAbstainDecisions()) {  
  21.             throw new AccessDeniedException("Access is denied");  
  22.         }  
  23.     }  
  24.       
  25.     //In this method, need to compare authentication with configAttributes.  
  26.     // 1, A object is a URL, a filter was find permission configuration by this URL, and pass to here.  
  27.     // 2, Check authentication has attribute in permission configuration (configAttributes)  
  28.     // 3, If not match corresponding authentication, throw a AccessDeniedException.  
  29.       
  30.     //这里的三个参数可以片面的理解为: 用户登录后的凭证(其中包含了用户名和角色的对应关系)、请求的URL、请求的URL对应角色(这些角色可以访问这些URL)  
  31.     public void decide(Authentication authentication, Object object, Collection configAttributes)  
  32.             throws AccessDeniedException, InsufficientAuthenticationException {  
  33.         //@4  
  34.         if(configAttributes == null){  
  35.             return;  
  36.         }  
  37.           
  38.         Iterator ite=configAttributes.iterator();  
  39.         while(ite.hasNext()){  
  40.             ConfigAttribute ca=ite.next();  
  41.             String needRole=((SecurityConfig)ca).getAttribute();  
  42.             for(GrantedAuthority ga:authentication.getAuthorities()){  
  43.                 if(needRole.equals(ga.getAuthority())){  //ga is user's role.  
  44.                     return;  
  45.                 }  
  46.             }  
  47.         }  
  48.           
  49.         checkAllowIfAllAbstainDecisions();  
  50.     }  
  51.   
  52.     @Override  
  53.     public boolean supports(ConfigAttribute attribute) {  
  54.         return true;  
  55.     }  
  56.   
  57.     @Override  
  58.     public boolean supports(Class clazz) {  
  59.         return true;  
  60.     }  
  61.   
  62.   
  63. }  

 

  6. 其他工具类代码这不做赘述,具体可以参考附件中的工程代码。

 

参考:

  1. Spring Security官方文档:http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity.html
  2. 官方文档中文翻译:http://www.mossle.com/docs/springsecurity3/html/springsecurity.html
  3. 入门教程:http://www.blogjava.net/fastzch/archive/2013/05/02/315028.html
  4. 更多的自定义选择:http://blog.csdn.net/shadowsick/article/details/8576449

 

  • SpringSecurity3.zip (28.2 KB)
  • 下载次数: 94

你可能感兴趣的:(spring,security)