Using Redirect in Spring Boot - java

Im having trouble using redirect functionality within my spring boot application. As shown in my code below im returning "redirect:/aucConfirm/" but when i initiate this i get a "This application has no explicit mapping" error.
#Controller
#RequestMapping("/")
public class WelcomeController {
#Autowired
AuctionItemRepository aucRepository;
// inject via application.properties
#Value("${welcome.message:test}")
private String message = "Hello World";
#RequestMapping(value = "/")
public String welcome(Map<String, Object> model) {
model.put("message", this.message);
return "welcome";
}
#RequestMapping(value = "/sell", method = RequestMethod.GET)
public String addAuction(Model model, HttpServletRequest request) {
model.addAttribute("newAuction", new AuctionItem());
return "NewAuction";
}
#RequestMapping(value = "/sell", method = RequestMethod.POST)
public String saveAuction(#ModelAttribute AuctionItem newAuction, RedirectAttributes attributes){
return "redirect:/aucConfirm";
}
#RequestMapping(value = "/aucConfirm", method = RequestMethod.GET)
public String confirmNewAuction(Model model, HttpServletRequest request) {
return "aucConfirm";
}
}
This is my current configuration class -
#SpringBootApplication
#ComponentScan
#EnableJpaRepositories("All")
#EntityScan("All")
public class AuctionWebApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(AuctionWebApplication.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(AuctionWebApplication.class, args);
}
#Bean
DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/auctiondb?zeroDateTimeBehavior=convertToNull");
dataSource.setUsername("root");
dataSource.setPassword("toor");
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(dataSource);
entityManager.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManager.setPackagesToScan("All");
Properties jpaProperties = new Properties();
jpaProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
jpaProperties.setProperty("hibernate.hbm2ddl.auto", "update");
jpaProperties.setProperty("hibernate.id.new_generator_mappings", "false");
entityManager.setJpaProperties(jpaProperties);
return entityManager;
}
}
Update
Could it possibly have something to do with my jsp page that im trying to establish the recommit? I have a mvc form here -
<mvc:form class="form-inline" action="${action}" method="post" modelAttribute="newAuction" id="addNewAuc">
and when the user hits the next button it should hit the "redirect:/aucConfirm" -
<button type="submit" name="submit" class="btn btn-success" form="addNewAuc">Next</button>
Is there anything wrong with this code? Im running out of ideas at this point. Do i need to add anything to my spring boot configuration class to get it working? - Please help!
Update 2
http://localhost:8080/AuctionWebsite/sell
When i enter details int this form and select next i want it to redirect to-
http://localhost:8080/AuctionWebsite/aucConfirm
However this is what appears-
not redirecting at all and remaining with link http://localhost:8080/AuctionWebsite/sell
http://localhost:8080/AuctionWebsite/aucConfirm works when entered manually
Im getting a little bit desperate at this point, would really appreciate any help.

Use this code
redirect:aucConfirm
Instead of
redirect:/aucConfirm
As per your updated question. it seems that
"This application has no explicit mapping" error
Is for /error url;
The actual error is
Validation failed for object 'auctionItem'
So problem is not with redirection , it is something else related to validation. You may find specific error on console log.

I could not get this to work (with or without a '/' after the ':')...
#PostMapping("/customer/update")
public String customerUpdate(Customer customer) {
customerRepository.save(customer);
return "redirect:customer/list";
}
...so went with this...
#PostMapping("/customer/update")
public RedirectView customerUpdate(Customer customer) {
customerRepository.save(customer);
return new RedirectView("/customer/list");
}

Related

HttpServletRequest request.getSession().setAttribute not working

