debug調試: 運行:http://localhost:8080/acegitest1/index.jsp 因為web.xml中配置:
<filter-name >AcegiFilterChainProxy </filter-name > <filter-class > org.acegisecurity.util.FilterToBeanProxy </filter-class > <init-param > <param-name >targetBean </param-name > <param-value >filterChainProxy </param-value > </init-param >
1. 進入:FilterToBeanProxy代理類中的doFilter,因為init只執行一次,啟動時已經執行一次,所以訪問url時,直接直接進入doFilter方法。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!initialized ) { doInit(); } delegate.doFilter(request, response, chain); }2. 進入:目標類filterChainProxy 中的doFilter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); ConfigAttributeDefinition cad = this.filterInvocationDefinitionSource .getAttributes(fi); if (cad == null) { if ( logger.isDebugEnabled()) { logger.debug(fi.getRequestUrl() + " has no matching filters"); } chain.doFilter(request, response); return; } Filter[] filters = obtainAllDefinedFilters(cad); if (filters.length == 0) { if ( logger.isDebugEnabled()) { logger.debug(fi.getRequestUrl() + " has an empty filter list"); } chain.doFilter(request, response); return; } VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters); virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse()); } 這個方法,主要擷取aceg設定檔中,使用者配置了多少個filter,並且filter順序以數組形式展示,這樣就開始filter過濾連了。3.進入:過濾連第一個filter,也就是咱們配置的basicProcessingFilter,其中doFilter如下:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!(request instanceof HttpServletRequest)) { throw new ServletException( "Can only process HttpServletRequest"); } if (!(response instanceof HttpServletResponse)) { throw new ServletException( "Can only process HttpServletResponse"); } HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; 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); } 這個關鍵在於
if ((header !=
null) && header.startsWith( "Basic ")),其中basic認證是把使用者輸入使用者名稱和密碼存放到header中,basic認證的。因為這個header為null【稍後在分析header】,所以執行下一個filter。4.進入exceptionfilter5.進入filterInvocationInterceptor
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } 跟著進去invoke,然後進入InterceptorStatusToken token =
super.beforeInvocation(fi);最後關鍵處在於ConfigAttributeDefinition attr =
this.obtainObjectDefinitionSource ().getAttributes(object); obtainObjectDefinitionSource 大家眼熟吧,就是acegi設定檔,配置那個檔案有那個角色。因為index.jsp沒有配置,所以擷取為null6.類似回呼函數,一步步返回。這個頁面就執行了一遍,即使exceptionfilter捕捉了異常,但是因為index.jsp沒有配置授權的事情,所以異常filter中的異常策略authenticationEntryPoint沒執行。 index.jsp訪問完畢,我們來調試訪問受保護的頁面security.jsp,因為acegi進行配置了。上述已經把基本的流程跟大家調通,接下來就是不同之處,當然也是這個過濾連流程。因為訪問受保護的資源,所以捕捉沒有輸入使用者名稱和密碼,然後異常交給basic處理,basic認證是彈出框,輸入使用者名稱和密碼的。 那我們輸入正確有效使用者,測試如下: header如下: 你配置保護urlObjectDefinitionSource擷取許可權:
public ConfigAttributeDefinition lookupAttributes(String url) { // Strip anything after a question mark symbol, as per SEC-161. See also SEC-321 int firstQuestionMarkIndex = url.indexOf( "?"); if (firstQuestionMarkIndex != -1) { url = url.substring(0, firstQuestionMarkIndex); } if (isConvertUrlToLowercaseBeforeComparison()) { url = url.toLowerCase(); if ( logger.isDebugEnabled()) { logger.debug( "Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'"); } } Iterator iter = requestMap.iterator(); while (iter.hasNext()) { EntryHolder entryHolder = (EntryHolder) iter.next(); boolean matched = pathMatcher.match(entryHolder.getAntPath(), url); if ( logger.isDebugEnabled()) { logger.debug( "Candidate is: '" + url + "'; pattern is " + entryHolder.getAntPath() + "; matched=" + matched); } if (matched) { return entryHolder.getConfigAttributeDefinition(); } } return null; } 進行投票機制: 關鍵代碼:
authenticated = SecurityContextHolder.getContext().getAuthentication(); this. accessDecisionManager .decide(authenticated, object, attr); public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) { int result = ACCESS_ABSTAIN; Iterator iter = config.getConfigAttributes(); while (iter.hasNext()) { ConfigAttribute attribute = (ConfigAttribute) iter.next(); if ( this.supports(attribute)) { result = ACCESS_DENIED; // Attempt to find a matching granted authority for ( int i = 0; i < authentication.getAuthorities().length ; i++) { if (attribute.getAttribute().equals(authentication.getAuthorities()[i].getAuthority())) { return ACCESS_GRANTED; } } } } return result; } while (iter.hasNext()) { AccessDecisionVoter voter = (AccessDecisionVoter) iter.next(); int result = voter.vote(authentication, object, config); switch (result) { case AccessDecisionVoter. ACCESS_GRANTED: return; case AccessDecisionVoter. ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied" , "Access is denied")); } 上述給的不是特別完整的代碼,但是都是很關鍵的代碼。大家可以下載源碼,然後自己debug調試,同時我也會把acegi源碼附上。 一般情況下完整的系統很少使用basic認證,因為每個系統都有自己的登陸頁,若未登陸應該跳轉到登陸頁面,若許可權不足,應該跳轉到accessdefined頁面。那我們下篇部落格搭建基於表單認證。 項目源碼: