精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-01-25
最后修改:2011-01-26
这一阵子看到了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. if (this.targetBeanName == null) { this.targetBeanName = getFilterName(); } // 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 = 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<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资源控制部分 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2011-07-15
我想请教一下,怎么就知道springSecurityFilterChain对应的类就是org.springframeword.security.web.FilterChainProxy。这里面的实现机制是什么样的。在深入security源码的时候这部分不理解,还望楼主不吝赐教。
|
|
返回顶楼 | |
发表时间:2011-07-15
List<Filter> filters = getFilters(fi.getRequestUrl());
这个我感觉是这样的: 比如说有个security和CAS进行集成。那么用户访问受限制路径的时候,security会询问CAS认证器用户是否经过认证,如果是认证过的,CAS会返回一个CAS特定的路径,这个时候,通过这个方法就能找到相应的过滤器(和CAS相关的过滤器),这个过滤器负责想CAS询问ST的有效性。如果有效,则通过认证。 |
|
返回顶楼 | |
发表时间:2011-07-15
关于org.springframeword.security.web.FilterChainProxy,这个可以security的初始化部分可以找到,security将该类注册成了一个spring bean,所以从开始的代码看到 .DelegatingFilterProxy该类是从容器中取得 filterchainproxy的实现类的。那部分初始化的代码你有兴趣可以去看看,在security解析xml的时候实现的
|
|
返回顶楼 | |
发表时间:2011-07-15
sjbrising 写道 List<Filter> filters = getFilters(fi.getRequestUrl());
这个我感觉是这样的: 比如说有个security和CAS进行集成。那么用户访问受限制路径的时候,security会询问CAS认证器用户是否经过认证,如果是认证过的,CAS会返回一个CAS特定的路径,这个时候,通过这个方法就能找到相应的过滤器(和CAS相关的过滤器),这个过滤器负责想CAS询问ST的有效性。如果有效,则通过认证。 这个早就理解了,我现在用的security就是我根据 spring security的思路,自己提炼了一个简单的小型框架,扩展挺方便的,十分小巧,只有不到100行代码。 |
|
返回顶楼 | |
浏览 5323 次