这一阵子看到了security,很感兴趣。于是研究一下,我在javaeye上查了好多相关的文档,收益匪浅,从入门级的配置问题,到源码级的解读都非常不错,但是还要自己在亲自走一遍流程才踏实。


我看的security 3.0的源码,原因是 security 2.0 的源码没办法通过maven获取到 。


首先 security的控制内容有: url,method,session三种,我项目中用到的只有 url。下面就按url的流程来走。

思路:  使用filter,过滤所有的url 如 /* 这样,并且这个filter应在最前面,道理就不到说了吧。

1》 security使用的 filter是 org.springframework.web.filter.DelegatingFilterProxy类,在spring-web jar中。

	
	protected void initFilterBean() throws ServletException { 
		if (this.targetBeanName == null) {
			this.targetBeanName = getFilterName();
		}
		synchronized (this.delegateMonitor) {
			WebApplicationContext wac = findWebApplicationContext();
			if (wac != null) {
				this.delegate = initDelegate(wac);
			}
		}
	}
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain filterChain) throws ServletException, IOException { 
		Filter 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); // 该方法中的 代码
				Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
			}
			delegateToUse = this.delegate;
		} // Let the delegate perform the actual doFilter operation.
		invokeDelegate(delegateToUse, request, response, filterChain);
	}

这里需要注意一点 filter-name 必须为 springSecurityFilterChain,从DelegatingFilterProxy这个名字中可以猜到这只是个代理类(确实如此),当这个类执行时会去取得真正的filter类,这个类在spring容器中默认生成id为 springSecurityFilterChain,在3.0中 该filter 添加了一个 targetName 字段,可以从上面红色代码部分看到它的作用,因此可以通过指定targetName字段,来防止和项目中的其他filter冲突。


接下来 该真正的 filterChain出场了,这个类是security事务相关的,应该在security包中。

于是 在 spring-security-web jar中发现了这个类:org.springframeword.security.web.FilterChainProxy ,进去看看可以看出这个就是我要找的类。(关于这一点我是从命名上看出来的,bean的id要和类名保持一致)。


下班了 ,回去再继续添加。

==========================华丽丽的分界线====================================


吃完饭 ,继续。

FilterChainProxy 代码:

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		List filters = getFilters(fi.getRequestUrl());
		// 根据url 得到需要经过的filters
		// 这里不是很明白,有知道的同学可以留言。
		if (filters == null || filters.size() == 0) { // 如果没有合适的 ,就继续进行filter
			if (logger.isDebugEnabled()) {
				logger.debug(fi.getRequestUrl() + filters == null ? " has no matching filters": " has an empty filter list");
			}
			chain.doFilter(request, response);
			return;
		}
		// 如果有filter 就进行虚拟的filter链。这里并没有跳出容器的 filter链,
		// 当这个虚拟的filter链完成之后,就继续进行 容器的filter
		VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi,filters);
		virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
	}

接下来就该进行 filterChain了,在security中有好多的filter:

CHANNEL_FILTER ChannelProcessingFilter

CONCURRENT_SESSION_FILTER ConcurrentSessionFilter

SESSION_CONTEXT_INTEGRATION_FILTER HttpSessionContextIntegrationFilter

LOGOUT_FILTER LogoutFilter

X509_FILTER X509PreAuthenticatedProcessigFilter

PRE_AUTH_FILTER Subclass of AstractPreAuthenticatedProcessingFilter

CAS_PROCESSING_FILTER CasProcessingFilter

AUTHENTICATION_PROCESSING_FILTER AuthenticationProcessingFilter

BASIC_PROCESSING_FILTER BasicProcessingFilter

SERVLET_API_SUPPORT_FILTER classname

REMEMBER_ME_FILTER RememberMeProcessingFilter

ANONYMOUS_FILTER AnonymousProcessingFilter

EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter

NTLM_FILTER NtlmProcessingFilter

FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor

SWITCH_USER_FILTER SwitchUserProcessingFilter 。


下面我只分析了 AuthenticationProcessingFilter,这是登录认证处理filter

	public UsernamePasswordAuthenticationFilter() {
		super("/j_spring_security_check");// 这就是 登录验证的 url。
	}
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: "
							+ request.getMethod());
		} // 只允许以post方法 进行认证,能防止一些简单的破解
		String username = obtainUsername(request);
		String password = obtainPassword(request);
	if (username == null) {
		username = "";
	}
	if (password == null) {
		password = "";
	}
	username = username.trim();
	// 只是将 username和password封装进去
	UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

	// Place the last username attempted into HttpSession for views
	HttpSession session = request.getSession(false);
	if (session != null || getAllowSessionCreation()) {
		request.getSession().setAttribute(
				SPRING_SECURITY_LAST_USERNAME_KEY,
				TextEscapeUtils.escapeEntities(username));
	}
	setDetails(request, authRequest);
	// 取得AuthenticationManager 进行认证
	return this.getAuthenticationManager().authenticate(authRequest);
}

从request中取得 username,password,封装进 UsernamePasswordAuthenticationToken 中,

然后将username中写到 session中,这里对username去掉了首尾的空格

然后调用 AuthenticationManager的 authenticate方法进行具体的认证操作。

	public Authentication doAuthentication(Authentication authentication)
			throws AuthenticationException {
		Class toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			logger.debug("Authentication attempt using "+ provider.getClass().getName());
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			} catch (AccountStatusException e) {
				// SEC-546: Avoid polling additional providers if auth failure
				// is due to invalid account status
				eventPublisher.publishAuthenticationFailure(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}"));
		}
		eventPublisher.publishAuthenticationFailure(lastException,authentication);
		throw lastException;
	}

这里对 用户进行认证,成功就发布成功事件,并返回。 失败就发布失败事件,并返回exception

这里具体的认证过程还是不大熟悉,等再详细的看明白了 再细说。

看明白了,authenticationManager可以有多个 provider如 默认的daoAuthenticationProvider 和  JaasAuth ,RememmberMeAuth 等

下面说 daoAuthenticationProvider 中可以有  UserDetailsService ,

        
	protected final UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		UserDetails loadedUser;
		try {
			loadedUser = this.getUserDetailsService().loadUserByUsername(username);
		} catch (DataAccessException repositoryProblem) {
			throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
		}
		if (loadedUser == null) {
			throw new AuthenticationServiceException(
					"UserDetailsService returned null, which is an interface contract violation");
		}
		return loadedUser;
	}

又通过 userDetailsService.loadUserByUsername()  ,当不存在时 ,返回null

来得到 UserDetails 这样就能把 整个认证过程理顺了


总结一下,  security 的认证过程能理顺了,对其衔接的过渡代码 还有些拿不准,还有 取得filters的 规则还不是很清楚,还要继续看下去。

已经能理顺了, 在BeanId 类中发现了那些默认的字符串,这样 在spring 解析xml时,遇到 security的标签后,会将这个节点交给 security下的类来执行SecurityNamespaceHandler。

这样就能保证security初始化的正常。包括生成默认的 FilterChainProxy 和添加一些依赖。生成 AuthenticationManager及其依赖的 ProviderManager 等。

下次,继续看 url资源控制部分