Spring Acegi框架鉴权的实现

 一直对这块了解的不是很透彻,网上看到一位牛人写的一篇文章,分析的很到位,拿来借鉴一下:

原文网址:http://www.evget.com/zh-CN/Info/catalog/7179.html

简单分析一下  Spring Acegi  的源代码实现:
      Servlet.Filter  的实现  AuthenticationProcessingFilter  启动  Web  页面的验证过程 - 在  AbstractProcessingFilter  定义了整个验证过程的模板:


代码
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)  
    throws IOException, ServletException {  
    //这里检验是不是符合  ServletRequest/SevletResponse  的要求  
    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;  
    //根据  HttpServletRequest和HttpServletResponse  来进行验证  
    if (requiresAuthentication(httpRequest, httpResponse)) {  
        if (logger.isDebugEnabled()) {  
            logger.debug("Request is to process authentication");  
        }  
        //这里定义  Acegi中的Authentication  对象来持有相关的用户验证信息  
        Authentication authResult;  
 
        try {  
            onPreAuthentication(httpRequest, httpResponse);  
            //这里的具体验证过程委托给子类完成,比如  AuthenticationProcessingFilter  来完成基于  Web  页面的用户验证  
            authResult = attemptAuthentication(httpRequest);  
        } catch (AuthenticationException failed) {  
            // Authentication failed  
            unsuccessfulAuthentication(httpRequest, httpResponse, failed);  
 
            return;  
        }  
 
        // Authentication success  
        if (continueChainBeforeSuccessfulAuthentication) {  
            chain.doFilter(request, response);  
        }  
        //完成验证后的后续工作,比如跳转到相应的页面  
        successfulAuthentication(httpRequest, httpResponse, authResult);  
 
        return;  
    }  
 
    chain.doFilter(request, response);  
}  


在  AuthenticationProcessingFilter  中的具体验证过程是这样的:

代码
public Authentication attemptAuthentication(HttpServletRequest request)  
    throws AuthenticationException {  
    //这里从  HttpServletRequest  中得到用户验证的用户名和密码  
    String username = obtainUsername(request);  
    String password = obtainPassword(request);  
 
    if (username == null) {  
        username = "";  
    }  
 
    if (password == null) {  
        password = "";  
    }  
    //这里根据得到的用户名和密码去构造一个  Authentication  对象提供给  AuthenticationManager  进行验证,里面包含了用户的用户名和密码信息  
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);  
 
    // Place the last username attempted into HttpSession for views  
    request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, username);  
 
    // Allow subclasses to set the "details" property  
    setDetails(request, authRequest);  
    //这里启动  AuthenticationManager  进行验证过程  
    return this.getAuthenticationManager().authenticate(authRequest);  
}  


      在  Acegi  框架中,进行验证管理的主要类是  AuthenticationManager,我们看看它是怎样进行验证管理的 - 验证的调用入口是  authenticate  在  AbstractAuthenticationManager  的实现中:
      //这是进行验证的函数,返回一个  Authentication  对象来记录验证的结果,其中包含了用户的验证信息,权限配置等,同时这个        Authentication  会以后被授权模块使用

代码
//如果验证失败,那么在验证过程中会直接抛出异常  
    public final Authentication authenticate(Authentication authRequest)  
        throws AuthenticationException {  
        try {//这里是实际的验证处理,我们下面使用  ProviderManager  来说明具体的验证过程,传入的参数  authRequest  里面已经包含了从  HttpServletRequest  中得到的用户输入的用户名和密码  
            Authentication authResult = doAuthentication(authRequest);  
            copyDetails(authRequest, authResult);  
 
            return authResult;  
        } catch (AuthenticationException e) {  
            e.setAuthentication(authRequest);  
            throw e;  
        }  
    }  


在  ProviderManager  中进行实际的验证工作,假设这里使用数据库来存取用户信息:

代码
public Authentication doAuthentication(Authentication authentication)  
    throws AuthenticationException {  
    //这里取得配置好的  provider  链的迭代器,在配置的时候可以配置多个  provider,这里我们配置的是  DaoAuthenticationProvider  来说明, 它使用数据库来保存用户的用户名和密码信息。  
    Iterator iter = providers.iterator();  
 
    Class toTest = authentication.getClass();  
 
    AuthenticationException lastException = null;  
 
    while (iter.hasNext()) {  
        AuthenticationProvider provider = (AuthenticationProvider) iter.next();  
 
        if (provider.supports(toTest)) {  
            logger.debug("Authentication attempt using " + provider.getClass().getName());  
            //这个  result  包含了验证中得到的结果信息  
            Authentication result = null;  
 
            try {//这里是  provider  进行验证处理的过程  
                result = provider.authenticate(authentication);  
                sessionController.checkAuthenticationAllowed(result);  
            } catch (AuthenticationException ae) {  
                lastException = ae;  
                result = null;  
            }  
 
            if (result != null) {  
                sessionController.registerSuccessfulAuthentication(result);  
                publishEvent(new AuthenticationSuccessEvent(result));  
 
                return result;  
            }  
        }  
    }  
 
    if (lastException == null) {  
        lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",  
                    new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));  
    }  
 
    // 这里发布事件来通知上下文的监听器  
    String className = exceptionMappings.getProperty(lastException.getClass().getName());  
    AbstractAuthenticationEvent event = null;  
 
    if (className != null) {  
        try {  
            Class clazz = getClass().getClassLoader().loadClass(className);  
            Constructor constructor = clazz.getConstructor(new Class[] {  
                        Authentication.class, AuthenticationException.class 
                    });  
            Object obj = constructor.newInstance(new Object[] {authentication, lastException});  
            Assert.isInstanceOf(AbstractAuthenticationEvent.class, obj, "Must be an AbstractAuthenticationEvent");  
            event = (AbstractAuthenticationEvent) obj;  
        } catch (ClassNotFoundException ignored) {}  
        catch (NoSuchMethodException ignored) {}  
        catch (IllegalAccessException ignored) {}  
        catch (InstantiationException ignored) {}  
        catch (InvocationTargetException ignored) {}  
    }  
 
    if (event != null) {  
        publishEvent(event);  
    } else {  
        if (logger.isDebugEnabled()) {  
            logger.debug("No event was found for the exception " + lastException.getClass().getName());  
        }  
    }  
 
    // Throw the exception  
    throw lastException;  
}  


      我们下面看看在  DaoAuthenticationProvider  是怎样从数据库中取出对应的验证信息进行用户验证的,在它的基类  AbstractUserDetailsAuthenticationProvider  定义了验证的处理模板:

代码
public Authentication authenticate(Authentication authentication)  
    throws AuthenticationException {  
    Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,  
        messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",  
            "Only UsernamePasswordAuthenticationToken is supported"));  
 
    // 这里取得用户输入的用户名  
    String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();  
    // 如果配置了缓存,从缓存中去取以前存入的用户验证信息 - 这里是  UserDetail,是服务器端存在数据库里的用户信息,这样就不用每次都去数据库中取了  
    boolean cacheWasUsed = true;  
    UserDetails user = this.userCache.getUserFromCache(username);  
    //没有取到,设置标志位,下面会把这次取到的服务器端用户信息存入缓存中去  
    if (user == null) {  
        cacheWasUsed = false;  
 
        try {//这里是调用  UserDetailService  去取用户数据库里信息的地方  
            user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);  
        } catch (UsernameNotFoundException notFound) {  
            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");  
    }  
 
    if (!user.isAccountNonLocked()) {  
        throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",  
                "User account is locked"));  
    }  
 
    if (!user.isEnabled()) {  
        throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",  
                "User is disabled"));  
    }  
 
    if (!user.isAccountNonExpired()) {  
        throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",  
                "User account has expired"));  
    }  
 
    // This check must come here, as we don't want to tell users  
    // about account status unless they presented the correct credentials  
    try {//这里是验证过程,在  retrieveUser  中从数据库中得到用户的信息,在  additionalAuthenticationChecks  中进行对比用户输入和服务器端的用户信息  
          //如果验证通过,那么构造一个  Authentication  对象来让以后的授权使用,如果验证不通过,直接抛出异常结束鉴权过程  
        additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);  
    } catch (AuthenticationException exception) {  
        if (cacheWasUsed) {  
            // There was a problem, so try again after checking  
            // we're using latest data (ie not from the cache)  
            cacheWasUsed = false;  
            user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);  
            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);  
        } else {  
            throw exception;  
        }  
    }  
 
    if (!user.isCredentialsNonExpired()) {  
        throw new CredentialsExpiredException(messages.getMessage(  
                "AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));  
    }  
    //根据前面的缓存结果决定是不是要把当前的用户信息存入缓存以供下次验证使用  
    if (!cacheWasUsed) {  
        this.userCache.putUserInCache(user);  
    }  
 
    Object principalToReturn = user;  
 
    if (forcePrincipalAsString) {  
        principalToReturn = user.getUsername();  
    }  
    //最后返回  Authentication  记录了验证结果供以后的授权使用  
    return createSuccessAuthentication(principalToReturn, authentication, user);  
}  
//这是是调用  UserDetailService  去加载服务器端用户信息的地方,从什么地方加载要看设置,这里我们假设由  JdbcDaoImp  来从数据中进行加载  
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)  
    throws AuthenticationException {  
    UserDetails loadedUser;  
    //这里调用  UserDetailService  去从数据库中加载用户验证信息,同时返回从数据库中返回的信息,这些信息放到了  UserDetails  对象中去了  
    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;  
}  


下面我们重点分析一下  JdbcDaoImp  这个类来看看具体是怎样从数据库中得到用户信息的:

代码
public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {  
    //~ Static fields/initializers ==============================================================

//这里是预定义好的对查询语句,对应于默认的数据库表结构,也可以自己定义查询语句对应特定的用户数据库验证表的设计  
    public static final String DEF_USERS_BY_USERNAME_QUERY =  
            "SELECT username,password,enabled FROM users WHERE username = ?";  
    public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =  
            "SELECT username,authority FROM authorities WHERE username = ?";  
 
    //~ Instance fields ===============================================================  
    //这里使用  Spring JDBC  来进行数据库操作  
    protected MappingSqlQuery authoritiesByUsernameMapping;  
    protected MappingSqlQuery usersByUsernameMapping;  
    private String authoritiesByUsernameQuery;  
    private String rolePrefix = "";  
    private String usersByUsernameQuery;  
    private boolean usernameBasedPrimaryKey = true;  
 
    //~ Constructors ===============================================================   
    //在初始化函数中把查询语句设置为预定义的  SQL  语句  
    public JdbcDaoImpl() {  
        usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;  
        authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;  
    }  
 
    //~ Methods ==============================================================   
  
    protected void addCustomAuthorities(String username, List authorities) {}  
 
    public String getAuthoritiesByUsernameQuery() {  
        return authoritiesByUsernameQuery;  
    }  
 
    public String getRolePrefix() {  
        return rolePrefix;  
    }  
 
    public String getUsersByUsernameQuery() {  
        return usersByUsernameQuery;  
    }  
 
    protected void initDao() throws ApplicationContextException {  
        initMappingSqlQueries();  
    }  
 
    /** 
     * Extension point to allow other MappingSqlQuery objects to be substituted in a subclass 
     */ 
    protected void initMappingSqlQueries() {  
        this.usersByUsernameMapping = new UsersByUsernameMapping(getDataSource());  
        this.authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping(getDataSource());  
    }  
 
    public boolean isUsernameBasedPrimaryKey() {  
        return usernameBasedPrimaryKey;  
    }  
    //这里是取得数据库用户信息的具体过程  
    public UserDetails loadUserByUsername(String username)  
        throws UsernameNotFoundException, DataAccessException {  
        //根据用户名在用户表中得到用户信息,包括用户名,密码和用户是否有效的信息  
        List users = usersByUsernameMapping.execute(username);  
 
        if (users.size() == 0) {  
            throw new UsernameNotFoundException("User not found");  
        }  
        //取集合中的第一个作为有效的用户对象  
        UserDetails user = (UserDetails) users.get(0); // contains no GrantedAuthority[]  
        //这里在权限表中去取得用户的权限信息,同样的返回一个权限集合对应于这个用户  
        List dbAuths = authoritiesByUsernameMapping.execute(user.getUsername());  
 
        addCustomAuthorities(user.getUsername(), dbAuths);  
 
        if (dbAuths.size() == 0) {  
            throw new UsernameNotFoundException("User has no GrantedAuthority");  
        }  
        //这里根据得到的权限集合来配置返回的  User  对象供以后使用  
        GrantedAuthority[] arrayAuths = (GrantedAuthority[]) dbAuths.toArray(new GrantedAuthority[dbAuths.size()]);  
 
        String returnUsername = user.getUsername();  
 
        if (!usernameBasedPrimaryKey) {  
            returnUsername = username;  
        }  
 
        return new User(returnUsername, user.getPassword(), user.isEnabled(), true, true, true, arrayAuths);  
    }  
 
    public void setAuthoritiesByUsernameQuery(String queryString) {  
        authoritiesByUsernameQuery = queryString;  
    }  
 
    public void setRolePrefix(String rolePrefix) {  
        this.rolePrefix = rolePrefix;  
    }  
 
    public void setUsernameBasedPrimaryKey(boolean usernameBasedPrimaryKey) {  
        this.usernameBasedPrimaryKey = usernameBasedPrimaryKey;  
    }  
 
    public void setUsersByUsernameQuery(String usersByUsernameQueryString) {  
        this.usersByUsernameQuery = usersByUsernameQueryString;  
    }  
 
    //~ Inner Classes ==============================================================   
  
    /** 
     * 这里是调用  Spring JDBC  的数据库操作,具体可以参考对  JDBC  的分析,这个类的作用是把数据库查询得到的记录集合转换为对象集合 - 一个很简单的  O/R  实现 
     */ 
    protected class AuthoritiesByUsernameMapping extends MappingSqlQuery {  
        protected AuthoritiesByUsernameMapping(DataSource ds) {  
            super(ds, authoritiesByUsernameQuery);  
            declareParameter(new SqlParameter(Types.VARCHAR));  
            compile();  
        }  
 
        protected Object mapRow(ResultSet rs, int rownum)  
            throws SQLException {  
            String roleName = rolePrefix + rs.getString(2);  
            GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName);  
 
            return authority;  
        }  
    }  
 
    /** 
     * Query object to look up a user. 
     */ 
    protected class UsersByUsernameMapping extends MappingSqlQuery {  
        protected UsersByUsernameMapping(DataSource ds) {  
            super(ds, usersByUsernameQuery);  
            declareParameter(new SqlParameter(Types.VARCHAR));  
            compile();  
        }  
 
        protected Object mapRow(ResultSet rs, int rownum)  
            throws SQLException {  
            String username = rs.getString(1);  
            String password = rs.getString(2);  
            boolean enabled = rs.getBoolean(3);  
            UserDetails user = new User(username, password, enabled, true, true, true,  
                    new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});  
 
            return user;  
        }  
    }  
}  


      从数据库中得到用户信息后,就是一个比对用户输入的信息和这个数据库用户信息的比对过程,这个比对过程在      DaoAuthenticationProvider:

