簡(jiǎn)單分析一下Spring Acegi的源代碼實(shí)現:
Servlet.Filter的實(shí)現AuthenticationProcessingFilter啟動(dòng)Web頁(yè)面的驗證過(guò)程 - 在A(yíng)bstractProcessingFilter定義了整個(gè)驗證過(guò)程的模板:
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來(lái)進(jìn)行驗證if (requiresAuthentication(httpRequest, httpResponse)) {if (logger.isDebugEnabled()) {logger.debug("Request is to process authentication");}//這里定義Acegi中的Authentication對象來(lái)持有相關(guān)的用戶(hù)驗證信息Authentication authResult;try {onPreAuthentication(httpRequest, httpResponse);//這里的具體驗證過(guò)程委托給子類(lèi)完成,比如AuthenticationProcessingFilter來(lái)完成基于Web頁(yè)面的用戶(hù)驗證authResult = attemptAuthentication(httpRequest);} catch (AuthenticationException failed) {// Authentication failedunsuccessfulAuthentication(httpRequest, httpResponse, failed);return;}// Authentication successif (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//完成驗證后的后續工作,比如跳轉到相應的頁(yè)面successfulAuthentication(httpRequest, httpResponse, authResult);return;}chain.doFilter(request, response);}
在A(yíng)uthenticationProcessingFilter中的具體驗證過(guò)程是這樣的:
public Authentication attemptAuthentication(HttpServletRequest request)throws AuthenticationException {//這里從HttpServletRequest中得到用戶(hù)驗證的用戶(hù)名和密碼String username = obtainUsername(request);String password = obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}//這里根據得到的用戶(hù)名和密碼去構造一個(gè)Authentication對象提供給AuthenticationManager進(jìn)行驗證,里面包含了用戶(hù)的用戶(hù)名和密碼信息UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Place the last username attempted into HttpSession for viewsrequest.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, username);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);//這里啟動(dòng)AuthenticationManager進(jìn)行驗證過(guò)程return this.getAuthenticationManager().authenticate(authRequest);}
在A(yíng)cegi框架中,進(jìn)行驗證管理的主要類(lèi)是AuthenticationManager,我們看看它是怎樣進(jìn)行驗證管理的 - 驗證的調用入口是authenticate在A(yíng)bstractAuthenticationManager的實(shí)現中:
//這是進(jìn)行驗證的函數,返回一個(gè)Authentication對象來(lái)記錄驗證的結果,其中包含了用戶(hù)的驗證信息,權限配置等,同時(shí)這個(gè)Authentication會(huì )以后被授權模塊使用
//如果驗證失敗,那么在驗證過(guò)程中會(huì )直接拋出異常public final Authentication authenticate(Authentication authRequest)throws AuthenticationException {try {//這里是實(shí)際的驗證處理,我們下面使用ProviderManager來(lái)說(shuō)明具體的驗證過(guò)程,傳入的參數authRequest里面已經(jīng)包含了從HttpServletRequest中得到的用戶(hù)輸入的用戶(hù)名和密碼Authentication authResult = doAuthentication(authRequest);copyDetails(authRequest, authResult);return authResult;} catch (AuthenticationException e) {e.setAuthentication(authRequest);throw e;}}
在ProviderManager中進(jìn)行實(shí)際的驗證工作,假設這里使用數據庫來(lái)存取用戶(hù)信息:
public Authentication doAuthentication(Authentication authentication)throws AuthenticationException {//這里取得配置好的provider鏈的迭代器,在配置的時(shí)候可以配置多個(gè)provider,這里我們配置的是DaoAuthenticationProvider來(lái)說(shuō)明, 它使用數據庫來(lái)保存用戶(hù)的用戶(hù)名和密碼信息。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());//這個(gè)result包含了驗證中得到的結果信息Authentication result = null;try {//這里是provider進(jìn)行驗證處理的過(guò)程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}"));}// 這里發(fā)布事件來(lái)通知上下文的監聽(tīng)器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 exceptionthrow lastException;}
我們下面看看在DaoAuthenticationProvider是怎樣從數據庫中取出對應的驗證信息進(jìn)行用戶(hù)驗證的,在它的基類(lèi)AbstractUserDetailsAuthenticationProvider定義了驗證的處理模板:
public Authentication authenticate(Authentication authentication)throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));// 這里取得用戶(hù)輸入的用戶(hù)名String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();// 如果配置了緩存,從緩存中去取以前存入的用戶(hù)驗證信息 - 這里是UserDetail,是服務(wù)器端存在數據庫里的用戶(hù)信息,這樣就不用每次都去數據庫中取了boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username);//沒(méi)有取到,設置標志位,下面會(huì )把這次取到的服務(wù)器端用戶(hù)信息存入緩存中去if (user == null) {cacheWasUsed = false;try {//這里是調用UserDetailService去取用戶(hù)數據庫里信息的地方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 credentialstry {//這里是驗證過(guò)程,在retrieveUser中從數據庫中得到用戶(hù)的信息,在additionalAuthenticationChecks中進(jìn)行對比用戶(hù)輸入和服務(wù)器端的用戶(hù)信息//如果驗證通過(guò),那么構造一個(gè)Authentication對象來(lái)讓以后的授權使用,如果驗證不通過(guò),直接拋出異常結束鑒權過(guò)程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"));}//根據前面的緩存結果決定是不是要把當前的用戶(hù)信息存入緩存以供下次驗證使用if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (forcePrincipalAsString) {principalToReturn = user.getUsername();}//最后返回Authentication記錄了驗證結果供以后的授權使用return createSuccessAuthentication(principalToReturn, authentication, user);}//這是是調用UserDetailService去加載服務(wù)器端用戶(hù)信息的地方,從什么地方加載要看設置,這里我們假設由JdbcDaoImp來(lái)從數據中進(jìn)行加載protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {UserDetails loadedUser;//這里調用UserDetailService去從數據庫中加載用戶(hù)驗證信息,同時(shí)返回從數據庫中返回的信息,這些信息放到了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;}
下面我們重點(diǎn)分析一下JdbcDaoImp這個(gè)類(lèi)來(lái)看看具體是怎樣從數據庫中得到用戶(hù)信息的:
public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {//~ Static fields/initializers =====================================================================================//這里是預定義好的對查詢(xún)語(yǔ)句,對應于默認的數據庫表結構,也可以自己定義查詢(xún)語(yǔ)句對應特定的用戶(hù)數據庫驗證表的設計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來(lái)進(jìn)行數據庫操作protected MappingSqlQuery authoritiesByUsernameMapping;protected MappingSqlQuery usersByUsernameMapping;private String authoritiesByUsernameQuery;private String rolePrefix = "";private String usersByUsernameQuery;private boolean usernameBasedPrimaryKey = true;//~ Constructors ===================================================================================================//在初始化函數中把查詢(xún)語(yǔ)句設置為預定義的SQL語(yǔ)句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;}//這里是取得數據庫用戶(hù)信息的具體過(guò)程public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException, DataAccessException {//根據用戶(hù)名在用戶(hù)表中得到用戶(hù)信息,包括用戶(hù)名,密碼和用戶(hù)是否有效的信息List users = usersByUsernameMapping.execute(username);if (users.size() == 0) {throw new UsernameNotFoundException("User not found");}//取集合中的第一個(gè)作為有效的用戶(hù)對象UserDetails user = (UserDetails) users.get(0); // contains no GrantedAuthority[]//這里在權限表中去取得用戶(hù)的權限信息,同樣的返回一個(gè)權限集合對應于這個(gè)用戶(hù)List dbAuths = authoritiesByUsernameMapping.execute(user.getUsername());addCustomAuthorities(user.getUsername(), dbAuths);if (dbAuths.size() == 0) {throw new UsernameNotFoundException("User has no GrantedAuthority");}//這里根據得到的權限集合來(lái)配置返回的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的分析,這個(gè)類(lèi)的作用是把數據庫查詢(xún)得到的記錄集合轉換為對象集合 - 一個(gè)很簡(jiǎn)單的O/R實(shí)現*/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;}}}
從數據庫中得到用戶(hù)信息后,就是一個(gè)比對用戶(hù)輸入的信息和這個(gè)數據庫用戶(hù)信息的比對過(guò)程,這個(gè)比對過(guò)程在DaoAuthenticationProvider:
//這個(gè)UserDetail是從數據庫中查詢(xún)到的,這個(gè)authentication是從用戶(hù)輸入中得到的protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {Object salt = null;if (this.saltSource != null) {salt = this.saltSource.getSalt(userDetails);}//如果用戶(hù)沒(méi)有輸入密碼,直接拋出異常if (authentication.getCredentials() == null) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),includeDetailsObject ? userDetails : null);}//這里取得用戶(hù)輸入的密碼String presentedPassword = authentication.getCredentials() == null ? "" : authentication.getCredentials().toString();//這里判斷用戶(hù)輸入的密碼是不是和數據庫里的密碼相同,這里可以使用passwordEncoder來(lái)對數據庫里的密碼加解密// 如果不相同,拋出異常,如果相同則鑒權成功if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),includeDetailsObject ? userDetails : null);}}
上面分析了整個(gè)Acegi進(jìn)行驗證的過(guò)程,從AuthenticationProcessingFilter中攔截Http請求得到用戶(hù)輸入的用戶(hù)名和密碼,這些用戶(hù)輸入的驗證信息會(huì )被放到Authentication對象中持有并傳遞給AuthenticatioManager來(lái)對比在服務(wù)端的用戶(hù)信息來(lái)完成整個(gè)鑒權。這個(gè)鑒權完成以后會(huì )把有效的用戶(hù)信息放在一個(gè)Authentication中供以后的授權模塊使用。在具體的鑒權過(guò)程中,使用了我們配置好的各種Provider以及對應的UserDetailService和Encoder類(lèi)來(lái)完成相應的獲取服務(wù)器端用戶(hù)數據以及與用戶(hù)輸入的驗證信息的比對工作。
聯(lián)系客服