I'm working with RedisHttpSession and my basic goal is to save the staff object in the session object on successful login, retrieve it wherever I need and destroy the session on logout.
On successful login, this is what I'm doing:
Staff staff = staffService.getEmailInstance(body.getEmailId());
request.getSession(true).setAttribute("staff", staff);
And Logout is simply this:
request.getSession().invalidate();
In a different controller, I am calling this utility method that checks if the staff is logged in: util.isStaffLoggedIn(request, response, StaffRole.EDITOR); If the staff is logged in, the API proceeds, else the user is redirected to the login page.
#Service
public class Util {
public boolean isStaffLoggedIn(HttpServletRequest request, HttpServletResponse response, StaffRole staffRole)
throws PaperTrueInvalidCredentialsException, PaperTrueJavaException {
Staff staff = (Staff) request.getSession().getAttribute("staff");
if (!isObjectNull(staff) && staff.getStaffRole().equals(staffRole)) {
return true;
}
invalidateSessionAndRedirect(request, response);
return false;
}
public void invalidateSessionAndRedirect(HttpServletRequest request, HttpServletResponse response)
throws PaperTrueJavaException, PaperTrueInvalidCredentialsException {
request.getSession().invalidate();
try {
response.sendRedirect(ProjectConfigurations.configMap.get("staff_logout_path"));
} catch (IOException e) {
throw new PaperTrueJavaException(e.getMessage());
}
throw new PaperTrueInvalidCredentialsException("Staff not loggedIn");
}
}
Now while the app is running, the get-jobs API is called immidiately after successful login. Most of the times the request.getSession().getAttribute("staff") method works fine and returns the 'staff' object but, once in a while, it returns null. This doesn't happen often, but it does. I printed the session Id to see if they are different after logout, and they were. After each logout I had a new session Id. I even checked if the staff object I retrieved from the database was null, but it wasn't.
The staff object was successfully saved in the sessions but I wasn't able to retrieve it in othe APIs. This is how my session config looks:
#EnableRedisHttpSession(maxInactiveIntervalInSeconds = 10800)
public class SessionConfig {
HashMap<String, String> configMap = ProjectConfigurations.configMap;
#Bean
public LettuceConnectionFactory connectionFactory() {
int redisPort = Integer.parseInt(configMap.get("redis_port"));
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(
configMap.get("redis_host"), redisPort);
redisStandaloneConfiguration.setPassword(configMap.get("redis_password"));
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("PTSESSIONID");
serializer.setSameSite("none");
serializer.setUseSecureCookie(!configMap.get("staff_logout_path").contains("localhost"));
return serializer;
}
}
Please let me know if I missed out anything. Thanks in advance.
Update 1
I'm not invalidating the session anymore and I've replaced request.getSession(true).setAttribute("staff", staff); to request.getSession().setAttribute("staff", staff);
I'm setting the 'staff' in StaffController and getting it in EditorController. Here's how I'm setting it:
#RestController
#RequestMapping(path = { "/staff" }, produces = "application/json")
public class StaffApiController {
private final HttpServletRequest request;
private final HttpSession httpSession;
#Autowired
private StaffService staffService;
#Autowired
StaffApiController(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
this.request = request;
this.httpSession = session;
}
#PostMapping("/login")
public ResponseEntity<StaffLoginResponse> login(#Valid #RequestBody StaffLoginBody body) {
StaffLoginResponse staffLoginResponse = new StaffLoginResponse();
try {
if (!staffService.isValidLogin(body.getEmailId(), body.getPassword())) {
throw new PaperTrueInvalidCredentialsException("Invalid Credentials");
}
Staff staff = staffService.getEmailInstance(body.getEmailId());
httpSession.setAttribute("staff", staff);
staffLoginResponse.setEmail(staff.getEmail()).setRole(staff.getStaffRole().getValue())
.setStaffID(staff.getId()).setStatus(new Status("Staff Login Successful"));
} catch (PaperTrueException e) {
httpSession.removeAttribute("staff");
staffLoginResponse.setStatus(new Status(e.getCode(), e.getMessage()));
}
return ResponseEntity.ok(staffLoginResponse);
}
#PostMapping("/logout")
public ResponseEntity<Status> logout() {
httpSession.removeAttribute("staff");
return ResponseEntity.ok(new Status("Staff Logged Out Successfully"));
}
}
If you are using Spring Security, you can create a custom "/login" endpoint that authenticates the user by setting the SecurityContext.
You can use the default logout behaviour provided by Spring Security.
If you do not need to supply the credentials in the body, you can use the default login behaviour provided by Spring Security and omit this Controller altogether.
This is intended as a starting point.
It does not offer comprehensive security, for example it may be vulnerable session fixation attacks.
#RestController
public class LoginController {
private AuthenticationManager authenticationManager;
public LoginController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#PostMapping("/login")
public void login(#RequestBody StaffLoginBody body, HttpServletRequest request) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(body.getUsername(), body.getPassword());
Authentication auth = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
HttpSession session = request.getSession();
session.setAttribute("staff", "staff_value");
}
#GetMapping("/jobs")
public String getStaffJobs(HttpServletRequest request) {
return request.getSession().getAttribute("staff").toString();
}
}
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// expose AuthenticationManager bean to be used in Controller
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
)
// use built in logout
.logout(logout -> logout
.deleteCookies("PTSESSIONID")
);
}
}
You will need to add the Spring Security dependency to use this code org.springframework.boot:spring-boot-starter-security.

