如何使用 Spring MVC 和 Spring Security 更新有关授权用户的信息并将其保存在数据库中
Posted
技术标签:
【中文标题】如何使用 Spring MVC 和 Spring Security 更新有关授权用户的信息并将其保存在数据库中【英文标题】:how to update information about an authorized user and save it in a database using Spring MVC and Spring Security 【发布时间】:2016-09-21 18:39:48 【问题描述】:我有 profile.jsp。它关于授权用户的当前信息。
<div class='contacts'>Contacts</div>
<table>
<tr><th>E-mail</th><td><div>$email</div></td></tr>
<tr><th>Phone number</th><td><div>$phone</div></td></tr>
<tr><th>Name</th><td><div>$name</div></td></tr>
<tr><th>Surname</th><td><div>$surname</div></td></tr>
<tr><th>Middle name</th><td><div>$middleName</div></td></tr>
</table>
这是配置文件控制器
@RequestMapping(value = "/user/profile")
public ModelAndView userProfile(Principal principal)
ModelAndView modelAndView = new ModelAndView();
UserEntity userEntity = userService.getByEmail(principal.getName());
modelAndView.addObject("userName", userEntity.getName());
modelAndView.addObject("email", userEntity.getEmail());
modelAndView.addObject("phone",userEntity.getPhone());
modelAndView.addObject("name", userEntity.getName());
modelAndView.addObject("surname", userEntity.getSurname());
modelAndView.addObject("middleName", userEntity.getMiddleName());
modelAndView.setViewName("profile");
return modelAndView;
这是 update_user.jsp
<c:url value="/user/profile/update" var="updateProfile"/>
<form enctype="multipart/form-data" class="form-horizontal" id="loginModalForm" action="$updateProfile" method="post">
<input type="hidden" value="$_csrf.token" name="$_csrf.parameterName" />
<div class="form-group"><label class="col-sm-3 control-label" for="User_name">Name</label>
<div class="col-sm-9"><input class="form-control" placeholder="Name" name="userName" id="User_name" type="text" maxlength="50" value="$userName" />
<div class="help-block error" id="User_name_em_" style="display:none"></div>
</div>
</div>
<div class="form-group"><label class="col-sm-3 control-label" for="User_surname">Surname</label>
<div class="col-sm-9"><input class="form-control" placeholder="Surname" name="userSurname" id="User_surname" type="text" maxlength="50" value="$userSurname" />
<div class="help-block error" id="User_surname_em_" style="display:none"></div>
</div>
</div>
<div class="form-group"><label class="col-sm-3 control-label" for="User_middle_name">Middle name</label>
<div class="col-sm-9"><input class="form-control" placeholder="Middle name" name="userMiddleName" id="User_middle_name" type="text" maxlength="50" value="$userMiddleName" />
<div class="help-block error" id="User_middle_name_em_" style="display:none"></div>
</div>
</div>
<div class="form-group"><label class="col-sm-3 control-label required" for="User_phone">Phone number <span class="required">*</span></label>
<div class="col-sm-9"><input class="form-control" placeholder="Phone number" name="userPhone" id="User_phone" type="text" maxlength="14" value="$userPhone" />
<div class="help-block error" id="User_phone_em_" style="display:none"></div>
</div>
</div>
<button class="btn btn-primary" id="yw1" type="submit" name="yt0">Update</button>
</form>
及其控制器
@RequestMapping(value = "/user/profile/update")
public ModelAndView userProfileUpdate(HttpServletRequest request, Principal principal)
ModelAndView modelAndView = new ModelAndView();
UserEntity userEntity = userService.getByEmail(principal.getName());
modelAndView.setViewName("update_user");
modelAndView.addObject("userName", userEntity.getName());
modelAndView.addObject("userSurname", userEntity.getSurname());
modelAndView.addObject("userMiddleName", userEntity.getMiddleName());
modelAndView.addObject("userPhone", userEntity.getPhone());
String userName = request.getParameter("userName");
String userSurname = request.getParameter("userSurname");
String userMiddleName = request.getParameter("userMiddleName");
String userPhone = request.getParameter("userPhone");
userEntity.setName(userName);
userEntity.setSurname(userSurname);
userEntity.setMiddleName(userMiddleName);
userEntity.setPhone(userPhone);
userService.update(userEntity);
return new ModelAndView(new RedirectView("/user/profile"));
我的 userProfileUpdate 有什么问题?当我尝试访问 update_user.jsp 时出现错误:
type Exception report
message Request processing failed; nested exception is org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
description The server encountered an internal error that prevented it from fulfilling this request.
exception
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:980)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:859)
javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:844)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:316)
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:96)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
root cause
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:526)
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
com.sun.proxy.$Proxy75.save(Unknown Source)
edinar.service.impl.UserServiceImpl.update(UserServiceImpl.java:43)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:483)
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
com.sun.proxy.$Proxy77.update(Unknown Source)
edinar.controllers.UserController.userProfileUpdate(UserController.java:208)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:483)
root cause
javax.persistence.RollbackException: Error while committing the transaction
org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:86)
org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
com.sun.proxy.$Proxy75.save(Unknown Source)
edinar.service.impl.UserServiceImpl.update(UserServiceImpl.java:43)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
root cause
javax.validation.ConstraintViolationException: Validation failed for classes [edinar.entity.UserEntity] during update time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImplinterpolatedMessage='may not be empty', propertyPath=phone, rootBeanClass=class edinar.entity.UserEntity, messageTemplate='org.hibernate.validator.constraints.NotEmpty.message'
ConstraintViolationImplinterpolatedMessage='may not be empty', propertyPath=password, rootBeanClass=class edinar.entity.UserEntity, messageTemplate='org.hibernate.validator.constraints.NotEmpty.message'
]
org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:138)
org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreUpdate(BeanValidationEventListener.java:86)
org.hibernate.action.internal.EntityUpdateAction.preUpdate(EntityUpdateAction.java:244)
org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:118)
org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:560)
org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:434)
org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295)
org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:468)
org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3135)
org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2352)
org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61)
org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
com.sun.proxy.$Proxy75.save(Unknown Source)
edinar.service.impl.UserServiceImpl.update(UserServiceImpl.java:43)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:483)
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
com.sun.proxy.$Proxy77.update(Unknown Source)
我的 SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
UserService userService;
@Autowired
UloginService uloginService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
auth.authenticationProvider(new UloginAuthentifiactionProvider());
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
@Override
protected void configure(HttpSecurity http) throws Exception
UloginAuthenticationFilter uloginFilter =
new UloginAuthenticationFilter("/ulogin","http://localhost:8080/", uloginService, userDetailsService, userService);
uloginFilter.setAuthenticationManager(authenticationManager());
HttpSecurity httpSecurity = http.
addFilterBefore(uloginFilter, AnonymousAuthenticationFilter.class);
httpSecurity.authorizeRequests().antMatchers("/").permitAll()
.anyRequest().authenticated() ;
httpSecurity.formLogin().loginPage("/");
http
.addFilterBefore(customUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/resources/**", "/**").permitAll()
.anyRequest().permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403");
http.formLogin()
.loginProcessingUrl("/user/login")
.usernameParameter("loginemail")
.passwordParameter("loginpassword")
.successHandler(loginSuccessHandler())
.failureHandler(loginFailureHandler())
.permitAll();
http.logout()
.permitAll()
.logoutUrl("/user/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.csrf().ignoringAntMatchers("/ulogin","/user/verify");
【问题讨论】:
当您尝试保存时,您的用户实体对象的密码和电话字段是否为空白/空? 我知道。我想打开我的 update_user.jsp 但我做不到,我得到了错误。我无法填写表格,因为页面未打开。 您可以尝试将 userProfileUpdate 方法分解为两个方法吗?一个用于获取,另一个用于发布?所以第一种方法基本上是显示 update_user.jsp 并返回带有 update_user 视图的 ModelAndView 对象。下一个方法将处理帖子,这就是您调用 userServiceUpdate 的地方。 为我工作,谢谢。但是现在提交后我得到403错误。我没有访问此页面的权限。 很高兴这部分工作。我把答案放在下面。我认为 403 错误可能应该是一个不同的问题,因此您可以在帖子中提供更多信息。 【参考方案1】:userProfileUpdate() 方法既是设置 update_user.jsp 也是在显示 update_user.jsp 页面之前更新用户实体对象。尝试将逻辑拆分为两种方法,一种设置 update_user.jsp,另一种处理调用 userservice.update() 方法的帖子,如下所示。
@RequestMapping(value = "/user/profile/update", method=RequestMethod.GET)
public ModelAndView userProfileUpdate(HttpServletRequest request, Principal principal)
ModelAndView modelAndView = new ModelAndView();
UserEntity userEntity = userService.getByEmail(principal.getName());
modelAndView.setViewName("update_user");
modelAndView.addObject("userName", userEntity.getName());
modelAndView.addObject("userSurname", userEntity.getSurname());
modelAndView.addObject("userMiddleName", userEntity.getMiddleName());
modelAndView.addObject("userPhone", userEntity.getPhone());
return modelAndView;
@RequestMapping(value = "/user/profile/updatePost", method=RequestMethod.POST)
public ModelAndView userProfileUpdatePost(HttpServletRequest request, Principal principal)
ModelAndView modelAndView = new ModelAndView();
UserEntity userEntity = userService.getByEmail(principal.getName());
String userName = request.getParameter("userName");
String userSurname = request.getParameter("userSurname");
String userMiddleName = request.getParameter("userMiddleName");
String userPhone = request.getParameter("userPhone");
userEntity.setName(userName);
userEntity.setSurname(userSurname);
userEntity.setMiddleName(userMiddleName);
userEntity.setPhone(userPhone);
userService.update(userEntity);
return new ModelAndView(new RedirectView("/user/profile"));
【讨论】:
以上是关于如何使用 Spring MVC 和 Spring Security 更新有关授权用户的信息并将其保存在数据库中的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Spring MVC、Spring Security 和 FreeMarker 在每个页面上显示用户名
我们如何在 Spring MVC 项目中使用 Spring Cloud Sleuth?