Spring Acegi鉴权管理之基础模式(BASIC)

阅读更多

Acegi久负盛名,这个家伙是一个spring中广泛使用的认证和安全工具,最初由spring社区爱好者发起,目的是为spring应用提供一个安全服务,比如用户认证及授权等。后来spring官方觉得这个东西很不错,就收编了,并且在2006年发布了spring官方的1.0版本。虽然是基于Acegi,但springsecurity已经在原有基础上增加了很多新的特性进来。为了能够方便一窥Acegi的真容,我们通过一个basic模式来看下Acegi是如何来处理用户认证及授权工作。

1、配置安全所需过滤器org.acegisecurity.util.FilterChainProxy,填充 filterInvocationDefinitionSource 属性如下所示:


    
	 
	     PATTERN_TYPE_APACHE_ANT
	     /**=basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
	 
    

 注:默认情况下,在filterInvocationDefinitionSource属性中指明使用PATTERN_TYPE_APACHE_ANT,则说明,这里的配置信息是启用Apache Ant路径风格的URL匹配模式,FilterInvocationDefinitionSourceEditor会实例化PathBasedFilterInvocationDefinitionMap实例。如果这里没有指定则采用默认的正是表达式,此时RegExpBasedFilterInvocationDefinitionMap会被实例化。

FilterInvocationDefinitionSourceEditor在进行初始化过程中,acegi源码处理过程的片段代码如下:

if ((s == null) || "".equals(s)) {
    // Leave target object empty
    source.setDecorated(new RegExpBasedFilterInvocationDefinitionMap());
} else {
    // Check if we need to override the default definition map
    if (s.lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1) {
        source.setDecorated(new PathBasedFilterInvocationDefinitionMap());

        if (logger.isDebugEnabled()) {
            logger.debug(("Detected " + DIRECTIVE_PATTERN_TYPE_APACHE_ANT
                + " directive; using Apache Ant style path expressions"));
        }
    } else {
        source.setDecorated(new RegExpBasedFilterInvocationDefinitionMap());
    }

    if (s.lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1) {
        if (logger.isDebugEnabled()) {
            logger.debug("Detected " + DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                + " directive; Instructing mapper to convert URLs to lowercase before comparison");
        }

        source.setConvertUrlToLowercaseBeforeComparison(true);
    }

 另外需要说明的是,PathBasedFilterInvocationDefinitionMap和RegExpBasedFilterInvocationDefinitionMap都是接口FilterInvocationDefinitionSource的实现类。

FilterInvocationDefinitionSourceEditor在初始化athBasedFilterInvocationDefinitionMap和RegExpBasedFilterInvocationDefinitionMap类时,提供了2个常量用:

  • DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON:FilterInvocationDefinitionSourceEditor类通过该常量的设值情况判断是否对当前路径进行小写转换
  • PATTERN_TYPE_APACHE_ANT:FilterInvocationDefinitionSourceEditor通过这个常量决定具体是采用正则模式还是ant路径风格模式。

我们这里根据执行顺序,指明了3个filter类过滤执行安全策略:basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor

basicProcessingFilter:在Basic模式下,用户名和密码通过对称加密算法,将用户的登录信息存放在http请求的header信息中。服务器在收到浏览器发送来的验证请求后,将加密过的用户名密码通过Apache提供的commons-codec工具包中的org.apache.commons.codec.binary.Base64进行解码。

例如:发起一次请求验证通过后的http头摘要如下:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, sdch, br
Accept-Language:zh-CN,zh;q=0.8
Authorization:Basic dGVzdDox
Connection:keep-alive
Cookie:JSESSIONID=A749345B4E56805343189AA5A1223655
Host:localhost:8080
Referer:http://localhost:8080/rest-common-acegi/secure.jsp
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36

 

basicProcessingFilter片段代码如下:

String header = httpRequest.getHeader("Authorization");

if (logger.isDebugEnabled()) {
    logger.debug("Authorization header: " + header);
}

if ((header != null) && header.startsWith("Basic ")) {
    String base64Token = header.substring(6);
    String token = new String(Base64.decodeBase64(base64Token.getBytes()));

    String username = "";
    String password = "";
    int delim = token.indexOf(":");

    if (delim != -1) {
        username = token.substring(0, delim);
        password = token.substring(delim + 1);
    }

    if (authenticationIsRequired(username)) {
        UsernamePasswordAuthenticationToken authRequest =
                new UsernamePasswordAuthenticationToken(username, password);
        authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));

        Authentication authResult;

        try {
            authResult = authenticationManager.authenticate(authRequest);
        } catch (AuthenticationException failed) {
            // Authentication failed
            if (logger.isDebugEnabled()) {
                logger.debug("Authentication request for user: " + username + " failed: " + failed.toString());
            }

            SecurityContextHolder.getContext().setAuthentication(null);

            if (rememberMeServices != null) {
                rememberMeServices.loginFail(httpRequest, httpResponse);
            }

            if (ignoreFailure) {
                chain.doFilter(request, response);
            } else {
                authenticationEntryPoint.commence(request, response, failed);
            }

            return;
        }

        // Authentication success
        if (logger.isDebugEnabled()) {
            logger.debug("Authentication success: " + authResult.toString());
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);

        if (rememberMeServices != null) {
            rememberMeServices.loginSuccess(httpRequest, httpResponse, authResult);
        }
    }
}

chain.doFilter(request, response);
}

 

可以看出,basicProcessingFilter从Header中获取Authorization信息,并通过Apache的codec包中的解码工具对token进行解码,从而获取用户输入的用户名、密码信息,用于后面的校验动作。

basicProcessingFilter在获取到验证请求需要用到的用户名及密码信息后,实际的用户有效性验证,交给了org.acegisecurity.providers.ProviderManager来管理的org.acegisecurity.providers.dao.DaoAuthenticationProvider类的执行实际验证处理过程。
对basicProcessingFilter的详细配置如下:


    
    



    



    
         
              
         
    



    


    
        
            test=111111,ROLE_SUPERVISOR
            zhangsan=111111,ROLE_SUPERVISOR,disabled
        
    

 

接下来开始配置exceptionTranslationFilter,配置信息如下:



    

 注:ExceptionTranslationFilter类用来处理权限验证失败时页面的路由情况,我们这里给ExceptionTranslationFilter配置了一个默认的basicProcessingFilterEntryPoint

对异常处理的片段代码如下:

public void commence(ServletRequest request, ServletResponse response, AuthenticationException authException)
    throws IOException, ServletException {
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    httpResponse.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
    httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}

 从上面的代码可以看到,当权限验证失败后,response请求被指向了HttpServletResponse.SC_UNAUTHORIZED页面(“401”访问受限页面)

在HttpServletResponse中定义的返回取值范围及常量定义如下所示:

public static final int SC_CONTINUE = 100;
public static final int SC_SWITCHING_PROTOCOLS = 101;
public static final int SC_OK = 200;
public static final int SC_CREATED = 201;
public static final int SC_ACCEPTED = 202;
public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;
public static final int SC_NO_CONTENT = 204;
public static final int SC_RESET_CONTENT = 205;
public static final int SC_PARTIAL_CONTENT = 206;
public static final int SC_MULTIPLE_CHOICES = 300;
public static final int SC_MOVED_PERMANENTLY = 301;
public static final int SC_MOVED_TEMPORARILY = 302;
public static final int SC_FOUND = 302;
public static final int SC_SEE_OTHER = 303;
public static final int SC_NOT_MODIFIED = 304;
public static final int SC_USE_PROXY = 305;
public static final int SC_TEMPORARY_REDIRECT = 307;
public static final int SC_BAD_REQUEST = 400;
public static final int SC_UNAUTHORIZED = 401;
public static final int SC_PAYMENT_REQUIRED = 402;
public static final int SC_FORBIDDEN = 403;
public static final int SC_NOT_FOUND = 404;
public static final int SC_METHOD_NOT_ALLOWED = 405;
public static final int SC_NOT_ACCEPTABLE = 406;
public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
public static final int SC_REQUEST_TIMEOUT = 408;
public static final int SC_CONFLICT = 409;
public static final int SC_GONE = 410;
public static final int SC_LENGTH_REQUIRED = 411;
public static final int SC_PRECONDITION_FAILED = 412;
public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;
public static final int SC_REQUEST_URI_TOO_LONG = 414;
public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
public static final int SC_EXPECTATION_FAILED = 417;
public static final int SC_INTERNAL_SERVER_ERROR = 500;
public static final int SC_NOT_IMPLEMENTED = 501;
public static final int SC_BAD_GATEWAY = 502;
public static final int SC_SERVICE_UNAVAILABLE = 503;
public static final int SC_GATEWAY_TIMEOUT = 504;
public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;

 

最后配置filterInvocationInterceptor:


    
    
    
        
    



    
        
            
        
    

 注:FilterSecurityInterceptor是filterchain中比较复杂,也是比较核心的过滤器,主要负责授权的工作

spring通过HttpConfigurationBuilder类来为filter构造过滤器实例,代码片段如下:

	private void createFilterSecurityInterceptor(BeanReference authManager) {
		boolean useExpressions = FilterInvocationSecurityMetadataSourceParser
				.isUseExpressions(httpElt);
		RootBeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser
				.createSecurityMetadataSource(interceptUrls, addAllAuth, httpElt, pc);

		RootBeanDefinition accessDecisionMgr;
		ManagedList voters = new ManagedList(2);

		if (useExpressions) {
			//表达式模式,这里省略,不是本例重点
		}
		else {
			voters.add(GrantedAuthorityDefaultsParserUtils.registerWithDefaultRolePrefix(pc, RoleVoterBeanFactory.class));
			voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
		}
		accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
		accessDecisionMgr.getConstructorArgumentValues().addGenericArgumentValue(voters);
		accessDecisionMgr.setSource(pc.extractSource(httpElt));

		// Set up the access manager reference for http
		String accessManagerId = httpElt.getAttribute(ATT_ACCESS_MGR);

		if (!StringUtils.hasText(accessManagerId)) {
			accessManagerId = pc.getReaderContext().generateBeanName(accessDecisionMgr);
			pc.registerBeanComponent(new BeanComponentDefinition(accessDecisionMgr,
					accessManagerId));
		}

		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.rootBeanDefinition(FilterSecurityInterceptor.class);

		builder.addPropertyReference("accessDecisionManager", accessManagerId);
		builder.addPropertyValue("authenticationManager", authManager);

		if ("false".equals(httpElt.getAttribute(ATT_ONCE_PER_REQUEST))) {
			builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
		}

		builder.addPropertyValue("securityMetadataSource", securityMds);
		BeanDefinition fsiBean = builder.getBeanDefinition();
		String fsiId = pc.getReaderContext().generateBeanName(fsiBean);
		pc.registerBeanComponent(new BeanComponentDefinition(fsiBean, fsiId));

		// Create and register a DefaultWebInvocationPrivilegeEvaluator for use with
		// taglibs etc.
		BeanDefinition wipe = new RootBeanDefinition(
				DefaultWebInvocationPrivilegeEvaluator.class);
		wipe.getConstructorArgumentValues().addGenericArgumentValue(
				new RuntimeBeanReference(fsiId));

		pc.registerBeanComponent(new BeanComponentDefinition(wipe, pc.getReaderContext()
				.generateBeanName(wipe)));

		this.fsi = new RuntimeBeanReference(fsiId);
	}

 从上面的代码片段可以看出,在FilterInvocationSecurityMetadataSourceParser类中定义了一个静态方法用于处理鉴权元数据,代码片段如下:

	static RootBeanDefinition createSecurityMetadataSource(List interceptUrls,
			boolean addAllAuth, Element httpElt, ParserContext pc) {
		MatcherType matcherType = MatcherType.fromElement(httpElt);
		boolean useExpressions = isUseExpressions(httpElt);

		ManagedMap requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
				matcherType, interceptUrls, useExpressions, addAllAuth, pc);
		BeanDefinitionBuilder fidsBuilder;

		if (useExpressions) {
			Element expressionHandlerElt = DomUtils.getChildElementByTagName(httpElt,
					Elements.EXPRESSION_HANDLER);
			String expressionHandlerRef = expressionHandlerElt == null ? null
					: expressionHandlerElt.getAttribute("ref");

			if (StringUtils.hasText(expressionHandlerRef)) {
				logger.info("Using bean '" + expressionHandlerRef
						+ "' as web SecurityExpressionHandler implementation");
			}
			else {
				expressionHandlerRef = registerDefaultExpressionHandler(pc);
			}

			fidsBuilder = BeanDefinitionBuilder
					.rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
			fidsBuilder.addConstructorArgValue(requestToAttributesMap);
			fidsBuilder.addConstructorArgReference(expressionHandlerRef);
		}
		else {
			fidsBuilder = BeanDefinitionBuilder
					.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
			fidsBuilder.addConstructorArgValue(requestToAttributesMap);
		}

		fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(httpElt));

		return (RootBeanDefinition) fidsBuilder.getBeanDefinition();
	}

 

完整的spring-acegi.xml配置如下所示(完整路径:src/main/resources/META-INF/spring/spring-acegi.xml):



     
            
                 
                     PATTERN_TYPE_APACHE_ANT
                     /**=basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
                 
            
     

     
            
            
     

     
            
     

     
            
                 
            
     

     
            
     

    
     
            
                 
                     test=1,ROLE_SUPERVISOR
                     zhangsan=1,ROLE_SUPERVISOR,disabled
                 
            
     

     
     
            
     

   
            
            
            
                 
            
     

     
            
                 
            
     

 

对应的web.xml文件配置信息如下所示:

  

  Archetype Created Web Application
  
    contextConfigLocation
    classpath:META-INF/spring/spring-acegi.xml
  
  
  
    AcegiFilterChainProxy
    
                org.acegisecurity.util.FilterToBeanProxy
           
    
      targetBean
      filterChainProxy
   
 
  
    AcegiFilterChainProxy
    /j_acegi_security_check
 
  
    AcegiFilterChainProxy
    /j_acegi_logout
 
  
    AcegiFilterChainProxy
    *.do
 
  
    AcegiFilterChainProxy
    *.jsp
 
  
      index.jsp
 

  
       
        org.springframework.web.context.ContextLoaderListener
      
  

 

不失完整性,用于构建工程用到的指令如下:

mvn archetype:generate -DgroupId=com.myteay -DartifactId=rest-common-acegi -Dversion=1.0.0 -Dpackage=com.myteay -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeGroupId=org.apache.maven.archetypes  -DinteractiveMode=false

 

方便使用起见,贴出工程用到的完整pom


  4.0.0
  com.myteay
  rest-common-acegi
  war
  1.0.0
  rest-common-acegi Maven Webapp
  http://maven.apache.org
  
		  
		    org.springframework.security  
		    spring-security-core  
		    4.2.2.RELEASE  
		 
		
		  
		    org.springframework.security  
		    spring-security-web  
		    4.2.2.RELEASE  
		

		  
		    org.springframework.security  
		    spring-security-config  
		    4.2.2.RELEASE  
		    runtime  
		
		
	        org.apache.geronimo.specs
	        geronimo-servlet_2.4_spec
	        1.1.1
	        provided
	    
		
		
			org.springframework
			spring-aop
			4.2.2.RELEASE
		
		
			org.springframework
			spring-beans
			4.2.2.RELEASE
		
		
			org.springframework
			spring-context
			4.2.2.RELEASE
		
		
			org.springframework
			spring-context-support
			4.2.2.RELEASE
		
		
			org.springframework
			spring-core
			4.2.2.RELEASE
		
		
			org.springframework
			spring-expression
			4.2.2.RELEASE
		
		
			org.springframework
			spring-jdbc
			4.2.2.RELEASE
		
		
			org.springframework
			spring-oxm
			4.2.2.RELEASE
		
		
			org.springframework
			spring-tx
			4.2.2.RELEASE
		
		
			org.springframework
			spring-web
			4.2.2.RELEASE
		
		
			org.springframework
			spring-webmvc
			4.2.2.RELEASE
		
		
			org.springframework
			spring-orm
			4.2.2.RELEASE
		
		
			org.springframework
			spring-test
			4.2.2.RELEASE
		
		

		
		
			jmock
			jmock
			1.2.0
		
		
			jmock
			jmock-cglib
			1.2.0
		
    
      junit
      junit
      3.8.1
      test
    
	
		org.acegisecurity
		acegi-security
		1.0.7
	
  
  
    rest-common-acegi
    
		
			org.mortbay.jetty
			maven-jetty-plugin
			6.1.26
			
				3
				
					
						80
					
				
				
					
						src/main/webapp/WEB-INF
						
							**/*.jsp
						
						
							**/*.properties
							**/*.xml
						
					
				
			
		
    
  

 

 

 

 

 

 

你可能感兴趣的:(spring,acegi,鉴权,basic模式)