http://feiyan35488.iteye.com/blog/896733
这一阵子看到了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中。
@Override
- protected void initFilterBean() throws ServletException {
- // If no target bean name specified, use filter name.
- <span style="color: #ff0000;">if (this.targetBeanName == null) {
- this.targetBeanName = getFilterName();
- }</span>
- // Fetch Spring root application context and initialize the delegate early,
- // if possible. If the root application context will be started after this
- // filter proxy, we'll have to resort to lazy initialization.
- 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 {
- // Lazily initialize the delegate if necessary.
- 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 =<span style="color: #ff0000;"> wac.getBean(getTargetBeanName(), Filter.class);</span>
- }
- 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<Filter> 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));
}
// Allow subclasses to set the "details" property
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<? extends Authentication> 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资源控制部分
本文详细解析Spring Security的源码实现,重点介绍了如何通过filter进行URL权限控制,以及登录认证处理流程,包括从请求中获取用户名和密码,封装认证请求,调用AuthenticationManager进行认证,以及认证成功后的事件发布。

356

被折叠的 条评论
为什么被折叠?



