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对(用户登录)验证和(安全资源)授权的基本流程如下图:

 Spring Security3实践总结_第1张图片

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

 

三、实现

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

 

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



  org.springframework.security
  spring-security-web
  3.1.2.RELEASE


  org.springframework.security
  spring-security-taglibs
  3.1.2.RELEASE


  org.springframework.security
  spring-security-core
  3.1.2.RELEASE


  org.springframework.security
  spring-security-config
  3.1.2.RELEASE

 

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


 springSecurityFilterChain
 org.springframework.web.filter.DelegatingFilterProxy

 
 springSecurityFilterChain
 /*

  

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

 

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



 
  
 



 
 
 
 
 



 
 
 




 
 

 

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



 
 




 
 

 

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

 

 
 
 

 

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



 
  
   
   
   
   
   
   
   
   
  
 

 

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



  
  
  
  
  
  
  
  
  

 

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



	
		
			/index.htm
			/hello.htm
		
	




	
		
			administrator
		
	

 

    3.7) 开启SpringSecurity3日志:



 

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

/**
 * 权限拦截器
 * 
 * @author Watson Xu
 * @since 1.0.7 

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

*/ public class CustomizedFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; private static Log logger = LogFactory.getLog(CustomizedFilterSecurityInterceptor.class); // ~ Methods // ======================================================================================================== /** * Method that is actually called by the filter chain. Simply delegates to * the {@link #invoke(FilterInvocation)} method. * * @param request the servlet request * @param response the servlet response * @param chain the filter chain * @throws IOException if the filter chain fails * @throws ServletException if the filter chain fails */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //@1 HttpServletRequest httpRequest = (HttpServletRequest)request; HttpServletResponse httpResponse = (HttpServletResponse)response; String url = httpRequest.getRequestURI().replaceFirst(httpRequest.getContextPath(), ""); // 1.1)判断登陆状态:如果未登陆则要求登陆 if(!SessionUserDetailsUtil.isLogined()) { httpResponse.sendRedirect(httpRequest.getContextPath() + SecurityConstants.LOGIN_URL); logger.info("未登陆用户,From IP:" + SecutiryRequestUtil.getRequestIp(httpRequest) + "访问 :URI" + url); return; } // 1.2)过用户白名单:如果为超级管理员,则直接执行 if(SecurityUserTrustListHolder.isTrustUser(SessionUserDetailsUtil.getLoginUserName())) { chain.doFilter(request, response); return; } // 1.3)过资源(URL)白名单:如果为公共页面,直接执行 if(SecurityMetadataSourceTrustListHolder.isTrustSecurityMetadataSource(url)){ chain.doFilter(request, response); return; } FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } public Class getSecureObjectClass() { return FilterInvocation.class; } public void invoke(FilterInvocation fi) throws IOException, ServletException { //@2,进行安全验证 InterceptorStatusToken token = null; try { token = super.beforeInvocation(fi); } catch (IllegalArgumentException e) { HttpServletRequest httpRequest = fi.getRequest(); HttpServletResponse httpResponse = fi.getResponse(); String url = httpRequest.getRequestURI().replaceFirst(httpRequest.getContextPath(), ""); logger.info("用户 " + SessionUserDetailsUtil.getLoginUserName() + ",From IP:" + SecutiryRequestUtil.getRequestIp(httpRequest) + "。尝试访问未授权(或者) URI:" + url); httpResponse.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(SecurityConstants.NOT_ACCEPTABLE); dispatcher.forward(httpRequest, httpResponse); return; } 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; } @Override public void destroy() { } @Override public void init(FilterConfig arg0) throws ServletException { } }

 

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

/**
 * 安全资源(URL)和角色映射关系处理器
 * 
 * @author Watson Xu
 * @since 1.0.7 

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

*/ public class CustomizedInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private boolean rejectPublicInvocations = false; private CommonDao dao; private static Map resources = new HashMap(); public CustomizedInvocationSecurityMetadataSource(CommonDao dao) { this.dao = dao; loadSecurityMetadataSource(); } // According to a URL, Find out permission configuration of this URL. // 根据URL来查找所有能够访问该资源的角色。 public Collection getAttributes(Object object) throws IllegalArgumentException { // guess object is a URL. //@3 String url = ((FilterInvocation)object).getRequestUrl(); if(resources.isEmpty()) return null; Integer resourceId = resources.get(url); if(rejectPublicInvocations && resourceId == null) { throw new IllegalArgumentException("Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. ");//请求不存在 } return getRolesByResouceId(resourceId); } private Collection getRolesByResouceId(Integer resourceId) { List roles = dao.getRoleByResourceId(resourceId); Collection atts = null; if(roles != null) { atts = new ArrayList(); for (String role : roles) { atts.add(new SecurityConfig(role)); } } return atts; } //加载所有安全资源(URL) private void loadSecurityMetadataSource() { List> resourceDtos = dao.getAllResource(); if(resourceDtos != null) { resources.clear(); for (Map dto : resourceDtos) { resources.put(dto.get("url").toString(), Integer.parseInt(dto.get("id").toString())); } } } public boolean supports(Class clazz) { return true; } public Collection getAllConfigAttributes() { return null; } public void setDao(CommonDao dao) { this.dao = dao; } public void setRejectPublicInvocations(boolean rejectPublicInvocations) { this.rejectPublicInvocations = rejectPublicInvocations; } }
   6. 自定义决策器。在这个类中,最重要的是decide方法,如果不存在对该资源的定义,直接放行;否则,如果找到正确的角色,即认为拥有权限,并放行,否则throw new AccessDeniedException("no right");这样,就会进入上面提到的403.jsp页面。
/**
 * 安全资源(URL)权限决策器
 * 
 * @author Watson Xu
 * @since 1.0.7 

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

*/ public class CustomizedAccessDecisionManager implements AccessDecisionManager { private boolean allowIfAllAbstainDecisions = false; public boolean isAllowIfAllAbstainDecisions() { return allowIfAllAbstainDecisions; } public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) { this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions; } protected final void checkAllowIfAllAbstainDecisions() { if (!this.isAllowIfAllAbstainDecisions()) { throw new AccessDeniedException("Access is denied"); } } //In this method, need to compare authentication with configAttributes. // 1, A object is a URL, a filter was find permission configuration by this URL, and pass to here. // 2, Check authentication has attribute in permission configuration (configAttributes) // 3, If not match corresponding authentication, throw a AccessDeniedException. //这里的三个参数可以片面的理解为: 用户登录后的凭证(其中包含了用户名和角色的对应关系)、请求的URL、请求的URL对应角色(这些角色可以访问这些URL) public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { //@4 if(configAttributes == null){ return; } Iterator 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())){ //ga is user's role. return; } } } checkAllowIfAllAbstainDecisions(); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class clazz) { return true; } }

 

  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)
  • 下载次数: 514

你可能感兴趣的:(Spring,Security3,实践总结)