Spring Boot + Security + Thymeleaf and CSRF token not injected automatically - spring-security

Disclaimer: I know how to inject the token in a form with thymeleaf manually with this:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />`
The goal of this post is to improve the knowledge of the platform and get a better understanding of what's going on inside Spring Boot
I haven't tried Spring Boot, but recently i just decided to give it a try, and have to admit its awesome, but with Thymeleaf and Security on Spring MVC, i didn't need to inject CSRF token on forms (POST), because Thymeleaf took care of it automatically, but now in Spring Boot for some reason it doesn't.
From the Spring Boot Reference, i found a list of the common properties used on application.properties file, and the ones related to thymeleaf and security are:
Thymeleaf Properties
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.excluded-view-names= # comma-separated list of view names that should be excluded from resolution
spring.thymeleaf.view-names= # comma-separated list of view names that can be resolved
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html # ;charset=<encoding> is added
spring.thymeleaf.cache=true # set to false for hot refresh
Security Properties
security.user.name=user # login username
security.user.password= # login password
security.user.role=USER # role assigned to the user
security.require-ssl=false # advanced settings ...
security.enable-csrf=false
security.basic.enabled=true
security.basic.realm=Spring
security.basic.path= # /**
security.basic.authorize-mode= # ROLE, AUTHENTICATED, NONE
security.filter-order=0
security.headers.xss=false
security.headers.cache=false
security.headers.frame=false
security.headers.content-type=false
security.headers.hsts=all # none / domain / all
security.sessions=stateless # always / never / if_required / stateless
security.ignored= # Comma-separated list of paths to exclude from the default secured paths
But if the solution to make Thymeleaf inject the token again is there, i fail to see it.
Edit: adding my configuration
The project was created using the initializer that was shipped in the last STS version (which in my opinion is awesome), with Web, Thymeleaf, Security, JPA, MySQL, H2, Mail, Facebook, Twitter, LinkedIn and Actuator items checked, and added some extras aftwerwards
Using Java 7 and Tomcat 7 because i intend to deploy the project on Openshift in a near future, and next there are my config files:
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.adrisasws.springmvc.WebApplication</start-class>
<java.version>1.7</java.version>
<tomcat.version>7.0.59</tomcat.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>1.1.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-facebook</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-linkedin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-twitter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-google</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>openshift</id>
<build>
<finalName>webapp</finalName>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<outputDirectory>webapps</outputDirectory>
<warName>ROOT</warName>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Security config (exactly the same security file i'm using in a non-boot project in which the CSRF token actually gets injected automatically)
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//////////////////////////////////////////////////////////////////////////
// DEPENDENCIES //
//////////////////////////////////////////////////////////////////////////
#Autowired private DataSource dataSource;
#Autowired private UserRepository userRepository;
//////////////////////////////////////////////////////////////////////////
// PROPERTIES //
//////////////////////////////////////////////////////////////////////////
#Value("${custom.security.rememberme-secret}") private String secret;
#Value("${custom.security.rememberme-create-tables}") private String createTables;
private final static String[] adminRequests = new String[] { ... some matchers here... };
private final static String[] userRequests = new String[] { ... some matchers here... };
private final static String[] publicRequests = new String[] { ...some matchers here... };
//////////////////////////////////////////////////////////////////////////
// AUTHORIZATION //
//////////////////////////////////////////////////////////////////////////
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/error**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(adminRequests).access("hasRole('"+Role.ADMIN.toString()+"')")
.antMatchers(userRequests).access("hasRole('"+Role.USER.toString()+"')")
.antMatchers(publicRequests).permitAll()
.anyRequest().authenticated()
.and()
.requiresChannel()
.anyRequest().requiresSecure()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/", false)
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.rememberMe()
.rememberMeServices(rememberMeService())
.and()
.apply(new SpringSocialConfigurer());
}
//////////////////////////////////////////////////////////////////////////
// AUTHENTICATION //
//////////////////////////////////////////////////////////////////////////
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(bCryptPasswordEncoder());
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder(11);
}
#Bean
public UserDetailsService userDetailsService() {
return new UserRepositoryUserDetailsService(userRepository);
}
#Bean
public SocialUserDetailsService socialUserDetailsService() {
return new UserRepositorySocialUserDetailsService(userDetailsService());
}
//////////////////////////////////////////////////////////////////////////
// REMEMBER ME //
//////////////////////////////////////////////////////////////////////////
#Bean
public JdbcTokenRepositoryImpl jdbcTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
jdbcTokenRepository.setCreateTableOnStartup(Boolean.valueOf(createTables));
return jdbcTokenRepository;
}
#Bean
public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
return new RememberMeAuthenticationProvider(secret);
}
#Bean
public PersistentTokenBasedRememberMeServices rememberMeService() {
PersistentTokenBasedRememberMeServices service =
new PersistentTokenBasedRememberMeServices(secret, userDetailsService(), jdbcTokenRepository());
service.setUseSecureCookie(true);
service.setParameter("rememberme");
service.setTokenValiditySeconds(AbstractRememberMeServices.TWO_WEEKS_S);
return service;
}
#Bean
public RememberMeAuthenticationFilter authenticationFilter() throws Exception {
return new RememberMeAuthenticationFilter(authenticationManager(), rememberMeService());
}
}
in my spring boot configt at the moment related to thymeleaf, and for development purposes
spring.thymeleaf.cache=false
and thymeleaf templates look like this (my login page at the moment, will include only the relevant content for the sake of clarity)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security/"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="thymeleaf/layouts/default">
<head>
... css and meta tags ...
</head>
<body>
... some html ...
<th:block sec:authorize="isAnonymous()">
<!-- Bad Credentials -->
<div th:if="${param.error}" class="alert alert-danger text-center">
Invalid username and/or password.
</div>
<!-- Logout -->
<div th:if="${param.logout}" class="alert alert-success text-center">
You have been logged out.
</div>
<!-- Login Form -->
<form id="f" th:action="#{/login}" method="post" role="form" autocomplete="off">
<!-- Username -->
<input type="text" class="form-control text-center" id="username" name="username" th:placeholder="#{form.login.username}" />
<!-- Password -->
<input type="password" class="form-control text-center" id="password" name="password" th:placeholder="#{form.login.password}" />
<!-- Remember me -->
<input type="checkbox" id="rememberme" name="rememberme" />
<!-- Submit -->
<button type="submit" class="btn btn-primary" th:utext="#{form.login.submit}">Login</button>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</form>
... more html and javascript ...
</body>
</html>
Edit2 - after doing some debugging in the direction Faraj Farook pointed, i found out that, in a project with the configuration i posted, in the Spring Boot version, in this class org.thymeleaf.spring4.requestdata.RequestDataValueProcessor4Delegate, the following function returns a null processor
public Map<String, String> getExtraHiddenFields(
final RequestContext requestContext, final HttpServletRequest request) {
final RequestDataValueProcessor processor = requestContext.getRequestDataValueProcessor();
if (processor == null) {
return null;
}
return processor.getExtraHiddenFields(request);
}
whereas the non Spring boot version, it returns a processor which is an instance of org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor.

I had a similar issue. After some investigation I've found out that only forms that were using 'th:action' attribute (not plain 'action') had the csrf token injected.
For login forms it seems that you need to inject the csrf manually (link).
In the official spring docs (link) there is a suggestion to retrieve the csrf token just before login form submission to prevent session timeouts. In this scenario there would be no csrf token in hidden input on the form.

Using Spring Boot + Thymeleaf + Spring Security it worked with this:
Application Properties
security.enable-csrf=true
Update 30/03/2017:
One important thing is: use th:action inside your form, this will tell the Spring Security to inject CSRF inside the form without the need of manual insertion.
For manual insertion:
html template
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
Update 25/01/2017:
pom.xml
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>

According to the Thymeleaf developers, RequestDataValueProcessor interface is used by Thymeleaf to find the extra hidden fields which is automatically added to the form post back.
The below code in org/thymeleaf/spring3/processor/attr/SpringActionAttrProcessor.java shows this.
final Map<String,String> extraHiddenFields =
RequestDataValueProcessorUtils.getExtraHiddenFields(arguments.getConfiguration(), arguments);
To sort the issue, and automatically add the CSRF Token; In your application create a custom request data value processor and register it with spring. To do this, you may go through the tutorial below.
Csrf Defense in Spring-MVC
I also suggest you to check your previous spring MVC code without the spring boot, to confirm that project's configuration XML has a custom made RequestDataValueProcessor or not.

You'll have to do 2 things. Declare a bean
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
... other beans ...
#Bean
public RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
}
Make sure the html form in your themeleaf template uses "th:action"
<form th:action="#{/youractionurl}">
... input tags
</form>
This automatically inserts _csrf token like this
<input type="hidden" name="_csrf" value="4568ad84-b300-48c4-9532-a9dcb58366f3" />

Related

Null Pointer while trying to get Principal User with Okta

After logging in on website using Okta for authentication I go to the home page and it gives a null pointer exception when I am trying to grab the logged in User Principal.
Here is my Security Dependencies: (I am using Spring boot 1.5.6)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.okta.spring</groupId>
<artifactId>okta-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
Here is my controller:
#GetMapping("/")
public String home(Model model, Principal principal) {
System.out.println(principal.getName());
return "/home";
}
Any help to point me in the right direction would be great. I have looked at the documentation and it seems I am not doing things incorrectly.

Jersey 1.19 Grizzly 2.3.25 and Umlaute in URL - JUnit tests does not work while browser access does

In a RESTFul Jersey solution I am trying to receive URLs that contain German Umlaut characters e.g.
http://localhost:8085/hello/hello/echo/ÄÖÜßööü
When I enter such URLs with a browser I get correct results.
When I test things with a JUnit test it doesn't work.
org.junit.ComparisonFailure: expected:<[ÄÖÜßäöü]> but was:<[ÃÃÃÃäöü]>
at org.junit.Assert.assertEquals(Assert.java:115)
at org.junit.Assert.assertEquals(Assert.java:144)
at com.bitplan.rest.test.TestHello.testUmlaute(TestHello.java:43)
the debug output is:
http://localhost:8085/hello/hello/echo/%C3%84%C3%96%C3%9C%C3%9F%C3%A4%C3%B6%C3%BC
http://localhost:8085/hello/hello/echo/ÄÖÜßäöü
GET:hello/echo/ÃÃÃÃäöü
and so it seems the url is not handled correctly by Grizzly/Jersey.
How can I fix this or work around it?
I found: https://github.com/javaee/grizzly/issues/1377 and tried different encodings e.g. as proposed in https://stackoverflow.com/a/9542781/1497139 but that didn't make any difference.
I also set
server.getServerConfiguration().setDefaultQueryEncoding(Charsets.UTF8_CHARSET);
as outlined in https://github.com/javaee/grizzly/issues/1450 but still no luck
JUnit Test
see https://github.com/BITPlan/com.bitplan.simplerest/blob/master/src/test/java/com/bitplan/rest/test/TestHello.java#L42
#Test
public void testUmlaute() throws Exception {
super.startServer();
URI uri=new URI("http://localhost:8085/hello/hello/echo/ÄÖÜßäöü");
System.out.println(uri.toASCIIString());
System.out.println(uri.toString());
WebResource wrs = Client.create().resource(uri);
String result= wrs.accept("text/html; charset=utf-8").get(String.class);
assertEquals("ÄÖÜßäöü",result);
}
"Hello/Echo" Resource
package com.bitplan.hello.resources;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.UriInfo;
#Path("/hello")
/**
* Simple Hello Resource
* #author wf
*
*/
public class HelloResource {
#Context
UriInfo uriInfo;
#Context
Request request;
#GET
public String getHello() {
return "Hello";
}
#GET
#Produces("text/html")
#Path("echo/{value}")
public String getEcho(#PathParam("value") String value) {
System.out.println(request.getMethod()+":"+uriInfo.getPath());
return value;
}
}
maven dependencies
<properties>
<!-- jersey version -->
<!-- <jersey.version>2.22.2</jersey.version> -->
<jersey.version>1.19.1</jersey.version>
<!-- http://grizzly.java.net/ -->
<grizzly.version>2.3.25</grizzly.version>
...
</properties>
...
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- Grizzly server -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-grizzly2</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-grizzly2-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-server</artifactId>
<version>${grizzly.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-servlet</artifactId>
<version>${grizzly.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http</artifactId>
<version>${grizzly.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.servlet</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>

Keycloak redirects me to my index url instead of to the requested one

I'm using Keycloak server (v 1.5.1) to perform an open-id-connect like authentication to my service. I've set up a basic web application which has two urls, the /index.html one and other one called /hello. I use Spring security, Spring boot and Spring MVC for all of that. That's my pom.xml configuration:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.keycloaktes</groupId>
<artifactId>keycloaktes</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
<version>1.5.1.Final</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat8-adapter</artifactId>
<version>1.5.1.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The issue comes when I address to /hello url when not logged in, the keycloak login screen shows properly, but instead of performing a redirection to /hello after successful login, it does it to my /index.html page. That's how I've configured the security adapter:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(
new SessionRegistryImpl());
}
#Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(
filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(
filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests().antMatchers("/*").hasRole("ADMIN")
.anyRequest().permitAll();
http.csrf().disable();
}
}
I've been trying enabling both the KeycloakAuthenticationProcessingFilter and KeycloakPreAuthActionsFilter, but result keeps the same. Does anybody know how to solve the issue?
You have to permit access to the SSO login entrypoint.
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/sso/login*").permitAll()
.antMatchers("/*").hasRole("ADMIN")
.anyRequest().permitAll();
http.csrf().disable();
}
Spring Security roles are evaluated in the order they're declared so permitting access to /sso/login must come before more restrictive rules.

primefaces graphicImage f:param doesn't work [duplicate]

This question already has answers here:
Display dynamic image from database or remote source with p:graphicImage and StreamedContent
(4 answers)
Closed 7 years ago.
I have JSF:
<p:graphicImage value="#{chatView.userPhoto}">
<f:param name="userId" value="#{user.id}"/>
</p:graphicImage>
user.id is valid I have checked.
And bean:
#ManagedBean
#ViewScoped
public class ChatView extends BaseView {
/*
* Injecting managed beans in each other
* http://balusc.blogspot.com/2011/09/communication-in-jsf-20.html#InjectingManagedBeansInEachOther
*/
#ManagedProperty("#{chatUsers}")
private ChatUsers users;
public StreamedContent getUserPhoto() {
// there output is {}
System.out.println(FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap());
return new DefaultStreamedContent();
}
}
But I have empty map in debug output for requestParameterMap. Any idea why?
UPDATED:
I created a simple project with such pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>grim</groupId>
<artifactId>grim</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<version.jboss.bom>9.0.0.CR1</version.jboss.bom>
<version.primefaces>5.2</version.primefaces>
<version.atmosphere>2.3.1</version.atmosphere>
<version.jboss.logging>3.2.1.Final</version.jboss.logging>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.wildfly.bom</groupId>
<artifactId>jboss-javaee-7.0-wildfly-with-tools</artifactId>
<version>${version.jboss.bom}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- First declare the APIs we depend on and need for compilation. All
of them are provided by JBoss WildFly -->
<!-- Import the CDI API, we use provided scope as the API is included in
JBoss WildFly -->
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- Import the Common Annotations API (JSR-250), we use provided scope
as the API is included in JBoss WildFly -->
<dependency>
<groupId>org.jboss.spec.javax.annotation</groupId>
<artifactId>jboss-annotations-api_1.2_spec</artifactId>
<scope>provided</scope>
</dependency>
<!-- Import the JAX-RS API, we use provided scope as the API is included
in JBoss WildFly -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
<scope>provided</scope>
</dependency>
<!--Hibernate-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<scope>provided</scope>
</dependency>
<!-- Import the JPA API, we use provided scope as the API is included in
JBoss WildFly -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- Import the EJB API, we use provided scope as the API is included in
JBoss WildFly -->
<dependency>
<groupId>org.jboss.spec.javax.ejb</groupId>
<artifactId>jboss-ejb-api_3.2_spec</artifactId>
<scope>provided</scope>
</dependency>
<!-- JSR-303 (Bean Validation) Implementation -->
<!-- Provides portable constraints such as #Email -->
<!-- Hibernate Validator is shipped in JBoss WildFly -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Import the JSF API, we use provided scope as the API is included in
JBoss WildFly -->
<dependency>
<groupId>org.jboss.spec.javax.faces</groupId>
<artifactId>jboss-jsf-api_2.2_spec</artifactId>
<scope>provided</scope>
</dependency>
<!-- Now we declare any tools needed -->
<!-- Annotation processor to generate the JPA 2.0 metamodel classes for
typesafe criteria queries -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<scope>provided</scope>
</dependency>
<!-- Annotation processor that raising compilation errors whenever constraint
annotations are incorrectly used. -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<scope>provided</scope>
</dependency>
<!-- Needed for running tests (you may also use TestNG) -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- Optional, but highly recommended -->
<!-- Arquillian allows you to test enterprise code such as EJBs and Transactional(JTA)
JPA from JUnit/TestNG -->
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.protocol</groupId>
<artifactId>arquillian-protocol-servlet</artifactId>
<scope>test</scope>
</dependency>
<!-- Import the Servlet API, we use provided scope as the API is included in JBoss WildFly. -->
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
<scope>provided</scope>
</dependency>
<!--PrimeFaces-->
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>${version.primefaces}</version>
</dependency>
<!--Atmosphere-->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-runtime</artifactId>
<version>${version.atmosphere}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!--Jboss Logging-->
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${version.jboss.logging}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
View:
#ManagedBean
#ViewScoped
public class TestView {
public StreamedContent getImg() {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
}
else {
// So, browser is requesting the image. Return a real StreamedContent with the image bytes.
String studentId = context.getExternalContext().getRequestParameterMap().get("shashistId");
Shashist student = shashistService.find(Long.valueOf(studentId));
return new DefaultStreamedContent(new ByteArrayInputStream(student.getPhoto()));
}
}
}
And xhtml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<f:view>
<h:outputLabel value="Hello, world"/>
<p:graphicImage value="#{testView.img}">
<f:param value="1" name="s"/>
</p:graphicImage>
</f:view>
</html>
But anyway I get empty request parameter map in debug output.
Thank to this answer https://stackoverflow.com/a/25425111/836701 I found the solution. The problem was in #ViewScope it should be #SessionScope for getting parameters from f:param.

Struts 2 submit form to one object

I have something similar to that:
class Model {
private String field1;
private String field2;
//setters
}
class Action extends ActionSupport {
private Model model;
public String execute() {
//breakpoint
}
public void setModel(Model model){
this.model=model;
}
}
on jsp:
<s:form id="addCommentForm" method="POST" namespace="%{namespace}" action="addComment">
<input type="text" name="model.field1"/>
<input type="text" name="model.field2"/>
</s:form>
When I submit this form unfortunately only 1 field in Model class is set.
I debug code and find that actually setters are called for both fields (field1 and field2), but for different instances of Model class.
So it appears, that it executes with next steps when form is submitteed:
create new instance (instance1) of Model class, set this instance in Action class
set field1 to instance1
create new instance (instance2) of Model class, set this instance in Action class
set field2 to instance2
So as I see instanse2 replace instance1.
I need field1 and field2 in one instance of Model class.
What need to be modified?
list of dependenced:
<dependency>
<groupId>com.vercer.engine.persist</groupId>
<artifactId>twig-persist</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>com.opensymphony</groupId>
<artifactId>xwork</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.struts.xwork</groupId>
<artifactId>xwork-core</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.19</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>opensymphony</groupId>
<artifactId>sitemesh</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>jetty</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1-6.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-spring-plugin</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-json-plugin</artifactId>
<version>2.1.8</version>
</dependency>
For complex types you need both a getter and setter so Struts2 can manipulate the object correctly, otherwise it will not be able to get the existing instance and will be forced to create a new instance of Model (new Model().setWhatever()) as opposed to seeing a model already exists and doing (getModel().setWhatever()).

Resources