代码
//这个  UserDetail  是从数据库中查询到的,这个  authentication  是从用户输入中得到的  
    protected void additionalAuthenticationChecks(UserDetails userDetails,  
        UsernamePasswordAuthenticationToken authentication)  
        throws AuthenticationException {  
        Object salt = null;  
 
        if (this.saltSource != null) {  
            salt = this.saltSource.getSalt(userDetails);  
        }  
        //如果用户没有输入密码,直接抛出异常  
        if (authentication.getCredentials() == null) {  
            throw new BadCredentialsException(messages.getMessage(  
                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),  
                    includeDetailsObject ? userDetails : null);  
        }  
        //这里取得用户输入的密码  
        String presentedPassword = authentication.getCredentials() == null ? "" : authentication.getCredentials().toString();  
        //这里判断用户输入的密码是不是和数据库里的密码相同,这里可以使用  passwordEncoder  来对数据库里的密码加解密  
        // 如果不相同,抛出异常,如果相同则鉴权成功  
        if (!passwordEncoder.isPasswordValid(  
                userDetails.getPassword(), presentedPassword, salt)) {  
            throw new BadCredentialsException(messages.getMessage(  
                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),  
                    includeDetailsObject ? userDetails : null);  
        }  
    }  


      上面分析了整个  Acegi  进行验证的过程,从  AuthenticationProcessingFilter  中拦截Http请求得到用户输入的用户名和密码,这些用户输入的验证信息会被放到  Authentication  对象中持有并传递给  AuthenticatioManager  来对比在服务端的用户信息来完成整个鉴权。这个鉴权完成以后会把有效的用户信息放在一个  Authentication  中供以后的授权模块使用。在具体的鉴权过程中,使用了我们配置好的各种  Provider  以及对应的  UserDetailService  和  Encoder  类来完成相应的获取服务器端用户数据以及与用户输入的验证信息的比对工作。

你可能感兴趣的:(Aceji)