Spring REST Controller content/type not supported for all content types

I'm trying to create a Web Service that consumes an HTML which is later used to create a PDF with iText.
I'm using Postman in order to send my requests to server but everytime I get the following error, no matter which content type I select:
{
"status": "CONFLICT",
"code": 40902,
"message": "Content type 'text/plain' not supported",
"developerMessage": "null",
"moreInfoUrl": "class org.springframework.web.HttpMediaTypeNotSupportedException",
"throwable": null
}
The message changes depending on the content type selected:
"message": "Content type 'text/xml' not supported"
"message": "Content type 'text/html' not supported"
This is my endpoint:
#RequestMapping(path = "/reports/pdf", method = RequestMethod.POST, consumes = MediaType.TEXT_HTML_VALUE)
public ResponseEntity<byte[]> generatePdfReport(#RequestBody String html) throws Exception {
byte[] pdfBytes = reportsService.generatePdf(html);
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(pdfBytes, HttpStatus.OK);
return response;
}
I've changed consumes attribute to:
MediaType.TEXT_PLAIN_VALUE
MediaType.TEXT_XML_VALUE
to match what I'm sending on Postman.
As well as per #dnault comment, removing it completely from the RequestMapping annotation with the same results.
I've looked into similar questions:
Content type 'text/plain;charset=UTF-8' not supported error in spring boot inside RestController class This is the closest one to my problem, but I'm already setting my content type in the request to the same one I expect on the endpoint.
Error org.springframework.web.HttpMediaTypeNotSupportedException
Error: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain;charset=UTF-8' not supported
However none of the above and some others that aren't really close to my problem that I've checked already provide an answer that solves this issue.
The sample HTML I'm trying to send to server is:
<table style="border: 1px solid black; font-size: 12px; margin-top: 1px; width: 900px;" id="table_direction">
<tr>
<td width="33%" colspan="2">
<strong>Data1</strong>
</td>
<td width="33%" colspan="2">
<strongData2</strong>
</td>
<td width="16%" colspan="1">Foo</td>
<td width="16%" colspan="1">Bar</td>
</tr>
<tr>
<td colspan="">ID</td>
<td colspan="">123456</td>
<td colspan="">Property 1</td>
<td colspan="">Foo</td>
<td colspan="">Property 2</td>
<td colspan="">Bar</td>
</tr>
</table>
Our configuration is made by Java configuration classes which are as follows:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.company.project")
#PropertySource("classpath:application.properties")
#EnableTransactionManagement // proxyTargetClass = true
#EnableJpaRepositories(basePackages = { "com.company.project.dao", "com.company.project.repository" })
public class HelloWorldConfiguration extends WebMvcConfigurerAdapter {
#Inject
Environment env;
#Value("${jdbc.user}")
private String userDB;
#Value("${jdbc.password}")
private String passDB;
#Value("${jdbc.url}")
private String urlDB;
#Value("${jdbc.driverClassName}")
private String driverClassName;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
#Bean(name = "dataSource")
public DriverManagerDataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(driverClassName);
driverManagerDataSource.setUrl(urlDB);
driverManagerDataSource.setUsername(userDB);
driverManagerDataSource.setPassword(passDB);
return driverManagerDataSource;
}
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "com.company.project.model" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
entityManagerFactoryBean.setPackagesToScan("com.company.project.model");
entityManagerFactoryBean.setJpaProperties(hibernateProperties());
return entityManagerFactoryBean;
}
private Properties hibernateProperties() {
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
jpaProperties.put("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
jpaProperties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
return jpaProperties;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Bean(name = "transactionManager2")
#Autowired
#Named("transactionManager2")
public HibernateTransactionManager transactionManager2(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
return vendorAdapter;
}
public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
Hibernate5Module module = new Hibernate5Module();
module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
mapper.registerModule(module);
messageConverter.setObjectMapper(mapper);
return messageConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}
}
#ControllerAdvice
public class GenericRepositoryRestExceptionHandler extends RepositoryRestExceptionHandler {
#Autowired
public GenericRepositoryRestExceptionHandler(MessageSource messageSource) {
super(messageSource);
// TODO Auto-generated constructor stub
}
#ResponseBody
#ExceptionHandler(Exception.class)
ResponseEntity<?> handleException(Exception e) {
// return response(HttpStatus.CONFLICT, 40902, e.getMessage());
return response(HttpStatus.CONFLICT, 40902, e.getMessage(), e.getCause() + "", e.getClass() + "");
}
private ResponseEntity<RestError> response(HttpStatus status, int code, String msg) {
return response(status, code, msg, "", "");
}
private ResponseEntity<RestError> response(HttpStatus status, int code, String msg, String devMsg, String moreInfo) {
return new ResponseEntity<>(new RestError(status, code, msg, devMsg, moreInfo, null), status);
}
}
public class CORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
System.out.println("Filtering on...........................................................");
SecurityContext ctx = SecurityContextHolder.getContext();
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
Because of this
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}
Spring MVC skips all the default converters it would have otherwise registered. (If you're curious, this is done in WebMvcConfigurationSupport#getMessageConverters(..).)
Your only HttpMessageConverter, MappingJackson2HttpMessageConverter , can only read MediaType.APPLICATION_JSON content, ie. application/json. Therefore, every other request content type will be rejected.
You can register all the regular defaults yourself in your configureMessageConverters override (or just the ones you need to read HTML forms, XML, plain text, etc.).
Or, you could instead override extendMessageConverters to find the default MappingJackson2HttpMessageConverter instance and configure it to use your custom ObjectMapper. For example,
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper mapper = new ObjectMapper();
// ...initialize...
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter m = (MappingJackson2HttpMessageConverter) converter;
m.setObjectMapper(mapper);
}
}
}
And maybe drop a comment about relying on the default list of converters.
I realize that the case mentioned in the OP'S problem was slightly different than mine, although I had the same error.
In my case, it showed up after I started using a different ObjectMapper.
Initially I was using the
"com.fasterxml.jackson.core.databind" as the ObjectMapper in my project.
I refactored some code, and in the process switched to org.codehaus.jackson as the source for the ObjectMapper.
That's when all hell broke loose, and Spring would simply reject every request, irrespective of the Content-Type with the same error-message as the OP.
It took me 3 days to sort this out, but finally I simply switched back to "databind" for the ObjectMapper. Everything worked magically after that.
I'm mentioning this here, in case it helps someone. Unline the OP I hadn't touched MessageConverters or anything that complicated.

