Saiku登录源码追踪呀~
>>首先我们需要debug跟踪saiku登录执行的源码信息
saiku源码的debug方式上一篇博客已有说明,这里简单介绍一下
在saiku启动脚本中添加如下命令: (windows下: start-saiku.bat)
set CATALINA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n (后面可能还会有一些JVM参数信息)
使用Eclipse打开saiku源码 -- > Eclipse工具栏中的 Run --> Debug configurations --> Remote Java Application --> 选中对应的源码项目(saiku-web),远程访问saiku的ip,以及脚本中指定监听的端口 (address) 8787 --> Debug
在浏览器中根据saiku地址信息访问saiku,输入用户名以及密码信息登录,Eclipse则会进入对应的debug阶段。
>>源码追踪
1.首先会调用 saiku-web项目 org.saiku.web.rest.resources 包下的 SessionResource中的登录方法
sessionService.login(res,username,password)
/** * Saiku Session Endpoints */ @Component @Path("/saiku/session") public class SessionResource { /*此处省略其他代码信息*/ private ISessionService sessionService; /** * Login to Saiku * @summary Login * @param req Servlet request * @param username Username * @param password Password * @return A 200 response */ @POST @Consumes("application/x-www-form-urlencoded") public Response login( @Context HttpServletRequest req, @FormParam("username") String username, @FormParam("password") String password) { try { sessionService.login(req, username, password); return Response.ok().build(); } catch (Exception e) { log.debug("Error logging in:" + username, e); return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getLocalizedMessage()).build(); } } }
2.进入org.saiku.web.service 包下的 SessionService类中的 sessionService.login(res,username,password)方法
进行认证的方法主要是: authenticate(req, username, password);
package org.saiku.web.service; /*此处省略了导入相关包信息*/
public class SessionService implements ISessionService { /**此处省略其他代码*/ /* (non-Javadoc) * @see org.saiku.web.service.ISessionService#login(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String) */ public Map<String, Object> login(HttpServletRequest req, String username, String password ) throws LicenseException { Object sl = null; String notice = null; HttpSession session = ((HttpServletRequest)req).getSession(true); session.getId(); sessionRepo.setSession(session); try { sl = l.getLicense(); } catch (Exception e) { log.debug("Could not process license", e); throw new LicenseException("Error fetching license. Get a free license from http://licensing.meteorite.bi. You can upload it at /upload.html"); } if (sl != null) { try { l.validateLicense(); } catch (RepositoryException | IOException | ClassNotFoundException e) { log.debug("Repository Exception, couldn't get license", e); throw new LicenseException("Error fetching license. Please check your logs."); } try { if (l.getLicense() instanceof SaikuLicense2) { if (authenticationManager != null) { authenticate(req, username, password); } if (SecurityContextHolder.getContext() != null && SecurityContextHolder.getContext().getAuthentication() != null) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (authorisationPredicate.isAuthorised(auth)) { Object p = auth.getPrincipal(); createSession(auth, username, password); return sessionHolder.get(p); } else { log.info(username + " failed authorisation. Rejecting login"); throw new RuntimeException("Authorisation failed for: " + username); } } return new HashMap<>(); } } catch (IOException | ClassNotFoundException | RepositoryException e) { log.debug("Repository Exception, couldn't get license", e); throw new LicenseException("Error fetching license. Please check your logs."); } } return null; } }
3.继续进入SessionService的 authenticate (req,username,password)方法进行认证
主要认证: Authentication authentication = this.authenticationManager.authenticate(token);
/* (non-Javadoc) * @see org.saiku.web.service.ISessionService#authenticate(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String) */ public void authenticate(HttpServletRequest req, String username, String password) { try { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); token.setDetails(new WebAuthenticationDetails(req)); Authentication authentication = this.authenticationManager.authenticate(token); log.debug("Logging in with [{}]", authentication.getPrincipal()); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (BadCredentialsException bd) { throw new RuntimeException("Authentication failed for: " + username, bd); } }
4.转入spring-security认证框架中的 org.springframework.security.authentication包下的ProviderManager类中的 this.authenticationManager.authenticate(token)方法
主要认证: result = provider.authenticate(authentication);
/** * Attempts to authenticate the passed {@link Authentication} object. * <p> * The list of {@link AuthenticationProvider}s will be successively tried until an * <code>AuthenticationProvider</code> indicates it is capable of authenticating the * type of <code>Authentication</code> object passed. Authentication will then be * attempted with that <code>AuthenticationProvider</code>. * <p> * If more than one <code>AuthenticationProvider</code> supports the passed * <code>Authentication</code> object, only the first * <code>AuthenticationProvider</code> tried will determine the result. No subsequent * <code>AuthenticationProvider</code>s will be tried. * * @param authentication the authentication request object. * * @return a fully authenticated object including credentials. * * @throws AuthenticationException if authentication fails. */ public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } } catch (AccountStatusException e) { prepareException(e, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw e; } catch (InternalAuthenticationServiceException e) { prepareException(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}")); } prepareException(lastException, authentication); throw lastException; }
5.转入 org.springframework.security.authentication.dao包下的 AbstractUserDetailsAuthenticationProvider类中的 result = provider.authenticate(authentication);
关于密码的校验主要是:additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // Determine username String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { logger.debug("User '" + username + "' not found"); 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"); } try { preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) { // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } else { throw exception; } }
6.org.springframework.security.authentication.dao包下的 DaoAuthenticationProvider 类中 additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication)方法如下
校验密码的主要方法: passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword,salt)
@SuppressWarnings("deprecation") 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) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } }
好了,关于saiku登录源码就追踪到这里啦~ ,到这里我们就知道怎么更改saiku对密码校验的逻辑信息啦~