I've followed some examples for redirecting in SWF and every do it equal. In my case the link don't redirect, execution reference don't change and url has changed to href's value. If I set it in a commandButton then the result is succes.
I use JSF 2.2 and SWF 2.3 on glassfish 4. Any suggestions> Thanks!
<a href="${flowExecutionUrl}&_eventId=TRANSITION_NAME" >
My link
<a href="${flowExecutionUrl}&_eventId=visit" >
URL result
http://localhost:8080/aio/spring/adminSpace?execution=e4s3&_eventId=visit
Flow definition
<view-state id="showEvent" view="list_events.xhtml">
<secured attributes="ROLE_MEMBER" />
<on-entry>
<evaluate expression="eventProvider.showEvents()" result="viewScope.pagesList"/>
</on-entry>
<transition on="pageCreated" to="newEvent" />
<transition on="visit" to="visitEvent"/>
</view-state>
<view-state id="newEvent" view="newEvent_1.xhtml">
<secured attributes="ROLE_MEMBER" />
<on-render>
<set name="pictureProvider.idRequest" value="'event'" />
</on-render>
<transition on="back" to="showEvent" />
<transition on="save" to="showEvent" />
<on-exit>
<evaluate expression="eventProvider.deleteTempComponents()" />
</on-exit>
</view-state>
<view-state id="visitEvent" view="event.xhtml">
</view-state>
WebFlowConfig class
#Configuration
public class WebFlowConfig extends AbstractFacesFlowConfiguration {
#Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry())
.addFlowExecutionListener(new FlowFacesContextLifecycleListener())
.addFlowExecutionListener(new SecurityFlowExecutionListener())
.build();
}
#Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder(flowBuilderServices())
.setBasePath("/WEB-INF/flows")
.addFlowLocationPattern("/**/*-flow.xml")
.build();
}
#Bean
public FlowBuilderServices flowBuilderServices() {
return getFlowBuilderServicesBuilder()
//.setViewFactoryCreator(new JsfViewFactoryCreator())
.setDevelopmentMode(true).build();
}
#Bean
public CustomScopeConfigurer customScopeConfigurer() {
Map<String, Object> scopes = new HashMap<String, Object>();
scopes.put("view", new ViewScope());
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.setScopes(scopes);
return configurer;
}
}
WebMvcConfig class
#EnableWebMvc
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
#Autowired
private WebFlowConfig webFlowConfig;
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("classpath:/resources/");
}
#Bean
public FlowHandlerMapping flowHandlerMapping() {
FlowHandlerMapping mapping = new FlowHandlerMapping();
mapping.setOrder(1);
mapping.setFlowRegistry(this.webFlowConfig.flowRegistry());
/* If no flow matches, map the path to a view, e.g. "/intro" maps to a view named "intro" */
mapping.setDefaultHandler(new UrlFilenameViewController());
return mapping;
}
#Bean
public FlowHandlerAdapter flowHandlerAdapter() {
JsfFlowHandlerAdapter adapter = new JsfFlowHandlerAdapter();
adapter.setFlowExecutor(this.webFlowConfig.flowExecutor());
return adapter;
}
#Bean
public UrlBasedViewResolver faceletsViewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setViewClass(JsfView.class);
resolver.setPrefix("/WEB-INF/");
resolver.setSuffix(".xhtml");
return resolver;
}
#Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
#Bean
public SessionLocaleResolver localeResolver(){
SessionLocaleResolver localeResolver=new SessionLocaleResolver();
localeResolver.setDefaultLocale(new Locale("es"));
return localeResolver;
}
#Bean
public ReloadableResourceBundleMessageSource messageSource(){
ReloadableResourceBundleMessageSource msg=new ReloadableResourceBundleMessageSource();
msg.setBasename("classpath*:error");
return msg;
}
#Bean
public StandardServletMultipartResolver multipartResolver(){
return new StandardServletMultipartResolver();
}
/*
#Bean
public LocaleChangeInterceptor localeChangeInterceptor(){
LocaleChangeInterceptor changeInterceptor=new LocaleChangeInterceptor();
changeInterceptor.setParamName("locale");
return changeInterceptor;
}
#Bean
public ControllerClassNameHandlerMapping nameHandlerMapping(){
ControllerClassNameHandlerMapping controller=new ControllerClassNameHandlerMapping();
Object[] obj={localeChangeInterceptor()};
controller.setInterceptors(obj);
return controller;
}*/
}
Related
i have an issue with my project, i'm using spring , jsf and primefaces, the problem is when i wish to call a method with ajax request, i can't and i have no error message to help me found the problem this is my first configuration with spring and jsf with annotations.
help me please .
my configuration:
#Configuration
#ComponentScan
#EnableTransactionManagement
public class AppConfig {
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306 /carouzetDb?useUnicode=true&useJDBCCompliantTimezoneShift=true& useLegacyDatetimeCode=false&serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
private Properties hibernateProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty(
"hibernate.hbm2ddl.auto", "create");
hibernateProperties.setProperty(
"hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return hibernateProperties;
}
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan("com.crouzet.carouzetroject.entities");
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public PlatformTransactionManager hibernateTransactionManager() {
HibernateTransactionManager transactionManager
= new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
}
public class MvcContentInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
servletContext.addListener(new ContextLoaderListener(ctx));
}
}
jsf configuration :
faces-config.xml
<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
<application>
<el-resolver>
org.springframework.web.jsf.el.SpringBeanFacesELResolver
</el-resolver>
</application>
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.FileUploadRenderer</renderer-type>
<renderer-class>com.crouzet.carouzetroject.FileUploadRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
and here is the example where i call my method:
#ManagedBean(name = "helloBean")
#ViewScoped
#Component
public class HelloBean {
List<Salarie> salaries;
List<Categorie> categories;
List<Poste> postes;
Production production;
Categorie selectedCat;
#Autowired
private CategorieDAO categorieDAO;
#Autowired
private SalarieDAO salarieDAO;
#PostConstruct
public void init(){
categories=categorieDAO.allCategories();
System.out.println("==========>>>FROM INIT"+categories.size());
}
public void actualiser(){
categories=categorieDAO.allCategories();
}
public void listeDesSalaries(){
System.out.println("========AAAAAAAAAAAAAA========>>>>");
if(selectedCat!=null)
salaries=salarieDAO.salariesParCategorie(selectedCat);
}
public List<Salarie> getSalaries() {
return salaries;
}
public void setSalaries(List<Salarie> salaries) {
this.salaries = salaries;
}
public List<Categorie> getCategories() {
return categories;
}
public void setCategories(List<Categorie> categories) {
this.categories = categories;
}
public List<Poste> getPostes() {
return postes;
}
public void setPostes(List<Poste> postes) {
this.postes = postes;
}
public Production getProduction() {
return production;
}
public void setProduction(Production production) {
this.production = production;
}
public Categorie getSelectedCat() {
return selectedCat;
}
public void setSelectedCat(Categorie selectedCat) {
this.selectedCat = selectedCat;
}
}
and here is my page
<h:form>
<p:commandButton action="#{helloBean.actualiser()}" value="Actualiser" update="mesListes"/>
<p:panelGrid id="mesListes" columns="2">
<p:outputLabel value="Ligne de fabrication"/>
<p:selectOneMenu id="categori" value="#{helloBean.selectedCat}">
<f:selectItem itemLabel="Select One" itemValue="" />
<f:selectItems value="#{helloBean.categories}" var="cat" itemLabel="#{cat.nom}" itemValue="#{cat}" />
<p:ajax listener="#{helloBean.listeDesSalaries()}" />
</p:selectOneMenu>
</p:panelGrid>
</h:form>
I'm having a hard time trying to get Spring Security SAML to work with ADFS 2.0.
Based on my current configuration, the generated AuthnRequest looks like this:-
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO"
Destination="https://server/adfs/ls/"
ForceAuthn="false" ID="a14edaf38ih92bi8acji5a1664a80e"
IsPassive="false" IssueInstant="2016-02-15T21:05:57.980Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
<saml2p:Scoping ProxyCount="2"/>
</saml2p:AuthnRequest>
However, it causes an error on ADFS side:-
Microsoft.IdentityModel.Protocols.XmlSignature.SignatureVerificationFailedException: MSIS0038: SAML Message has wrong signature. Issuer: 'https://localhost:8443/helix/saml/metadata'.
at Microsoft.IdentityServer.Protocols.Saml.Contract.SamlContractUtility.CreateSamlMessage(MSISSamlBindingMessage message)
at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.CreateErrorMessage(CreateErrorMessageRequest createErrorMessageRequest)
at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.ProcessRequest(Message requestMessage)
I was told by my security team that my AuthnRequest should look something like this:-
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO" Destination="https://server/adfs/ls/" ID="_e082771303738e4e6872e8d5711446d4" IssueInstant="2016-02-15T19:51:50.627Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#_e082771303738e4e6872e8d5711446d4">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>S8r/XbIhlFGFSMfLoSt/7IlksiI=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>TT4n3==...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIC8z...</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<saml2p:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
<saml2p:RequestedAuthnContext Comparison="exact">
<saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
</saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>
... but, I'm having trouble trying to make the <Signature> block to appear in my AuthnRequest.
My current Spring Security SAML configuration looks like this... I apologize it is rather verbose, but I'm unsure what to paste here besides the whole configuration.
#Configuration
#EnableWebSecurity
public abstract class SecuritySAMLConfig extends WebSecurityConfigurerAdapter {
private static final String METADATA_URL = "https://server/federationmetadata/2007-06/federationmetadata.xml";
private static final String ALIAS = "apollo";
private static final String STORE_PASS = "secret";
#Autowired
private SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
#Autowired
private SAMLAuthenticationProvider samlAuthenticationProvider;
#Bean
public static SAMLBootstrap SAMLBootstrap() {
return new CustomSamlBootstrap();
}
#Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
#Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
#Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
#Bean
public MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() {
return new MultiThreadedHttpConnectionManager();
}
#Bean
public HttpClient httpClient(MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager) {
return new HttpClient(multiThreadedHttpConnectionManager);
}
#Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
#Bean
public SAMLContextProviderImpl contextProvider() {
return new SAMLContextProviderImpl();
}
#Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
#Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
#Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
#Bean
public WebSSOProfileECPImpl ecpprofile() {
return new WebSSOProfileECPImpl();
}
#Bean
public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl();
}
#Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader.getResource("classpath:keystore.jks");
Map<String, String> passwords = new HashMap<>();
passwords.put(ALIAS, STORE_PASS);
return new JKSKeyManager(storeFile, STORE_PASS, passwords, ALIAS);
}
#Bean
public WebSSOProfileOptions webSSOProfileOptions() {
return new WebSSOProfileOptions();
}
#Bean
public SAMLEntryPoint samlEntryPoint(WebSSOProfileOptions webSSOProfileOptions) {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions);
return samlEntryPoint;
}
#Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
// #######
// ####### In theory, by setting these keys, the signature block should appear,
// ####### but, that didn't work for me
// #######
extendedMetadata.setSignMetadata(true);
extendedMetadata.setSigningKey(ALIAS);
extendedMetadata.setEncryptionKey(ALIAS);
// #######
return extendedMetadata;
}
#Bean
public ExtendedMetadataDelegate extendedMetadataDelegate(HttpClient httpClient,
ParserPool parserPool,
ExtendedMetadata extendedMetadata) throws MetadataProviderException {
Timer backgroundTaskTimer = new Timer(true);
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(backgroundTaskTimer,
httpClient,
METADATA_URL);
httpMetadataProvider.setParserPool(parserPool);
ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider,
extendedMetadata);
extendedMetadataDelegate.setMetadataTrustCheck(false);
return extendedMetadataDelegate;
}
#Bean
public CachingMetadataManager metadata(ExtendedMetadataDelegate extendedMetadataDelegate) throws MetadataProviderException {
List<MetadataProvider> providers = new ArrayList<>();
providers.add(extendedMetadataDelegate);
return new CachingMetadataManager(providers);
}
#Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setExtendedMetadata(extendedMetadata);
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager);
return metadataGenerator;
}
#Bean
public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter();
}
#Bean
public SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
#Bean
public SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl("/error");
return failureHandler;
}
#Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter(SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
AuthenticationManager authenticationManager,
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager);
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
return samlWebSSOHoKProcessingFilter;
}
#Bean
public SAMLProcessingFilter samlProcessingFilter(AuthenticationManager authenticationManager,
SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager);
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
return samlWebSSOProcessingFilter;
}
#Bean
public MetadataGeneratorFilter metadataGeneratorFilter(MetadataGenerator metadataGenerator) {
return new MetadataGeneratorFilter(metadataGenerator);
}
#Bean
public SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl("/");
return successLogoutHandler;
}
#Bean
public SecurityContextLogoutHandler securityContextLogoutHandler() {
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
#Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
SecurityContextLogoutHandler securityContextLogoutHandler) {
return new SAMLLogoutProcessingFilter(simpleUrlLogoutSuccessHandler, securityContextLogoutHandler);
}
#Bean
public SAMLLogoutFilter samlLogoutFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
SecurityContextLogoutHandler securityContextLogoutHandler) {
return new SAMLLogoutFilter(simpleUrlLogoutSuccessHandler,
new LogoutHandler[]{securityContextLogoutHandler},
new LogoutHandler[]{securityContextLogoutHandler});
}
#Bean
public HTTPArtifactBinding artifactBinding(HTTPSOAP11Binding httpSOAP11Binding,
HttpClient httpClient,
ParserPool parserPool,
VelocityEngine velocityEngine) {
ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient);
artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(httpSOAP11Binding));
return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile);
}
#Bean
public HTTPSOAP11Binding httpSOAP11Binding(ParserPool parserPool) {
return new HTTPSOAP11Binding(parserPool);
}
#Bean
public HTTPPostBinding httpPostBinding(ParserPool parserPool, VelocityEngine velocityEngine) {
return new HTTPPostBinding(parserPool, velocityEngine);
}
#Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding(ParserPool parserPool) {
return new HTTPRedirectDeflateBinding(parserPool);
}
#Bean
public HTTPPAOS11Binding httpPAOS11Binding(ParserPool parserPool) {
return new HTTPPAOS11Binding(parserPool);
}
#Bean
public SAMLProcessorImpl processor(HTTPRedirectDeflateBinding httpRedirectDeflateBinding,
HTTPPostBinding httpPostBinding,
HTTPArtifactBinding httpArtifactBinding,
HTTPSOAP11Binding httpSOAP11Binding,
HTTPPAOS11Binding httpPAOS11Binding) {
Collection<SAMLBinding> bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding);
bindings.add(httpPostBinding);
bindings.add(httpArtifactBinding);
bindings.add(httpSOAP11Binding);
bindings.add(httpPAOS11Binding);
return new SAMLProcessorImpl(bindings);
}
#Bean
public FilterChainProxy filterChainProxy(SAMLEntryPoint samlEntryPoint,
SAMLLogoutFilter samlLogoutFilter,
MetadataDisplayFilter metadataDisplayFilter,
SAMLProcessingFilter samlProcessingFilter,
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter,
SAMLLogoutProcessingFilter samlLogoutProcessingFilter) throws Exception {
List<SecurityFilterChain> chains = new ArrayList<>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
metadataDisplayFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), samlProcessingFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
samlWebSSOHoKProcessingFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter));
return new FilterChainProxy(chains);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(samlAuthenticationProvider);
}
}
I highly appreciate it if someone could nudge me towards the right direction.
Thank you.
UPDATE
My SP metadata generated by MetadataGenerator looks like this:-
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="https___localhost_8443_helix_saml_metadata" entityID="https://localhost:8443/helix/saml/metadata">
<md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIICx....</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIICxz....</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SingleLogout"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://localhost:8443/helix/saml/SingleLogout"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SSO" index="0" isDefault="true"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://localhost:8443/helix/saml/SSO" index="1"/>
</md:SPSSODescriptor>
</md:EntityDescriptor>
I also want to point out that I'm using SHA256withRSA instead of SHA1withRSA. Hence, I replace the default SAMLBootstrap bean with the below:-
public final class CustomSamlBootstrap extends SAMLBootstrap {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
super.postProcessBeanFactory(beanFactory);
BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
}
}
UPDATE 2
I got it working by modifying WebSSOProfileOptions.
#Bean
public WebSSOProfileOptions webSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
// Added this line
webSSOProfileOptions.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
return webSSOProfileOptions;
}
What binding do you use to send the Request to the IDP? In case it's HTTP-Request the SAML standard mandates that he Signature is removed before delivering the message. The signature is then performed on the serialized request and sent as a GET parameter.
I believe you should have set:
#Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) {
MetadataGenerator metadataGenerator = new MetadataGenerator();
// ... other configuration here
metadataGenerator.setRequestSigned ( true );
return metadataGenerator;
}
in ExtendedMetadata, you were only setting Metadata file to be signed. This doesn't affect signing of AuthnRequest.
I have a flow which seemed to be working fine until yesterday, when suddenly I started getting the following exception in my HTML page that maps to the first state in my flow:
org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'flowScope' cannot be found on null
The offending line of code was:
<h3 th:text="${flowRequestContext.flowScope}"/>
Further investigation showed that none of the flow variables are available anymore. Furthermore if I put print statements into the Service which the flow makes various calls to, I can see that none of these methods are being called anymore - it's like the flow just isn't running at all.
This was working fine previously. I even reverted all of my local changes to a previously stable version of the code, and the same issue was happening there as well. The only thing that seemed to temporarily get around the problem was to restart my computer - the problem disappeared for a short while but then came back.
To be honest I'm completely out of ideas as to what could have started causing such an intermittent problem. I was thinking along the lines of a stale Java process running in the background interfering with future runs of the application, but have checked for and killed off any remaining process in between deploys to no avail.
I have included what I hope are the relevant file below. Any help resolving this issue would be very much appreciated.
checkout.xml
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<on-start>
<set name="flowScope.paymentMethods" value="checkoutWidgetService.getPaymentMethods()"/>
<set name="flowScope.deliveryAddress" value="checkoutWidgetService.getDeliveryAddress()"/>
<set name="flowScope.sessionId" value="externalContext.nativeRequest.session.id"/>
</on-start>
<view-state id="payment-methods" view="payment-methods">
<transition on="selectPaymentMethod" to="new-details">
<evaluate expression="checkoutWidgetService.getCardDetails(requestParameters.type)" result="flowScope.cardDetails"/>
</transition>
</view-state>
<view-state id="new-details" view="new-details">
<transition on="submitDetails" to="summary">
<evaluate expression="checkoutWidgetService.buildCardDetails(requestParameters)" result="flowScope.cardDetails"/>
</transition>
</view-state>
<view-state id="summary" view="summary">
<transition on="completeCheckout" to="redirect">
<evaluate expression="checkoutWidgetService.completeCheckout(externalContext.nativeRequest.session, flowRequestContext, flowScope.cardDetails)"/>
</transition>
<transition on="cancelCheckout" to="redirect">
<evaluate expression="checkoutWidgetService.cancelCheckout(externalContext.nativeRequest.session, flowRequestContext)"/>
</transition>
</view-state>
<end-state id="redirect" view="externalRedirect:contextRelative:/payments/checkout-widgets/end"/>
</flow>
WebflowConfig.java
#Configuration
#AutoConfigureAfter(MvcConfig.class)
public class WebflowConfig extends AbstractFlowConfiguration {
#Autowired
private SpringTemplateEngine templateEngine;
#Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry())
.addFlowExecutionListener(new SecurityFlowExecutionListener())
.build();
}
#Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder(flowBuilderServices())
.addFlowLocation("classpath:/templates/checkout.xml", "payments/checkout-widget/start")
.build();
}
#Bean
public FlowBuilderServices flowBuilderServices() {
return getFlowBuilderServicesBuilder()
.setViewFactoryCreator(mvcViewFactoryCreator())
.setDevelopmentMode(true)
.build();
}
#Bean
public FlowController flowController() {
FlowController flowController = new FlowController();
flowController.setFlowExecutor(flowExecutor());
return flowController;
}
#Bean
public FlowHandlerMapping flowHandlerMapping() {
FlowHandlerMapping flowHandlerMapping = new FlowHandlerMapping();
flowHandlerMapping.setFlowRegistry(flowRegistry());
flowHandlerMapping.setOrder(-1);
return flowHandlerMapping;
}
#Bean
public FlowHandlerAdapter flowHandlerAdapter() {
FlowHandlerAdapter flowHandlerAdapter = new FlowHandlerAdapter();
flowHandlerAdapter.setFlowExecutor(flowExecutor());
flowHandlerAdapter.setSaveOutputToFlashScopeOnRedirect(true);
return flowHandlerAdapter;
}
#Bean
public AjaxThymeleafViewResolver thymeleafViewResolver() {
AjaxThymeleafViewResolver viewResolver = new AjaxThymeleafViewResolver();
viewResolver.setViewClass(FlowAjaxThymeleafView.class);
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
#Bean
public MvcViewFactoryCreator mvcViewFactoryCreator() {
List<ViewResolver> viewResolvers = new ArrayList<>();
viewResolvers.add(thymeleafViewResolver());
MvcViewFactoryCreator mvcViewFactoryCreator = new MvcViewFactoryCreator();
mvcViewFactoryCreator.setViewResolvers(viewResolvers);
mvcViewFactoryCreator.setUseSpringBeanBinding(true);
return mvcViewFactoryCreator;
}
}
CheckoutWidgetSessionMvcController.java
#Controller
#RequestMapping("/payments/checkout-widgets")
public class CheckoutWidgetSessionMvcController {
#Inject
private CheckoutWidgetService service;
#RequestMapping(value = {"/start"}, method = RequestMethod.GET)
public ModelAndView paymentMethods() {
return new ModelAndView("payment-methods", null);
}
#RequestMapping(value = "/end", method = RequestMethod.GET)
public String invalidateSession(HttpSession session) {
service.invalidateSession(session);
return "dummy-redirect-post";
}
}
CheckoutWidgetService.java
public interface CheckoutWidgetService {
List<PaymentMethod> getPaymentMethods();
CardDetails getCardDetails(String name);
CardDetails buildCardDetails(LocalParameterMap params);
String getDeliveryAddress();
void completeCheckout(HttpSession session, RequestContext context, CardDetails cardDetails);
void cancelCheckout(HttpSession session, RequestContext context);
void invalidateSession(HttpSession session);
}
CheckoutWidgetServiceImpl.java
#Service("checkoutWidgetService")
public class CheckoutWidgetServiceImpl implements CheckoutWidgetService {
#Inject
private CheckoutWidgetSessionService sessionService;
private final List<PaymentMethod> paymentMethods = new ArrayList<>();
private final String deliveryAddress;
public CheckoutWidgetServiceImpl() {
paymentMethods.add(new PaymentMethod("PayPal", "/images/paypal-logo.png"));
paymentMethods.add(new PaymentMethod("Mastercard", "/images/mc-logo.png"));
paymentMethods.add(new PaymentMethod("Visa", "/images/visa-logo.png"));
paymentMethods.add(new PaymentMethod("Amex", "/images/amex-logo.png"));
paymentMethods.add(new PaymentMethod("Google Checkout", "/images/google-logo.png"));
deliveryAddress = "xxxxx";
}
#Override
public List<PaymentMethod> getPaymentMethods() {
System.out.println("Returning paymentMethods: " + paymentMethods);
return paymentMethods;
}
#Override
public CardDetails getCardDetails(String name) {
CardDetails cardDetails = new CardDetails();
cardDetails.setCardType(name);
return cardDetails;
}
#Override
public CardDetails buildCardDetails(LocalParameterMap params) {
CardDetails cardDetails = new CardDetails();
cardDetails.setCardNumber(params.get("cardNumber"));
cardDetails.setExpiryMonth(params.get("expiryMonth"));
cardDetails.setExpiryYear(params.get("expiryYear"));
cardDetails.setNameOnCard(params.get("nameOnCard"));
cardDetails.setCvv2(params.get("cvv2"));
return cardDetails;
}
#Override
public String getDeliveryAddress() {
return deliveryAddress;
}
#Override
public void invalidateSession(HttpSession session) {
session.invalidate();
}
private RedirectUrls getRedirectUrls(String sessionId) {
CheckoutWidgetSession widgetSession = sessionService.getCheckoutWidgetSession(sessionId).get();
return widgetSession.getRedirectUrls();
}
#Override
public void completeCheckout(HttpSession session, RequestContext context, CardDetails cardDetails) {
RedirectUrls redirects = getRedirectUrls(session.getId());
context.getFlowScope().remove("paymentMethods");
UriBuilder uriBuilder = UriBuilder.fromUri(URI.create(redirects.getSuccessUrl()));
String forwardUrl = uriBuilder.queryParam("transactionId", "12345").toString();
context.getFlowScope().put("forwardUrl", forwardUrl);
context.getFlowScope().put("target", "_top");
}
#Override
public void cancelCheckout(HttpSession session, RequestContext context) {
RedirectUrls redirects = getRedirectUrls(session.getId());
context.getFlowScope().remove("paymentMethods");
String forwardUrl = redirects.getCancelUrl();
context.getFlowScope().put("forwardUrl", forwardUrl);
context.getFlowScope().put("target", "_top");
}
}
Application.java
#EnableAutoConfiguration
#ComponentScan(basePackages = {"com.pay.widgets.checkout"})
#Import(JerseyAutoConfiguration.class)
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Okay this was a really stupid mistake on my part, the problem turned out to be down to a typo in the #RequestParameter annotation on the Controller:
payments/checkout-widgets
Which didn't line up with what was in the WebflowConfig defined flowRegistry:
payments/checkout-widget
I can only assume the resource was cached by Tomcat which is why it took so long for the issue to manifest and threw me off the scent in terms of suspecting my own changes to be responsible.
I have a Spring MVC application with Thymeleaf configured to use fragments (NO tiles!) and all files have .html extension. Everything is working fine.
Now i'm trying to setup Webflow but, when i call my webflow url, i get 404 error as he tries to load a JSP view instead of html (outside flow, everything is fine):
HTTP Status 404 - /app/WEB-INF/views/contest/contest-step1.jsp
I know that put kilometers of line of code isn't good, but honestly i don't know which pieces are interesting and which no.
ThymeleafConfig:
#Configuration
public class ThymeleafConfig {
#Bean
public TemplateResolver templateResolver() {
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
/**
* only on development machine
*/
templateResolver.setCacheable(false);
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
Set<IDialect> dialects = new HashSet<IDialect>();
dialects.add(springSecurityDialect());
templateEngine.setAdditionalDialects(dialects);
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
#Bean
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
return resolver;
}
#Bean
public SpringSecurityDialect springSecurityDialect(){
SpringSecurityDialect dialect = new SpringSecurityDialect();
return dialect;
}
}
WebAppConfig:
#Configuration
#EnableWebMvc
#ComponentScan("com.myapp")
public class WebAppConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
#Bean
public UrlBasedViewResolver setupViewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
// Maps resources path to webapp/resources
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
/**************************************************
*
* Web Flow: wizard contest
*
*/
#Autowired
private WebFlowConfig webFlowConfig;
/**
* Maps request paths to flows in the flowRegistry;
* e.g. a path of /hotels/booking looks for a flow with id "hotels/booking"
*
* Configuring this mapping allows the Dispatcher to map application resource paths
* to flows in a flow registry.
* For example, accessing the resource path /hotels/booking would result in a
* registry query for the flow with id hotels/booking.
* If a flow is found with that id,
* that flow will handle the request. If no flow is found, the next handler mapping in the
* Dispatcher's ordered chain will be queried or a "noHandlerFound" response will be returned.
*/
#Bean
public FlowHandlerMapping flowHandlerMapping() {
FlowHandlerMapping handlerMapping = new FlowHandlerMapping();
handlerMapping.setOrder(-1); //0 ?
handlerMapping.setFlowRegistry(this.webFlowConfig.flowRegistry());
return handlerMapping;
}
#Bean
public FlowHandlerAdapter flowHandlerAdapter() {
FlowHandlerAdapter handlerAdapter = new FlowHandlerAdapter();
handlerAdapter.setFlowExecutor(this.webFlowConfig.flowExecutor());
handlerAdapter.setSaveOutputToFlashScopeOnRedirect(true);
return handlerAdapter;
}
#Bean(name="contest/add") //defined in in WebFlowConfig
public ContestFlowHandler ContestFlowHandler() {
return new ContestFlowHandler();
}
/* TODO
#Bean
public AjaxThymeleafViewResolver tilesViewResolver() {
AjaxThymeleafViewResolver viewResolver = new AjaxThymeleafViewResolver();
viewResolver.setViewClass(FlowAjaxThymeleafTilesView.class);
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
} */
}
WebFlowConfig:
#Configuration
public class WebFlowConfig extends AbstractFlowConfiguration {
#Autowired
private ThymeleafConfig thymeleafConfig;
//private WebFlowConfig WebAppConfig;
#Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder()
.addFlowLocation("/WEB-INF/views/gare/gare-add-flow.xml", "gare/add")
.build();
}
#Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry())
.setMaxFlowExecutions(5)
.setMaxFlowExecutionSnapshots(30)
.build();
}
#Bean
public FlowBuilderServices flowBuilderServices() {
return getFlowBuilderServicesBuilder()
.setViewFactoryCreator(mvcViewFactoryCreator())
.build();
}
#Bean
public MvcViewFactoryCreator mvcViewFactoryCreator() {
MvcViewFactoryCreator factoryCreator = new MvcViewFactoryCreator();
factoryCreator.setViewResolvers(Arrays.<ViewResolver>asList(this.thymeleafConfig.thymeleafViewResolver()));
factoryCreator.setUseSpringBeanBinding(true);
return factoryCreator;
}
}
I found a trick that gives me a quick solution, but honestly i would like don't do this, but correctly configure the resolver. The trick is to set the view file in flow xml file:
<view-state id="contest-step1" model="contest" view="/WEB-INF/views/contest/contest-step1.html"></view-state>
thanks
Try the following in flowRegistry method.
return getFlowDefinitionRegistryBuilder(flowBuilderServices())
instead of
return getFlowDefinitionRegistryBuilder()
Hope this helps.
I am not sure but kindly look into the following part of your code
#Bean
public UrlBasedViewResolver setupViewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
it is pointing to .jsp whatever you call in controller it will append .jsp
Also your controller code is not here url mapping should be like /login.html
I am not much familier with thymeleaf and spring-webflow-2
I'm trying to add a custom DateFormatter to my spring/thymeleaf application, with the help of the following documentation :
http://www.thymeleaf.org/doc/html/Thymeleaf-Spring3.html#conversions-utility-object
The problem is that I don't use xml configuration for my beans definitions, but a WebConfig.java class with the following implementation :
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.myapp.web.controller","net.atos.wfs.wts.adminportal.web.domain"})
public class WebConfig extends WebMvcConfigurerAdapter {
private static final Logger LOG = LoggerFactory.getLogger(WebConfig.class);
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
registry.addInterceptor(localeChangeInterceptor);
}
#Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
cookieLocaleResolver.setDefaultLocale(StringUtils.parseLocaleString("en"));
return cookieLocaleResolver;
}
#Bean
public ServletContextTemplateResolver templateResolver() {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".html");
//NB, selecting HTML5 as the template mode.
resolver.setTemplateMode("HTML5");
resolver.setCacheable(false);
return resolver;
}
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
engine.setMessageResolver(messageResolver());
engine.addDialect(new LayoutDialect());
return engine;
}
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[]{"*"});
viewResolver.setCache(false);
return viewResolver;
}
#Bean
public IMessageResolver messageResolver() {
SpringMessageResolver messageResolver = new SpringMessageResolver();
messageResolver.setMessageSource(messageSource());
return messageResolver;
}
#Override
public void addFormatters(FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatter(new DateFormatter());
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("/WEB-INF/messages/messages");
// if true, the key of the message will be displayed if the key is not
// found, instead of throwing a NoSuchMessageException
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
// # -1 : never reload, 0 always reload
messageSource.setCacheSeconds(0);
return messageSource;
}
}
And here is the code of my Custom DateFormatter :
public class DateFormatter implements Formatter<Date> {
#Autowired
private MessageSource messageSource;
public DateFormatter() {
super();
}
public Date parse(final String text, final Locale locale) throws ParseException {
final SimpleDateFormat dateFormat = createDateFormat(locale);
return dateFormat.parse(text);
}
public String print(final Date object, final Locale locale) {
final SimpleDateFormat dateFormat = createDateFormat(locale);
return dateFormat.format(object);
}
private SimpleDateFormat createDateFormat(final Locale locale) {
//The following line is not working (nullPointerException on messageSource)
//final String format = this.messageSource.getMessage("date.format", null, locale);
//The following line is working :
final String format = "dd/MM/yyyy";
final SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setLenient(false);
return dateFormat;
}
}
My question is : how can I add a custom formatter that will be able to use #Autowired elements ?
The xml configuration is this one :
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
...
<mvc:annotation-driven conversion-service="conversionService" />
...
<!-- **************************************************************** -->
<!-- CONVERSION SERVICE -->
<!-- Standard Spring formatting-enabled implementation -->
<!-- **************************************************************** -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<set>
<bean class="thymeleafexamples.stsm.web.conversion.VarietyFormatter" />
<bean class="thymeleafexamples.stsm.web.conversion.DateFormatter" />
</set>
</property>
</bean>
...
</beans>
I tried to uset the following configuration in WebConfig class :
#Bean
public FormattingConversionServiceFactoryBean conversionService() {
FormattingConversionServiceFactoryBean conversionService = new FormattingConversionServiceFactoryBean();
Set<Formatter<?>> formatters = new TreeSet<Formatter<?>>();
formatters.add(new DateFormatter());
conversionService.setFormatters(formatters);
return conversionService;
}
But in this case, the formatter is not taken into account in my application.
Thanks in advance,
Antoine.
Add this to your WebMvcConfigurerAdapter
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(dateFormatter);
}
#Autowired
private DateFormatter dateFormatter;
#Bean
public DateFormatter dateFormatter() {
return new DateFormatter("dd/MM/yyyy");
}
I used the following configuration class and was able to get the custom formatter to be called. I have not been able to get the messageSource to be passed in, it is always null as the auto-configure bean is created later in the application startup.
#Configuration
public class ThymeleafConfig {
#Autowired
private MessageSource messageSource;
#Bean
public SpringSecurityDialect securityDialect() {
return new SpringSecurityDialect();
}
#Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService( false );
conversionService.addFormatter( dateFormatter() );
return conversionService;
}
#Bean
public DateFormatter dateFormatter() {
return new DateFormatter( messageSource );
}
}
You also need to make sure in your template that you use the double brackets around the field. i.e.
<td th:text="${{user.createDate}}">12-MAR-2015</td>