Spring web flow don't redirect after post submission

I encounter a very strange issue in my project using spring web flow 2.4.0.
In the documentation of web-flow project we can read on chapter 2 the following statement:
By default Web Flow does a client-side redirect upon entering every
view state.
My concern is when i submit a form web flow do not make redirection that implies a new form submission is made if user reload or refresh the page. This is very boring.
I tried many things and found a couple of solutions on the web
for example make the redirection programmaticaly with the following code :
context.getExternalContext().requestFlowExecutionRedirect();
And i finally found an attribute "redirect" for the tag view-state in flow configuration. when it is set to true everything works fine.
Since web flow documentation mentions that this behavior (redirect automatically ) is the default one , does anyone heard about a better way to do this after form submission.
I am looking for a kind of POST REDIRECT GET pattern.
Thank you and sorry for my english :)
Just verify your springwebflow xml file configuration, it could be because of
redirect flag set to false in the flow-execution-attributes.
<webflow:flow-execution-attributes>
<webflow:always-redirect-on-pause value="true"/>
<webflow:redirect-in-same-state value="true"/>
</webflow:flow-execution-attributes>
</webflow:flow-executor>
To do the same
following is the code in Bean Level
#Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(this.flowRegistry())
.setRedirectInSameState(true)
.setAlwaysRedirectOnPause(true)
.build();
}
where the class extends AbstractFlowConfiguration
After lot of reading was finally able to change the default behaviour
( default behaviour was - Get request , post (302/303 - redirect as per location appended for each request ) , finally a get call.
So for one request we will send a Get Request then Service will return 302/303 with location attribute ( ie redirected with query param ) and as a response HTML with QueryString usually e1s1 is loaded. Sample proj is in this link and following is the change that is been implemented to avoid this default behaviour as following
To avoid 303/302 which has unpredictable behaviour i have stoped redirection with following addition to Config Class
#Configuration
public class WebFlowWithMvcConfig extends AbstractFlowConfiguration {
//implements WebMvcConfigurer
#Autowired
private LocalValidatorFactoryBean localValidatorFacotryBean;
/*
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor());
}
*/
#Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder() //
.setBasePath("classpath:flows") //
.addFlowLocationPattern("/**/*-flow.xml") //
.setFlowBuilderServices(this.flowBuilderServices()) //
.build();
}
#Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(this.flowRegistry())
.setAlwaysRedirectOnPause(false)
.setRedirectInSameState(false)
.build();
}
#Bean
public FlowBuilderServices flowBuilderServices() {
return getFlowBuilderServicesBuilder() //
.setViewFactoryCreator(this.mvcViewFactoryCreator()) // Important!
.setValidator(this.localValidatorFacotryBean)
.build();
}
// ----------------------------------------------------------
#Bean
public FlowHandlerMapping flowHandlerMapping() {
FlowHandlerMapping handlerMapping = new FlowHandlerMapping();
handlerMapping.setOrder(-1);
handlerMapping.setFlowRegistry(this.flowRegistry());
//handlerMapping.setInterceptors(new LogInterceptor());
return handlerMapping;
}
#Bean
public FlowHandlerAdapter flowHandlerAdapter() {
FlowHandlerAdapter handlerAdapter = new FlowHandlerAdapter();
handlerAdapter.setFlowExecutor(this.flowExecutor());
handlerAdapter.setSaveOutputToFlashScopeOnRedirect(true);
//handlerAdapter.setStatusCode(HttpStatus.SEE_OTHER);
//handlerAdapter.setStatusCode(HttpStatus.TEMPORARY_REDIRECT);
return handlerAdapter;
}
#Bean
public ViewFactoryCreator mvcViewFactoryCreator() {
MvcViewFactoryCreator factoryCreator = new MvcViewFactoryCreator();
factoryCreator.setViewResolvers(Collections.singletonList(this.thymeleafViewResolver()));
factoryCreator.setUseSpringBeanBinding(true);
return factoryCreator;
}
#Bean
#Description("Thymeleaf AJAX view resolver for Spring WebFlow")
public AjaxThymeleafViewResolver thymeleafViewResolver() {
AjaxThymeleafViewResolver viewResolver = new AjaxThymeleafViewResolver();
viewResolver.setViewClass(FlowAjaxThymeleafView.class);
viewResolver.setTemplateEngine(this.templateEngine());
viewResolver.setCharacterEncoding("UTF-8");
return viewResolver;
}
#Bean
#Description("Thymeleaf template resolver serving HTML 5")
public ClassLoaderTemplateResolver templateResolver() {
ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setPrefix("templates/");
templateResolver.setCacheable(false);
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}
#Bean
#Description("Thymeleaf template engine with Spring integration")
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(this.templateResolver());
return templateEngine;
}
}
So we have made following as false
.setAlwaysRedirectOnPause(false)
.setRedirectInSameState(false)
Which will avoid location redirect now the similar change has to be implemented in the template html's too. So the change was to add an action url to html template wherever form is present as following
<form .. th:action="${flowExecutionUrl}">
Which successfully does form submission and responds with 200 Ok http status and html page. Hence no more (GET - 200 to 302 redirect to 200) instead direct single request call with Get 200/Post 200 and response is binded to Html page.

