I have UserBean in my Spring-MVC project to store user.
#Component
#Scope(value="session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserBeanImpl implements UserBean {
private User user;
#Override
public User getUser() {
return user;
}
#Override
public UserBean setUser(User user) {
this.user = user;
return this;
}
}
Can I autowire it into service layer? Should I autowire it only into controllers?
First way (autowire into servise):
#Service
public class MyServiceImpl implements MyService {
#Autowired
UserBean userBean;
#Override
public void doSomething(int id) {
dao.doSomething(id,userBean.getUser());
}
Second way (autowire into cotroller):
#Service
public class MyServiceImpl implements MyService {
#Override
public void doSomething(int id, User user) {
dao.doSomething(id, user);
}
}
#Controller
public class MyController {
#Autowired
UserBean userBean;
#RequestMapping(value = {"/"})
public void do(#RequestParam("id") int id) {
myService.doSomething(id, userBean.getUser());
}
}
What is better in terms of Spring MVC-Service-DAO architecture?
#Service
public class MyServiceImpl implements MyService {
#Autowired
UserBean userBean;
#Override
public void doSomething(int id) {
dao.doSomething(id,userBean.getUser());
}
This is the best way (Service), because the controller layer should be used to get the requests, send responses and call Services to run Business logic. Save a user is a service layer task.
Related
In my post i have tried simple login functionality whether the user is valid or not. if the user is valid i have to implement session feature for 30 min,
if the user is not doing anything till 30 min how to make him to logout, he has to login with username and password. can anyone explain how i can implement session management in spring mvc with the above requirement session time for 30 min ?
AppConfig
#Configuration
#PropertySource("classpath:spring.properties")
#EnableWebMvc
#ComponentScan(basePackages = "com.spring")
public class AppConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource rb = new ResourceBundleMessageSource();
rb.setBasenames(new String[] { "messages/messages", "messages/validation" });
return rb;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
AppInitializer
public class AppInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
ctx.setServletContext(container);
ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
}
}
Contoller
#RequestMapping(value = "/authenticateUserLogin", method = RequestMethod.POST)
public ModelAndView authenticateUserLogin(#ModelAttribute("loginForm") #Validated User user, BindingResult result, Model model, final RedirectAttributes redirectAttributes) {
if(user.getEmail().equals("abc") && user.getPassword().equals("123")){
return new ModelAndView("Dashboard", "name", model);
}
else
{
return new ModelAndView("Login", "name", model);
}
}
In this answer it's explained how to do it via java config or xml config.
This is the Application.java class
#Configuration
#SpringBootApplication
//#EnableGlobalMethodSecurity(prePostEnabled=true)
#EnableTransactionManagement
#EnableCaching
#EnableJpaRepositories(basePackages="om.gov.moh.irs.dao.repos",repositoryImplementationPostfix="CustomImpl")
public class Application extends SpringBootServletInitializer {
#Autowired
Environment env;
#Bean
#ConfigurationProperties("spring.datasource")
public ComboPooledDataSource dataSource() {
return new ComboPooledDataSource();
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasenames("messages"); // name of the resource bundle
source.setUseCodeAsDefaultMessage(true);
return source;
}
#Bean
MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(env.getProperty("multipart.maxFileSize"));
factory.setMaxRequestSize(env.getProperty("multipart.maxRequestSize"));
return factory.createMultipartConfig();
}
}
This is the dto class which Im trying to cache.
public class PaginationDto implements Serializable {
private static final long serialVersionUID = 1L;
public Integer totalRecords;
public List<?> paginatedList;
//constructor and getter setter here
}
Controller class
#RequestMapping(value="categories", method=RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultDecorator searchCategories(#RequestBody CategoryDto categoryDto) int pageSize, #RequestParam("sort") String sortOrder) throws BusinessException{
return handler.resolveResult(categoryService.searchCategory(categoryDto), OperationEnum.SEARCH);
}
Service class where #cacheable is defined.
#Override
#Cacheable("category")
public PaginationDto searchCategory(CategoryDto categoryDto) throws CategoryException {
System.out.println("#######category");
PaginationDto paginationDtoResponse = null;
paginationDtoResponse = categoryRepoCustom.fetchCategories(categoryDto);
return paginationDtoResponse;
}
Defined ehcache.xml file
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd"
updateCheck="true" monitoring="autodetect" dynamicConfig="true">
<cache name="category"
maxElementsInMemory="100" eternal="false"
overflowToDisk="false"
timeToLiveSeconds="30000" timeToIdleSeconds="0"
memoryStoreEvictionPolicy="LFU" transactionalMode="off">
</cache>
</ehcache>
On hitting this http://localhost:9190/isa/categories each time console log is printing, which means data is not fetching from the cache.
This is the test class:
#MockBean
private UserRepository userRepository;
#Before
public void beforeClass() {
String mobile;
when(this.userRepository.findByMobile(Mockito.anyString())).thenAnswer(new Answer<User>() {
#Override
public User answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return MockData.getUserByMobile((String) args[0]);
}
});
}
#Test
#WithUserDetails(value = MockData.CUSTOMER_USERNAME, userDetailsServiceBeanName = "customizedUserDetailsService")
public void testAdd() throws Exception {}
And this is the userDetails implementation:
#Autowired
private UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) {
User user = (User) userRepository.findByMobile(username); // user is always null
What I expect is when userRepository.findByMobile is called, it should call the getUserByMobile method defined in #Before. But obviously the Mockito config does not work OR userRepository fail to mock. What's wrong and how to solve it?
UserRepository is used in userDetails implementation, and it needs to be injected into userDetails as described in this. However because XXRepository is in interface, so #InjectedMock cannot be used. Then classes become:
Test class:
#MockBean
private UserService userService;
#InjectMocks
private CustomizedUserDetailsService customizedUserDetailsService;
#Before
public void before() {
MockitoAnnotations.initMocks(this);
when(this.userService.findByMobile(Mockito.anyString())).thenAnswer(new Answer<User>() {
#Override
public User answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return MockData.getUserByMobile((String) args[0]);
}
});
}
#Test
#WithUserDetails(value = MockData.CUSTOMER_USERNAME, userDetailsServiceBeanName = "customizedUserDetailsService") {}
And userDetails:
#Autowired
private UserService userService;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) {
User user = (User) userService.findByMobile(username);
I can see that the userService in userDetails is the same userService mocked in test class, however #Before method is called after the #WithUserDetails userDetails. So finally in order to achieve loading MockData user been, I think I have to create another userDetails just for UT. EDIT 2: Actually, I have tried it without #InjectMocks and using userService (originally I was using userRepository), it works too.
I'm trying to set my user on to a course object that I created and I keeping getting the error org.springframework.security.core.userdetails.User cannot be cast to com.example.security.CustomUserDetails I know I'm getting the user details from the session, because when I run it in debug mode I can see the name of the currently logged in user, so I think I'm somewhat close to a solution.
Here's my Controller
#RequestMapping(value="createCourse", method=RequestMethod.POST)
public String createCoursePost (#ModelAttribute Course course, Long userId, ModelMap model, Authentication auth)
{
CustomUserDetails myUserDetails = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
userId = myUserDetails.getUser().getId();
User user = userRepo.findOne(userId);
course.setUser(user);
courseRepo.save(course);
return "redirect:/courses";
}
Here's my UserDetailsServiceImpl
#Service
#Qualifier("customUserDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepo;
#Transactional
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
com.example.domain.User user = userRepo.findByUsername(username);
List<GrantedAuthority> authorities = buildUserAuthority(user.getRoles());
return buildUserForAuthentication(user, authorities);
}
private User buildUserForAuthentication(com.example.domain.User user,
List<GrantedAuthority> authorities) {
return new User(user.getUsername(), user.getPassword(), authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRoleName()));
}
return new ArrayList<GrantedAuthority>(setAuths);
}
Here's my customUserDetails, it's possible that me getting and setting the user could be redundant, but I saw an example with this, and so I'm not really sure what to do with this.
public class CustomUserDetails extends User implements UserDetails{
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
private static final long serialVersionUID = 2020921373107176828L;
public CustomUserDetails () {}
public CustomUserDetails (User user) {
super(user);
}
#Override
public Set<Authorities> getAuthorities() {
return super.getAuthorities();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
Here's my webSecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static PasswordEncoder encoder;
#Autowired
private UserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.csrfTokenRepository(csrfTokenRepository());
http
.authorizeRequests()
.antMatchers("/", "/home", "/register", "/courses", "/editCourse", "/sets", "/search", "/viewCourse/{courseId}", "/fonts/glyphicons-halflings-regular.ttf","fonts/glyphicons-halflings-regular.woff", "fonts/glyphicons-halflings-regular.woff", "/viewCourse/post/{postId}").permitAll()
.anyRequest().authenticated();
http
.formLogin()
.loginPage("/login")
.usernameParameter("username").passwordParameter("password")
.permitAll()
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/loggedout")
.and()
.sessionManagement()
.maximumSessions(1);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
if(encoder == null) {
encoder = new BCryptPasswordEncoder();
}
return encoder;
}
private CsrfTokenRepository csrfTokenRepository()
{
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setSessionAttributeName("_csrf");
return repository;
}
}
I solved this just in case anyone else sees this. I think my problem was that I wasn't returning userdetails in the userdetailsimpl class. And I also had to update my controller. So here's my new code
Controller
#RequestMapping(value="createCourse", method=RequestMethod.POST)
public String createCoursePost (#ModelAttribute Course course, Long userId, ModelMap model)
{
CustomUserDetails myUserDetails = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
userId = myUserDetails.getUser().getId();
User user = userRepo.findOne(userId);
course.setUser(user);
courseRepo.save(course);
return "redirect:/courses";
}
And userDetailsServiceImpl
#Service
#Qualifier("customUserDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepo;
#Transactional
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
com.example.domain.User user = userRepo.findByUsername(username);
CustomUserDetails customUserDetails = new CustomUserDetails(user);
customUserDetails.setUser(user);
return customUserDetails;
}
}
I create form and controller this form have some validation constrains using Hibernate validator. I face problem when starting test the validation constrains but I got Blue Exception page with the attributemodel with the rejected.
This the configuration
#Configuration
#ComponentScan(basePackages = {"com.whatever.core.web"})
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurationSupport {
private static final String MESSAGE_SOURCE = "/WEB-INF/classes/messages";
private static final String TILES = "/WEB-INF/tiles/tiles.xml";
private static final String VIEWS = "/WEB-INF/views/**/views.xml";
private static final String RESOURCES_HANDLER = "/resources/";
private static final String RESOURCES_LOCATION = RESOURCES_HANDLER + "**";
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping requestMappingHandlerMapping = super.requestMappingHandlerMapping();
requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
requestMappingHandlerMapping.setUseTrailingSlashMatch(false);
return requestMappingHandlerMapping;
}
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJacksonHttpMessageConverter());
}
#Bean(name = "messageSource")
public MessageSource configureMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename(MESSAGE_SOURCE);
messageSource.setCacheSeconds(5);
return messageSource;
}
#Bean
public TilesViewResolver configureTilesViewResolver() {
return new TilesViewResolver();
}
#Bean
public TilesConfigurer configureTilesConfigurer() {
TilesConfigurer configurer = new TilesConfigurer();
configurer.setDefinitions(new String[] {TILES, VIEWS});
return configurer;
}
#Override
public Validator getValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(configureMessageSource());
return validator;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(RESOURCES_HANDLER).addResourceLocations(RESOURCES_LOCATION);
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
and the controller here
if(result.hasErrors()){
return null; OR "view name"
}
User user = new User();
user.setUsername(userModel.getUsername());
user.setFirstName(userModel.getFirstName());
user.setLastName(userModel.getLastName());
user.setGender(userModel.getGender());
user.setLocation(userModel.getLocation());
user.setPassword(passwordEncoder.encodePassword(userModel.getPassword(),null));
userRepository.save(user);
doAutoLogin(userModel.getUsername(),userModel.getPassword(),request);
return "redirect:/home";
NOTE: I use springMVC, spring security, tiles, and hibernate validator
I used SpringMVC with hibernate validator with XML configuration and portal environment and work fine I don't know what the wrong here!!
I Found the issue! the signature of the method controller should be like this
public String signup(#ModelAttribute("userModel") #Valid SignupForm userModel,BindingResult result,HttpServletRequest request,HttpServletResponse response,ModelMap model)
as what I read in sprinsource forum, the BindingResult should follow the modelAttribute and work find. I didn't find any official documentation for this but its work now.
to see the thread of springsource forum check this link http://forum.springsource.org/showthread.php?85815-BindException-Thrown-on-bind-errors-(instead-of-returning-errors-to-controller-method