标签:necessary ping access 应该 eof 防止 tor https 使用
SpringSecurity实现安全管理主要通过滤器(filter)、验证器(AuthenticationManager)、用户数据提供器(ProviderManager)、授权器(accessDecisionManager)、投票器(AccessDecisionVoter)这几个基本模块协作完成的。大概分为两个部分 用户验证 和授权 这个两个部分。这个部分主要在AuthenticationProcessingFilter和AbstractSecurityInterceptor中完成。
使用过SpringSecurity的用户应该知道,首先应该知道web.xml中申明如下配置
     <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>/*</url-pattern>
   </filter-mapping>
大家不要误认为DelegatingFilterProxy是springsecurity的入口,其实DelegatingFilterProxy其实这个类位于spring-web-3.0.5.RELEASE.jar就说明 这个类本身与springsecurity
无关。其实这个类的作用就是就是拦截请求,把这个请求过滤给springSecurityFilterChain的对应的类(FilterChainProxy)来处理。我们通过断点可以发现,当发送请求时首先进入这个DelegatingFilterProxy这个doFilter进行请求拦截,相关的源码如下:
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		// Lazily initialize the delegate if necessary.
          Filter delegateToUse = this.delegate;
         if (delegateToUse == null) {
         synchronized (this.delegateMonitor) {
           if (this.delegate == null) {
          WebApplicationContext wac = findWebApplicationContext();
          if (wac == null) {
        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
      }
      this.delegate = initDelegate(wac);
   }
      delegateToUse = this.delegate;
  } 
		}
       // Let the delegate perform the actual doFilter operation.
      invokeDelegate(delegateToUse, request, response, filterChain);
	}
这里的核心代码就是invokeDelegate(delegateToUse, request, response, filterChain);这个方法,查看的invokeDelegate方法的源码:
     protected void invokeDelegate(
          Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
       throws ServletException, IOException {
        delegate.doFilter(request, response, filterChain);
	}
可以看出就是把这个请求委托给FilterChainProxy来处理,delegate通过断点可以看出就是FilterChainProxy,这个过滤器链默认的顺序为
       ChannelProcessingFilter
       SecurityContextPersistenceFilter
       ConcurrentSessionFilter
       LogoutFilter
       UsernamePasswordAuthenticationFilter/CasAuthenticationFilter/BasicAuthenticationFilter 
       SecurityContextHolderAwareRequestFilter
       JaasApiIntegrationFilter
       RememberMeAuthenticationFilter
       AnonymousAuthenticationFilter
       ExceptionTranslationFilter
       FilterSecurityInterceptor
其中加粗的为重点,UsernamePasswordAuthenticationFilter等(进行登陆验证),FilterSecurityInterceptor(进行授权管理),这两个过滤器一般要自定义。
进入到FilterChainProxy 的 doFilter 相关源码如下:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
            //判断是否进行过滤申请了
            boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
            if (clearContext) {
            try {
            //给FILTER_APPLIED 给设置为true,做一个申请的标志
             request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
              //这个方法是重点 相关的源码如下:
                doFilterInternal(request, response, chain);
              }
            finally {
             SecurityContextHolder.clearContext();
              request.removeAttribute(FILTER_APPLIED);
            }
        }
		else {
			doFilterInternal(request, response, chain);
		}
	}
doFilterInternal 的的源码解析:
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
                     FirewalledRequest fwRequest = firewall
                             .getFirewalledRequest((HttpServletRequest) request);
                             HttpServletResponse fwResponse = firewall
                            .getFirewalledResponse((HttpServletResponse) response);
//依据请求路径获取相应的过滤器链 放到一个集合过滤器依次执行
List<Filter> filters = getFilters(fwRequest);//对这个代码进行相应的源码解析
                           if (filters == null || filters.size() == 0) {
                         if (logger.isDebugEnabled()) {
                         logger.debug(UrlUtils.buildRequestUrl(fwRequest)
                         + (filters == null ? " has no matching filters"
                           : " has an empty filter list"));
                             }
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
                            return;
                       }
                     VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
                         vfc.doFilter(fwRequest, fwResponse);
                           }
private List<Filter> getFilters(HttpServletRequest request) {
//filterChains多个过滤器链 这个属性是 注入的<http pattern="" security="" />注入的,当然springsecurity会自动会加入一个过滤器链如上代码所示。
                      //一般把特殊的权限控制 <http>标签放到默认过滤器前面,不然的话 会被覆盖
                       for (SecurityFilterChain chain : filterChains) {
                            //根据请求的路径 获取相应的过滤器链  
                           if (chain.matches(request)) {
                           //返回第一个匹配的过滤器链的 过滤器集合 并在上面的代码中依次执行(FilterChainProxy的静态内部类中VirtualFilterChain.doFiler()依次执行)
                            return chain.getFilters();
                        }
                      }
                      return null;
         }
下面我们重点讲一下在默认过滤器链中UsernamePasswordAuthenticationFilter(登陆验证过滤器),和FilterSecurityInterceptor(权限管理拦截器).
的
usernamepasswordAuthenticationFilter的过滤器源码解析:过滤器的入口的doFilter,调用的是其父类的AbstractAuthenticationProcessingFilter的dofiler也就是usernamepasswordAuthenticationFiler没有重写父类的代码,dofilter代码如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
//requiresAuthentication 方法匹配请求的路径,如果不是usernamepasswordAuthenticationFiler的默认的路径或者自己配置的路径直接跳过。
//默认的请求的url为j_spring_security_check,当然这个路径可以自己设置.
 public UsernamePasswordAuthenticationFilter() {
        super("/j_spring_security_check");
    }
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
            return;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Request is to process authentication");
        }
Authentication authResult;
try {
//当请求的url匹配上了之后调用attemptAuthentication 方法,attemptAuthentication 是AbstractAuthenticationProcessingFilter的核心方法,在这个方法类进行
//登陆验证。这个方法在AbstractAuthenticationProcessingFilter 中是一个抽象方法,调用的是usernamepasswordAuthenticationFiler对其进行的实现attemptAuthentication下面看一看具体的登陆配置
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                // return immediately as subclass has indicated that it hasn‘t completed authentication
                return;
            }
            sessionStrategy.onAuthentication(authResult, request, response);
        } catch(InternalAuthenticationServiceException failed) {
            logger.error("An internal error occurred while trying to authenticate the user.", failed);
            unsuccessfulAuthentication(request, response, failed);
            return;
        }
        catch (AuthenticationException failed) {
            // Authentication failed
//校验失败后调用  验证失败处理器(一般要自己实现)
            unsuccessfulAuthentication(request, response, failed);
            return;
        }
        // Authentication success
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
//校验成功后 调用验证成功处理器(一般要自己实现)
successfulAuthentication(request, response, chain, authResult);
    }
usernamepasswordAuthenticationFiler 中的attemptAuthentication的源码实现:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
                      //默认是 是支持post请求可以设置属性postOnly,这一行的代码postOnly && !request.getMethod().equals("POST") 很经典,大家自己体会(充分利用&&操作符的执行过程)
                      if (postOnly && !request.getMethod().equals("POST")) {
                      throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
                      }
//获取用户名 默认参数为(j_username)
// 源码public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username";
String username = obtainUsername(request);
//获取用户密码默认参数为(j_password)
// public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password";
//这里的注意的是 用户的登陆的密码是没有加密的密码,数据库中或其他服务器中的密码肯定是加密密码 (防止数据泄露)
//所以肯定要将客户端密码 加密之后再进行比对
String password = obtainPassword(request);
                       if (username == null) {
                      username = "";
                       }
                        if (password == null) {
                       password = "";
                          }
username = username.trim();
//这里用一个UsernamePasswordAuthenticationToken 对象存放登陆对象的信息注:UsernamePasswordAuthenticationToken实现了Authenticationj接口:
                                       
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
// Allow subclasses to set the "details" property
//设置一些请求的细节 如当前的sessionId和请求的地址信息 保存到 UsernamePasswordAuthenticationToken.details属性中:其实 保存的是一个WebAuthenticationDetails对象.
return this.getAuthenticationManager().authenticate(authRequest);
// getAuthenticationManager() 获得验证管理器 AuthenticationManager,获得的是这个实现类,默认的实现是ProviderManager类,调用的这个类authenticate()这个方法是整个验证就是在个这个方法中实现.
下面是这个类ProviderManager.authenticate()的源码:
                     public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                       Class<? extends Authentication> toTest = authentication.getClass();
                       AuthenticationException lastException = null;
                       Authentication result = null;
                       boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
//遍历数据提供器 从配置中注入:
                           if (!provider.supports(toTest)) {
                             continue;
                          }
                        if (debug) {
                        logger.debug("Authentication attempt using " + provider.getClass().getName());
                        }
try {
                       // 从数据提供器中 校验数据 待会详细介绍 daoAuthenticationProvider(从数据库中获取验证校验信息,并进行比对)
                         result = provider.authenticate(authentication);
                          if (result != null) {
                           copyDetails(authentication, result);
                          break;
                     }
                    } catch (AccountStatusException e) {
                         prepareException(e, authentication);
                      // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status
                            throw e;
                     } catch (InternalAuthenticationServiceException e) {
                         prepareException(e, authentication);
                           throw e;
                    } catch (AuthenticationException e) {
                       lastException = e;
                      }
               }
                 if (result == null && parent != null) {
                    // Allow the parent to try.
                 try {
                  result = parent.authenticate(authentication);
                  } catch (ProviderNotFoundException e) {
                 // ignore as we will throw below if no other exception occurred prior to calling parent and the parent
                  // may throw ProviderNotFound even though a provider in the child already handled the request
                  } catch (AuthenticationException e) {
                      lastException = e;
                  }
                  }
                   if (result != null) {
                         if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
                            // Authentication is complete. Remove credentials and other secret data from authentication
                           ((CredentialsContainer)result).eraseCredentials();
                  }  
                   eventPublisher.publishAuthenticationSuccess(result);
                    return result;
                }
// Parent was null, or didn‘t authenticate (or throw an exception).
                    if (lastException == null) {
                     lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",
                    new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));
                }
prepareException(lastException, authentication);
                     throw lastException;
                  }
}
DaoAuthenticationProvider.authenticate()走的是其父类AbstractUserDetailsAuthenticationProvider.authenticate()的方法
                 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                      Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                         messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
                                        "Only UsernamePasswordAuthenticationToken is supported"));
                              // Determine username
                     String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
//从缓存中加载数据,如果没有调用retrieveUser从数据库中获得这个数据,
                       if (user == null) {
                      cacheWasUsed = false;
try {
                     //从数据库总获取信息
                      user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                    } catch (UsernameNotFoundException notFound) {
                    logger.debug("User ‘" + username + "‘ not found");
                         if (hideUserNotFoundExceptions) {
                         throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                        } else {
                     throw notFound;
                  }
                 }
                         Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
             }
try {
preAuthenticationChecks.check(user);
//校验一些细节,账号是否有效 账号是否被锁等等
                       additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
                        } catch (AuthenticationException exception) {
                            if (cacheWasUsed) {
                          // There was a problem, so try again after checking
                         // we‘re using latest data (i.e. not from the cache)
                         cacheWasUsed = false;
                        user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                        preAuthenticationChecks.check(user);
                        additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
                           } else {
                            throw exception;
                         }
                        }
postAuthenticationChecks.check(user);
//比对密码
                            if (!cacheWasUsed) {
                           this.userCache.putUserInCache(user);
                              }
Object principalToReturn = user;
                          if (forcePrincipalAsString) {
                             principalToReturn = user.getUsername();
                           }
                               return createSuccessAuthentication(principalToReturn, authentication, user);
                      }
自此用户登陆彻底完成。
下面我们来看一下AbstractSecurityInterceptor权限管理的相关过程和源码解析:
我们首先来看这个的配置要:
               <beans:bean id="securityInterceptor"
                        class="com.newtouch.security.web.access.intercept.FilterSecurityInterceptor"
                                      p:validateConfigAttributes="false" p:authenticationManager-ref="authenticationManager"
                                       p:accessDecisionManager-ref="accessDecisionManager"
                                        p:securityMetadataSource-ref="securityMetadataSource" />
这里要自定义注入三个 authenticationManager 验证管理器,accessDecisionManager 授权管理器,securityMetadataSource 加载资源数据器 (将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问)
首先进入FilterSecurityInterceptor的doFiler方法:
                 public void doFilter(ServletRequest request, ServletResponse response,
                 FilterChain chain) throws IOException, ServletException {
                    FilterInvocation fi = new FilterInvocation(request, response, chain);
                      invoke(fi);
                    //这个方法是重点
                   }
            public void invoke(FilterInvocation fi) throws IOException, ServletException {
                      if ((fi.getRequest() != null)
                            && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                                && observeOncePerRequest) {
                      // filter already applied to this request and user wants us to observe
                     // once-per-request handling, so don‘t re-do security checking
                     fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
                     } else {
                    // first time this request being called, so perform security checking
                     if (fi.getRequest() != null) {
                    fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
                  }
InterceptorStatusToken token = beforeInvocation(fi);
//重点看这个方法,下面贴出源码
                  try {
                  fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
                   } finally {
                     finallyInvocation(token);
                  }
                     afterInvocation(token, null);
                   }
                }
这个方法走的是AbstractSecurityInterceptor的beforeInvocation
               protected InterceptorStatusToken beforeInvocation(Object object) {
                                 Assert.notNull(object, "Object was null");
                                     final boolean debug = logger.isDebugEnabled();
                        if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
                             throw new IllegalArgumentException("Security invocation attempted for object "
                               + object.getClass().getName()
                             + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
                            + getSecureObjectClass());
                }
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
//根据路径获取相应的权限配置 这个方法一般要自己实现
                  if (attributes == null || attributes.isEmpty()) {
                       if (rejectPublicInvocations) {
                         throw new IllegalArgumentException("Secure object invocation " + object +
                     " was denied as public invocations are not allowed via this interceptor. "
                                 + "This indicates a configuration error because the "
                              + "rejectPublicInvocations property is set to ‘true‘");
                  }
                     if (debug) {
                          logger.debug("Public object - authentication not attempted");
               }
publishEvent(new PublicInvocationEvent(object));
                     return null; // no further work post-invocation
                      }
                   if (debug) {
                     logger.debug("Secure object: " + object + "; Attributes: " + attributes);
             }
                       if (SecurityContextHolder.getContext().getAuthentication() == null) {
                                    credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
                             "An Authentication object was not found in the SecurityContext"), object, attributes);
              }
Authentication authenticated = authenticateIfRequired();
//确认用户身份是否验证,如果没有验证在调用验证管理器去验证
                  // Attempt authorization
                try {
                    this.accessDecisionManager.decide(authenticated, object, attributes);
//这个是授权方法 通过其子类去实现
//accessDecisionManager 有三种决策方法
                          
                //  AffirmativeBased 至少一个投票者必须决定授予访问权限
                 //ConsensusBased  多数投票者必须授予访问权限
                // UnanimousBased  所有投票者都必须投票或放弃投票授予访问权限(无投票表决拒绝访问)
               //我们重点看一下AffirmativeBased 这个类的decide的方法源码实现:
                 }
                catch (AccessDeniedException accessDeniedException) {
                  publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));
                 throw accessDeniedException;
                   }
                   if (debug) {
                 logger.debug("Authorization successful");
                }
                      if (publishAuthorizationSuccess) {
                     publishEvent(new AuthorizedEvent(object, attributes, authenticated));
                   }
                      // Attempt to run as a different user
                       Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
                  if (runAs == null) {
                               if (debug) {
                     logger.debug("RunAsManager did not change Authentication object");
                }
                       // no further work post-invocation
                        return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
                } else {
               if (debug) {
               logger.debug("Switching to RunAs Authentication: " + runAs);
          }
              SecurityContext origCtx = SecurityContextHolder.getContext();
              SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
              SecurityContextHolder.getContext().setAuthentication(runAs);
                 // need to revert to token.Authenticated post-invocation
              return new InterceptorStatusToken(origCtx, true, attributes, object);
           }
           }
//AffirmativeBased 的方法decide放法:
                   public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
                                 throws AccessDeniedException {
                       int deny = 0;
                       for (AccessDecisionVoter voter : getDecisionVoters()) {
                        int result = voter.vote(authentication, object, configAttributes);
//根据托票器来投票,返回结果,然后决策授权的结果,这个方法要决策成功与否
                     if (logger.isDebugEnabled()) {
                        logger.debug("Voter: " + voter + ", returned: " + result);
                  }
                   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"));
             }
                 // To get this far, every AccessDecisionVoter abstained
            checkAllowIfAllAbstainDecisions();
           }
到此授权结束。
SpringSecurity 依据用户请求的过程进行源码解析
标签:necessary ping access 应该 eof 防止 tor https 使用
原文地址:http://www.cnblogs.com/caibixiang123/p/7582405.html