ACL configuration problems with Spring Security

I'm new in the Spring's world I'm using Spring Boot 1.2.5 with Spring Security 3.1.2. Due to my project's requirements I need to configure an ACL security model. I have the following java class configuration:
#Configuration
public class ACLConfig {
#Autowired
DataSource dataSource;
#Bean
JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
#Bean
DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
#Bean
EhCacheBasedAclCache aclCache() {
EhCacheFactoryBean factoryBean = new EhCacheFactoryBean();
EhCacheManagerFactoryBean cacheManager = new EhCacheManagerFactoryBean();
cacheManager.setAcceptExisting(true);
cacheManager.setCacheManagerName(CacheManager.getInstance().getName());
cacheManager.afterPropertiesSet();
factoryBean.setName("aclCache");
factoryBean.setCacheManager(cacheManager.getObject());
factoryBean.setMaxBytesLocalHeap("16M");
factoryBean.setMaxEntriesLocalHeap(0L);
factoryBean.afterPropertiesSet();
return new EhCacheBasedAclCache(factoryBean.getObject());
}
#Bean
LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
#Bean
AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SUPER_ADMIN"),
new SimpleGrantedAuthority("ROLE_SUPER_ADMIN"),
new SimpleGrantedAuthority("ROLE_SUPER_ADMIN"));
}
#Bean
JdbcMutableAclService aclService() {
JdbcMutableAclService service = new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
service.setClassIdentityQuery("select currval(pg_get_serial_sequence('acl_class', 'id'))");
service.setSidIdentityQuery("select currval(pg_get_serial_sequence('acl_sid', 'id'))");
return service;
}
#Bean
AclEntryVoter aclDeleteVoter()
{
AclEntryVoter voter = new AclEntryVoter(aclService(),"ACL_NOMCITY_DELETE", new Permission[] {BasePermission.DELETE});
voter.setProcessDomainObjectClass(NomCity.class);
return voter;
}
#Bean
AclEntryVoter aclUpdateVoter()
{
return new AclEntryVoter(aclService(),"ACL_NOMCITY_UPDATE", new Permission[]{BasePermission.ADMINISTRATION});
}
#Bean
AclEntryVoter aclReadVoter()
{
return new AclEntryVoter(aclService(),"ACL_NOMCITY_READ", new Permission[]{BasePermission.READ});
}
#Bean
AccessDecisionManager accessDecisionManager (){
List<AccessDecisionVoter<? extends Object>> list = new ArrayList<>();
list.add(aclDeleteVoter());
list.add(aclReadVoter());
list.add(aclUpdateVoter());
return new AffirmativeBased(list);
}
}
I have the following RestController's methods, it use the ACLs defined earlier:
#RequestMapping(value = "/nomCitys",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
#Transactional
#Secured({"ROLE_ADMIN","ROLE_USER"})
public ResponseEntity<NomCity> create(#Valid #RequestBody NomCity nomCity) throws URISyntaxException {
NomCity result = nomCityRepository.save(nomCity);
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
ObjectIdentity oi = new ObjectIdentityImpl(NomCity.class,result.hashCode());
MutableAcl acl = mutableAclService.createAcl(oi);
acl.insertAce(0, BasePermission.ADMINISTRATION, new GrantedAuthoritySid("ROLE_ADMIN"), true);
acl.insertAce(1, BasePermission.DELETE, new PrincipalSid(user.getUsername()), true);
acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER"), true);
mutableAclService.updateAcl(acl);
return ResponseEntity.created(new URI("/api/nomCitys/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert("nomCity", result.getId().toString()))
.body(result);
}
When I create a new city the following ACL entries are created too:
The user with ROLE_ADMIN role have Admin permission.
The user how create the city have Delete permission.
The user with ROLE_USER role can read the city.
The following method is the delete method:
#RequestMapping(value = "/nomCitys/{id}",
method = RequestMethod.DELETE,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
#Transactional
#Secured("ACL_NOMCITY_DELETE")
public ResponseEntity<Void> delete(#PathVariable Long id) {
nomCityRepository.delete(id);
ObjectIdentity oid = new ObjectIdentityImpl(NomCity.class,id);
mutableAclService.deleteAcl(oid, true);
return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("nomCity", id.toString())).build();
}
When I create a new city all work fine, the ACL entries are created and stored in the data base, but when I go to remove a city I get a 403, although I'm logging with the user who created the city, reviewing some pages I saw the following xml entry:
<security:global-method-security
secured-annotations="enabled" access-decision-manager ref="customAccessDecisionManager" />
I suppose that it register the AccessDecisionManager but I don't know how do the same using Java Config and I don't if it's the reason of all my problems.
This question is for #secure anotation, but I finally solve the problem make a class configuration for using #Pre and #Post anotation, I post a config java class in my answer for this question.

Spring integration testing a controller with dependency injection

I am trying ot write an integration test for one of my controller classes which have an injected dependency in it. I try to test the part of my controller where it's calling a method through the injected object, but when i run my test its failing due to a null pointer exception. At the test i used #ContexConfiguration and #RunsWith annotations, but it didin't helped.
Some code might help :)
AuthenticationController:
#Controller
public class AuthenticationController {
#Resource(name = "userManagement")
private UserManagement um;
#RequestMapping(method = RequestMethod.POST)
public String onSubmit(#ModelAttribute("user") UserForm user,
BindingResult result, Model model, HttpSession session) {
LoginFormValidator validator = new LoginFormValidator();
validator.validate(user, result);
if (result.hasErrors()) {
return "login";
} else {
User u = um.login(user.getEmail(), user.getPwd());
if (u != null) {
session.setAttribute("user", u);
LOGGER.info("succesful login with email: " + u.getEmail());
model.addAttribute("result", "succesful login");
} else {
model.addAttribute("result", "login failed");
}
return "result";
}
}
in test-context.xml:
beans:bean id="userManagement" class="my.packages.UserManagement"
AuthenticationControllerTest:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"test-context.xml" })
public class AuthenticationControllerTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private AuthenticationController controller;
#Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
controller = new AuthenticationController();
}
#Test
public void testLoginPost() throws Exception {
request.setMethod("POST");
request.setRequestURI("/login");
request.setParameter("email", "test#email.com");
request.setParameter("pwd", "test");
final ModelAndView mav = new AnnotationMethodHandlerAdapter()
.handle(request, response, controller);
final UserForm u =
assertAndReturnModelAttributeOfType(mav, "user", UserForm.class);
assertEquals("test#email.com", u.getEmail());
assertEquals("test", u.getPwd());
assertViewName(mav, "result");
/* if UserForm is not valid */
final BindingResult errors = assertAndReturnModelAttributeOfType(mav,
"org.springframework.validation.BindingResult.user",
BindingResult.class);
assertTrue(errors.hasErrors());
assertViewName(mav, "login");
}
The stacktrace tells me that the error happens where the test calls the login method of the injected userMangaement object. um = null so the injection is not working with the test.
The controller works fine in useage.
Any comment would help a lot!
Thanks in advance,
Sorex
If you want autowire dependencies you can't create your controller like this:
controller = new AuthenticationController();
You can autowire you dependency into your test
#Autowired
private UserManagement um;
and create constructor in your controller to be able to do this:
#Before
public void setUp() {
controller = new AuthenticationController(um);
}
But I would recommend to use MockServletContext.
MockServletContext mockServletContext = new MockServletContext();
mockServletContext.addInitParameter("contextConfigLocation", "path to your xml config"));
ContextLoaderListener listener = new ContextLoaderListener();
listener.initWebApplicationContext(mockServletContext);
There should be also reference to DispatcherServlet somewhere. I have never done this in servlet environmentm, only in spring portlet mvc, but it shoul be similar. The idea is to create fake web application context and call dispacher servlet to have full integration test between your controllers an spring configuration.

Categories

Resources