Acegi学习心得《二》

在配置Acegi Filter Chain Proxy是设定了targetClass,并制定了其代表的类,并在其他配置文件中声明了其具体的实现。其中实现是通过指定filterInvocationDefinitionSource的。如下:
	<!--****** Fileter Chain ******-->
	<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
		<property name="filterInvocationDefinitionSource">
			<value>
				CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
				PATTERN_TYPE_APACHE_ANT
				/**=authenticationProcessingFilter,logoutFilter,rememberMeProcessingFilter,exceptionTranslationFilter
			</value>
		</property>
	</bean>

这里声明了过滤链时需要考虑前后,因为在实现是是从第一个开始的。
也就是说authenticationProcessingFilter是最先触发的,其触发的条件是其URI中是以j_acegi_security_check结尾的。而对于LogoutFilter因为具体实现时它会考虑请求的URI,所以并不是所有的请求都会触发这个过滤器。而rememberMeProcessingFilter并没有任何条件,它的实现如下:
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;

        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            //这里是通过rememberMeServices的autoLogin来进行登录作业的,具体的实现方式可查看下一段代码
            Authentication rememberMeAuth = rememberMeServices.autoLogin(httpRequest, httpResponse);

            if (rememberMeAuth != null) {
                // Attempt authenticaton via AuthenticationManager
                try {
                    authenticationManager.authenticate(rememberMeAuth);

                    // Store to SecurityContextHolder
                    SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

                    if (logger.isDebugEnabled()) {
                        logger.debug("SecurityContextHolder populated with remember-me token: '"
                            + SecurityContextHolder.getContext().getAuthentication() + "'");
                    }

                    // Fire event
                    if (this.eventPublisher != null) {
                        eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                                SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
                    }
                } catch (AuthenticationException authenticationException) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                            "SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '"
                            + rememberMeAuth + "'; invalidating remember-me token", authenticationException);
                    }

                    rememberMeServices.loginFail(httpRequest, httpResponse);
                }
            }

            chain.doFilter(request, response);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
                    + SecurityContextHolder.getContext().getAuthentication() + "'");
            }

            chain.doFilter(request, response);
        }
    }

承上说明:自动登录时通过其提供的services进行登录,在登录后返回一个Authentication对象,并通过authenticationManager的校验(认证),判断是否可以通过,如果允许(也就是没有抛出异常)则保存至ServletContextHolder中,并发布成功的事件。否则继续下一个过滤器。
而对于自动登录中的RememberMeServices的实现代码如下:
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
        Cookie[] cookies = request.getCookies();

        if ((cookies == null) || (cookies.length == 0)) {
            return null;
        }

        for (int i = 0; i < cookies.length; i++) {
            if (ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY.equals(cookies[i].getName())) {
                String cookieValue = cookies[i].getValue();

                if (Base64.isArrayByteBase64(cookieValue.getBytes())) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Remember-me cookie detected");
                    }

                    // Decode token from Base64
                    // format of token is:  
                    //     username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
                    String cookieAsPlainText = new String(Base64.decodeBase64(cookieValue.getBytes()));
                    String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, ":");

                    if (cookieTokens.length == 3) {
                        long tokenExpiryTime;

                        try {
                            tokenExpiryTime = new Long(cookieTokens[1]).longValue();
                        } catch (NumberFormatException nfe) {
                            cancelCookie(request, response,
                                "Cookie token[1] did not contain a valid number (contained '" + cookieTokens[1] + "')");

                            return null;
                        }

                        // Check it has not expired
                        if (tokenExpiryTime < System.currentTimeMillis()) {
                            cancelCookie(request, response,
                                "Cookie token[1] has expired (expired on '" + new Date(tokenExpiryTime)
                                + "'; current time is '" + new Date() + "')");

                            return null;
                        }

                        // Check the user exists
                        // Defer lookup until after expiry time checked, to possibly avoid expensive lookup
                        UserDetails userDetails;

                        try {
                            userDetails = this.userDetailsService.loadUserByUsername(cookieTokens[0]);
                        } catch (UsernameNotFoundException notFound) {
                            cancelCookie(request, response,
                                "Cookie token[0] contained username '" + cookieTokens[0] + "' but was not found");

                            return null;
                        }

                        // Immediately reject if the user is not allowed to login
                        if (!userDetails.isAccountNonExpired() || !userDetails.isCredentialsNonExpired()
                            || !userDetails.isEnabled()) {
                            cancelCookie(request, response,
                                "Cookie token[0] contained username '" + cookieTokens[0]
                                + "' but account has expired, credentials have expired, or user is disabled");

                            return null;
                        }

                        // Check signature of token matches remaining details
                        // Must do this after user lookup, as we need the DAO-derived password
                        // If efficiency was a major issue, just add in a UserCache implementation,
                        // but recall this method is usually only called one per HttpSession
                        // (as if the token is valid, it will cause SecurityContextHolder population, whilst
                        // if invalid, will cause the cookie to be cancelled)
                        String expectedTokenSignature = DigestUtils.md5Hex(userDetails.getUsername() + ":"
                                + tokenExpiryTime + ":" + userDetails.getPassword() + ":" + this.key);

                        if (!expectedTokenSignature.equals(cookieTokens[2])) {
                            cancelCookie(request, response,
                                "Cookie token[2] contained signature '" + cookieTokens[2] + "' but expected '"
                                + expectedTokenSignature + "'");

                            return null;
                        }

                        // By this stage we have a valid token
                        if (logger.isDebugEnabled()) {
                            logger.debug("Remember-me cookie accepted");
                        }

                        RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(this.key, userDetails,
                                userDetails.getAuthorities());
                        auth.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));

                        return auth;
                    } else {
                        cancelCookie(request, response,
                            "Cookie token did not contain 3 tokens; decoded value was '" + cookieAsPlainText + "'");

                        return null;
                    }
                } else {
                    cancelCookie(request, response,
                        "Cookie token was not Base64 encoded; value was '" + cookieValue + "'");

                    return null;
                }
            }
        }

        return null;
    }

承上说明:我们采用的是TokenBasedRememberMeServices的实现方式,其主要过程是读取Cookies文件,判断其中是否有Acegi标识的信息,并检验是否过期;并根据用户名和配置的userDetailsService去获取用户的具体信息(判断是否过期、密码是否有效、是否可用等),同时加密后与Cookies中的信息进行对比,判断是否一致。因为每一个session都需要保证如果用户有效,则装配信息,无效的话则需要取消(就是将Cookies设置为null,并返回响应)。做完校验后,把这个userDetail信息绑定到Key对应的键值中,供前面的AuthenticationManager做认证,并把userDetail保存至Request中。
那么就有一个疑问是Cookies的信息是在什么时候保存起来的呢?
这个其实可以先看一下配置信息
	<!-- 表单认证处理Filter -->
	<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
		<property name="authenticationManager" ref="authenticationManager"></property>
		<property name="authenticationFailureUrl" value="/acegi_login.jsp?login_error=1"></property>
		<property name="defaultTargetUrl" value="/userinfo.jsp"></property>
		<property name="filterProcessesUrl" value="/j_acegi_security_check"></property>
		<property name="rememberMeServices" ref="rememberMeServices"></property>
	</bean>

以前看别人的示例中并没有加上rememberMeServices,这样是没有办法自动登录的,因为系统提供的默认实现是NullRememberMeServices,并没有保存Cookies信息。
而这边配置的是TokenBasedRememberMeServices实现,其根据提交的参数是否有“_acegi_security_remember_me”选中,若有则保存Cookies。
并且如果注销后则系统将Cookies删除。所以要保证自动登录的话,是不可以点击注销的。
因为在LogoutFilter中的构造器中的Holder中加入了rememberMeServices实现,所以在注销时会调用其logout方法(就是把Cookies设为空并过时的操作),以及其他诸如org.acegisecurity.ui.logout.SecurityContextLogoutHandler的实现,用于清空Session。

你可能感兴趣的:(DAO,UI,ant,Security,Acegi)