Run jQuery and Bootstrap in Spring MVC with Spring Security - java

I am new to spring boot MVC app development. I have a new application, that I set up through Maven, in which I am trying to import jquery and bootstrap. I imported both through Maven as Jar files. They reside in Java Resources->Libraries->Maven Dependencies.
I am using spring framework 5.3.22 and spring security 5.7.3
When I run my site, I can see the jquery and bootstrap files are added in the Network tab, but I get the following errors:
"Refused to execute script from [website url] because its MIME type
('text/html') is not executable, and strict MIME type checking is
enabled."
And
"Refused to apply style from [website url] because its
MIME type ('text/html') is not a supported stylesheet MIME type, and
strict MIME checking is enabled."
On my JSP page, I include the files like this:
<link href="webjars/bootstrap/5.2.0/css/bootstrap.min.css" rel="stylesheet" >
<script src="webjars/jquery/3.6.1/jquery.min.js" type="text/javascript"></script>
<script src="webjars/bootstrap/5.2.0/js/bootstrap.min.js" type="text/javascript"></script>
My Servlet config looks like this:
public class MySpringMvcDispatcherServeletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
//return DemoAppConfig.java class
return new Class[] {DemoAppConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
}
#Configuration
#EnableWebSecurity
public class DemoSecurityConfig {
#Bean
public InMemoryUserDetailsManager userDetailsManager() {
System.out.println("======>> Add Details manager");
//add in memory users/roles
}
#Bean
public CorsConfigurationSource corsConfigurationSource(){
System.out.println("======>> Add CORS");
CorsConfiguration corsConfiguration = new CorsConfiguration();
// Below config will allow only following origines from web browser
corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8080/"));
// Whether user credentials are supported. By default, do not support
// If you want to allow credentials then set it true
corsConfiguration.setAllowCredentials(false);
// below will not allow DELETE methods, if you want then add DELETE also
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "OPTION"));
// Below will set allowed headers list, Other headers will not be supported
corsConfiguration.setAllowedHeaders(Arrays.asList("accept", "authorization", "apikey", "tenantId"));
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
// This will register above configurations on all resources from the root
// If you want different rules for different resources then create separate configuration
// and register on separate resource path uri
corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return corsConfigurationSource;
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// Ignore resources for any check
System.out.println("======>> Add web Customizer");
return (web) -> web.ignoring().antMatchers("/webjars/**","/resources/**", "/static/**", "/css/**", "/js/**", "/img/**", "/icon/**");
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
System.out.println("======>> Add Security Chain");
return http
.authorizeRequests(configurer ->
configurer
.antMatchers("/webjars/**","/resources/**", "/static/**", "/css/**", "/js/**", "/img/**", "/icon/**").permitAll()
.anyRequest().authenticated())
.formLogin(configurer ->
configurer
.loginPage("/showMyLoginPage")
.loginProcessingUrl("/authenticateTheUser")
.permitAll())
.build();
}
}

I was able to do this by overriding addResrouceHandlers. In my DemoAppConfig file, which implements WebMvcConfigurer, I add this code:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages="com.jason.springsecurity.demo")
public class DemoAppConfig implements WebMvcConfigurer{
//define bean for ViewResolver
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
System.out.println("================>Calling addResourceHandlers");
//my libraries
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/");
//webjars
if (!registry.hasMappingForPattern("/webjars/**")) {
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars/");
}
}
}
I can then add the files to my Views like this:
<link href="webjars/bootstrap/5.2.0/css/bootstrap.min.css" rel="stylesheet" >
<script src="webjars/jquery/3.6.1/jquery.min.js"></script>
<script src="webjars/bootstrap/5.2.0/js/bootstrap.min.js"></script>

Related

How to enable CORS in Spring Boot 3 for Angular project? [duplicate]

I have created two web applications - client and service apps.The interaction between client and service apps goes fine when they are deployed in same Tomcat instance.
But when the apps are deployed into seperate Tomcat instances (different machines), I get the below error when request to sent service app.
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:8080' is therefore not allowed access. The response had HTTP status code 401
My Client application uses JQuery, HTML5 and Bootstrap.
AJAX call is made to service as shown below:
var auth = "Basic " + btoa({usname} + ":" + {password});
var service_url = {serviceAppDomainName}/services;
if($("#registrationForm").valid()){
var formData = JSON.stringify(getFormData(registrationForm));
$.ajax({
url: service_url+action,
dataType: 'json',
async: false,
type: 'POST',
headers:{
"Authorization":auth
},
contentType: 'application/json',
data: formData,
success: function(data){
//success code
},
error: function( jqXhr, textStatus, errorThrown ){
alert( errorThrown );
});
}
My service application uses Spring MVC, Spring Data JPA and Spring Security.
I have included CorsConfiguration class as shown below:
CORSConfig.java:
#Configuration
#EnableWebMvc
public class CORSConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("*");
}
}
SecurityConfig.java:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#ComponentScan(basePackages = "com.services", scopedProxy = ScopedProxyMode.INTERFACES)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("authenticationService")
private UserDetailsService userDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().fullyAuthenticated();
http.httpBasic();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
}
Spring Security dependencies:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
I am using Apache Tomcat server for deployment.
CORS' preflight request uses HTTP OPTIONS without credentials, see Cross-Origin Resource Sharing:
Otherwise, make a preflight request. Fetch the request URL from origin source origin using referrer source as override referrer source with the manual redirect flag and the block cookies flag set, using the method OPTIONS, and with the following additional constraints:
Include an Access-Control-Request-Method header with as header field value the request method (even when that is a simple method).
If author request headers is not empty include an Access-Control-Request-Headers header with as header field value a comma-separated list of the header field names from author request headers in lexicographical order, each converted to ASCII lowercase (even when one or more are a simple header).
Exclude the author request headers.
Exclude user credentials.
Exclude the request entity body.
You have to allow anonymous access for HTTP OPTIONS.
Spring Security 3
Your modified (and simplified) code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
}
You still need your CORS configuration (probably with some additional values):
#Configuration
#EnableWebMvc
public class CORSConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("*");
}
}
Spring Security 4
Since Spring Security 4.2.0 you can use the built-in support, see Spring Security Reference:
19. CORS
Spring Framework provides first class support for CORS. CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the JSESSIONID). If the request does not contain any cookies and Spring Security is first, the request will determine the user is not authenticated (since there are no cookies in the request) and reject it.
The easiest way to ensure that CORS is handled first is to use the CorsFilter. Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource using the following:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors().and()
...
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Spring Security 5/6
For Spring Security 5/6 see Spring Security Reference:
CORS
Spring Framework provides first class support for CORS. CORS must be processed before Spring Security, because the pre-flight request does not contain any cookies (that is, the JSESSIONID). If the request does not contain any cookies and Spring Security is first, the request determines that the user is not authenticated (since there are no cookies in the request) and rejects it.
The easiest way to ensure that CORS is handled first is to use the CorsFilter. Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource that uses the following:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors(withDefaults())
...
return http.build();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
With Spring Security 5/6 you don't need to configure CORS twice (Spring Security and Spring MVC):
If you use Spring MVC’s CORS support, you can omit specifying the CorsConfigurationSource and Spring Security uses the CORS configuration provided to Spring MVC:
Since Spring Security 4.1, this is the proper way to make Spring Security support CORS (also needed in Spring Boot 1.4/1.5):
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}
and:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// http.csrf().disable();
http.cors();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(ImmutableList.of("*"));
configuration.setAllowedMethods(ImmutableList.of("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
// setAllowCredentials(true) is important, otherwise:
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
configuration.setAllowCredentials(true);
// setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Do not do any of below, which are the wrong way to attempt solving the problem:
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
web.ignoring().antMatchers(HttpMethod.OPTIONS);
Reference: http://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/cors.html
In my case, I have Resource Server with OAuth security enabled and any of above solutions didn't work. After some debugging and googling figured why.
#Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
Basically in this example Ordered.HIGHEST_PRECEDENCE is key!
https://github.com/spring-projects/spring-security-oauth/issues/938
Various pom dependencies add different kinds of filters and therefore we could have issues based on order.
Add the below configuration in the main application. It worked me in spring boot application 2.3.1
package com.example.restservicecors;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#SpringBootApplication
public class RestServiceCorsApplication {
public static void main(String[] args) {
SpringApplication.run(RestServiceCorsApplication.class, args);
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedHeaders("*").allowedMethods("*");
}
};
}
}
Reference source: https://spring.io/guides/gs/rest-service-cors/
Since none of this posted examples helped me, I've taken things in my own knowledge.
In this method:
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration cors = new CorsConfiguration();
cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE"));
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
CorsConfiguration by default have allowed method: POST, HEAD, GET, so PUT, DELETE will not work. What I did is I created a new instance of CorsConfiguration and set allowed methods:
cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE"));
so now my method looks like:
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration cors = new CorsConfiguration();
cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", cors.applyPermitDefaultValues());
return source;
}
Of course, all other configuration is made by Spring documentation.
Try this:
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
import java.util.List;
#Component
public class CorsFilterConfig {
public static final List<String> allowedOrigins = Arrays.asList("*");
#Bean
public FilterRegistrationBean<CorsFilter> initCorsFilter() {
// #formatter:off
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
config.addAllowedMethod("*");
config.setAllowedOrigins(allowedOrigins);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
// #formatter:on
}
}
You can easily add the #CrossOrigin annotation to allow all of them if you use UsernamePasswordAuthenticationFilter. And in the security configurations the http.cors().and(). This worked for me.
#CrossOrigin(origins = "*")
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean());
customAuthenticationFilter.setFilterProcessesUrl("/api/login");
http
.csrf().disable();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// We can ant match out paths to the corresponding roles --> we allow certain roles to access certain API's
http
.cors()
.and();
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/**").permitAll();
...
This worked for: spring-boot-starter-parent 2.2.6.RELEASE
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedHeaders("*").allowedMethods("*");
}
}
Change "*" to something meaningful in prod

Global CORS not working on Spring Boot Login route [duplicate]

I have created two web applications - client and service apps.The interaction between client and service apps goes fine when they are deployed in same Tomcat instance.
But when the apps are deployed into seperate Tomcat instances (different machines), I get the below error when request to sent service app.
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:8080' is therefore not allowed access. The response had HTTP status code 401
My Client application uses JQuery, HTML5 and Bootstrap.
AJAX call is made to service as shown below:
var auth = "Basic " + btoa({usname} + ":" + {password});
var service_url = {serviceAppDomainName}/services;
if($("#registrationForm").valid()){
var formData = JSON.stringify(getFormData(registrationForm));
$.ajax({
url: service_url+action,
dataType: 'json',
async: false,
type: 'POST',
headers:{
"Authorization":auth
},
contentType: 'application/json',
data: formData,
success: function(data){
//success code
},
error: function( jqXhr, textStatus, errorThrown ){
alert( errorThrown );
});
}
My service application uses Spring MVC, Spring Data JPA and Spring Security.
I have included CorsConfiguration class as shown below:
CORSConfig.java:
#Configuration
#EnableWebMvc
public class CORSConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("*");
}
}
SecurityConfig.java:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#ComponentScan(basePackages = "com.services", scopedProxy = ScopedProxyMode.INTERFACES)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("authenticationService")
private UserDetailsService userDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().fullyAuthenticated();
http.httpBasic();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
}
Spring Security dependencies:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
I am using Apache Tomcat server for deployment.
CORS' preflight request uses HTTP OPTIONS without credentials, see Cross-Origin Resource Sharing:
Otherwise, make a preflight request. Fetch the request URL from origin source origin using referrer source as override referrer source with the manual redirect flag and the block cookies flag set, using the method OPTIONS, and with the following additional constraints:
Include an Access-Control-Request-Method header with as header field value the request method (even when that is a simple method).
If author request headers is not empty include an Access-Control-Request-Headers header with as header field value a comma-separated list of the header field names from author request headers in lexicographical order, each converted to ASCII lowercase (even when one or more are a simple header).
Exclude the author request headers.
Exclude user credentials.
Exclude the request entity body.
You have to allow anonymous access for HTTP OPTIONS.
Spring Security 3
Your modified (and simplified) code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
}
You still need your CORS configuration (probably with some additional values):
#Configuration
#EnableWebMvc
public class CORSConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("*");
}
}
Spring Security 4
Since Spring Security 4.2.0 you can use the built-in support, see Spring Security Reference:
19. CORS
Spring Framework provides first class support for CORS. CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the JSESSIONID). If the request does not contain any cookies and Spring Security is first, the request will determine the user is not authenticated (since there are no cookies in the request) and reject it.
The easiest way to ensure that CORS is handled first is to use the CorsFilter. Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource using the following:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors().and()
...
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Spring Security 5/6
For Spring Security 5/6 see Spring Security Reference:
CORS
Spring Framework provides first class support for CORS. CORS must be processed before Spring Security, because the pre-flight request does not contain any cookies (that is, the JSESSIONID). If the request does not contain any cookies and Spring Security is first, the request determines that the user is not authenticated (since there are no cookies in the request) and rejects it.
The easiest way to ensure that CORS is handled first is to use the CorsFilter. Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource that uses the following:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors(withDefaults())
...
return http.build();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
With Spring Security 5/6 you don't need to configure CORS twice (Spring Security and Spring MVC):
If you use Spring MVC’s CORS support, you can omit specifying the CorsConfigurationSource and Spring Security uses the CORS configuration provided to Spring MVC:
Since Spring Security 4.1, this is the proper way to make Spring Security support CORS (also needed in Spring Boot 1.4/1.5):
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}
and:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// http.csrf().disable();
http.cors();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(ImmutableList.of("*"));
configuration.setAllowedMethods(ImmutableList.of("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
// setAllowCredentials(true) is important, otherwise:
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
configuration.setAllowCredentials(true);
// setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Do not do any of below, which are the wrong way to attempt solving the problem:
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
web.ignoring().antMatchers(HttpMethod.OPTIONS);
Reference: http://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/cors.html
In my case, I have Resource Server with OAuth security enabled and any of above solutions didn't work. After some debugging and googling figured why.
#Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
Basically in this example Ordered.HIGHEST_PRECEDENCE is key!
https://github.com/spring-projects/spring-security-oauth/issues/938
Various pom dependencies add different kinds of filters and therefore we could have issues based on order.
Add the below configuration in the main application. It worked me in spring boot application 2.3.1
package com.example.restservicecors;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#SpringBootApplication
public class RestServiceCorsApplication {
public static void main(String[] args) {
SpringApplication.run(RestServiceCorsApplication.class, args);
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedHeaders("*").allowedMethods("*");
}
};
}
}
Reference source: https://spring.io/guides/gs/rest-service-cors/
Since none of this posted examples helped me, I've taken things in my own knowledge.
In this method:
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration cors = new CorsConfiguration();
cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE"));
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
CorsConfiguration by default have allowed method: POST, HEAD, GET, so PUT, DELETE will not work. What I did is I created a new instance of CorsConfiguration and set allowed methods:
cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE"));
so now my method looks like:
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration cors = new CorsConfiguration();
cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", cors.applyPermitDefaultValues());
return source;
}
Of course, all other configuration is made by Spring documentation.
Try this:
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
import java.util.List;
#Component
public class CorsFilterConfig {
public static final List<String> allowedOrigins = Arrays.asList("*");
#Bean
public FilterRegistrationBean<CorsFilter> initCorsFilter() {
// #formatter:off
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
config.addAllowedMethod("*");
config.setAllowedOrigins(allowedOrigins);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
// #formatter:on
}
}
You can easily add the #CrossOrigin annotation to allow all of them if you use UsernamePasswordAuthenticationFilter. And in the security configurations the http.cors().and(). This worked for me.
#CrossOrigin(origins = "*")
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean());
customAuthenticationFilter.setFilterProcessesUrl("/api/login");
http
.csrf().disable();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// We can ant match out paths to the corresponding roles --> we allow certain roles to access certain API's
http
.cors()
.and();
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/**").permitAll();
...
This worked for: spring-boot-starter-parent 2.2.6.RELEASE
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedHeaders("*").allowedMethods("*");
}
}
Change "*" to something meaningful in prod

UTF-8 encoding issue with Thymeleaf Spring MVC

I have issue with UTF-8 characters. I've tried many solutions, but nothing is working for me. What I have right now:
Tomcat (server.xml):
<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
Base configuration class:
public class HomeInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebSecurityConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebMvcConfiguration.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8"); // forcing UTF-8
filter.setForceEncoding(true);
return new Filter[] { filter };
}
}
WebMvcConfiguration class which contains Thymeleaf configuration:
#Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setCharacterEncoding("UTF-8"); // forcing UTF-8
viewResolver.setContentType("text/html; charset=UTF-8");
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[] { ".html", ".xhtml" });
return viewResolver;
}
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setCharacterEncoding("UTF-8"); // forcing UTF-8
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCacheable(true);
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
registry.viewResolver(resolver);
}
WebSecurityConfig which contains the rest of the configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().anonymous().antMatchers("/login**", "/*.js", "/*.css", "/*.svg").permitAll().anyRequest().authenticated()
.antMatchers("/login**").permitAll().and().formLogin().loginPage("/login").loginProcessingUrl("/login").usernameParameter("username")
.passwordParameter("password").defaultSuccessUrl("/", true).permitAll().and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login?logout").deleteCookies("JSESSIONID")
.invalidateHttpSession(true).permitAll();
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8"); // another forcing UTF-8
filter.setForceEncoding(true);
http.addFilterBefore(filter, CsrfFilter.class);
}
all html pages contain:
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
before I've added Thymeleaf and used jsp I've been able to display special characters
also checked that all files (.html) are encoded with UTF-8
Eclipse settings changed to keep everything in UTF-8
checked probably all answers on stackoverflow related to this issue and verified all higher ranked answers (including this, this and that)
checked Thymeleaf messaging solution (some stack question for that)
escaped characters \u0119 are displayed as they are - \u0119
created new project with same pom.xml, Spring/Thymeleaf configuration
And still instead of polish
ąęłźżńłó
I have
???????ó
displayed on page. Any ideas?
Unfortunatelly I don't have
web.xml
and whole configuration uses annotations. I can't control
Filter
classes order - see can use #Order annotation
Used technologies: Spring Framework 5.1.6, Spring Security 5.1.5, Thymeleaf 3.0.11, Thymeleaf Spring security4 3.0.4, Maven 4, Tomcat 9.
Unfortunatelly I have to answer my own question.
To read UTF-8 data from properties file, just use encoding property
#Component
#PropertySource(value = "classpath:config/data.properties", encoding = "UTF-8")
basically what helped was adding some additional method calls to the configureViewResolvers method:
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8"); // <- this was added
resolver.setForceContentType(true); // <- this was added
resolver.setContentType("text/html; charset=UTF-8"); // <- this was added
registry.viewResolver(resolver);
}
and additionaly in configure(HttpSecurity http) method I've changed the way how filter is added to this:
#Override
protected void configure(HttpSecurity http) throws Exception {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
http.authorizeRequests().anyRequest().anonymous()
.antMatchers("/login**", "/*.js", "/*.css", "/*.svg" ).permitAll()
// ... some other config
.invalidateHttpSession(true)
.permitAll()
.and()
.addFilterBefore(filter, CsrfFilter.class); // <- this was added
}
So this might seem very remedial, but have you navigated to the directory structure, right clicked the folder containing your pages, and selecting refresh?
I don't know what IDE you're using, but in the past I have had to manually go into Eclipse and refresh the package containing the static content.
If you don't do this, it won't pick up on the changes.

Spring MVC redirecting unnecessary urls to particular url [duplicate]

As you know, in XML, the way to configure this is:
<error-page>
<error-code>404</error-code>
<location>/my-custom-page-not-found.html</location>
</error-page>
But I haven't found a way to do it in Java config. The first way I tried was:
#RequestMapping(value = "/**")
public String Error(){
return "error";
}
And it appeared to work, but it has conflicts retrieving the resources.
Is there a way to do it?
In Spring Framework, there are number of ways of handing exceptions (and particularly 404 error). Here is a documentation link.
First, you can still use error-page tag in web.xml, and customize error page. Here is an example.
Second, you can use one #ExceptionHandler for all controllers, like this:
#ControllerAdvice
public class ControllerAdvisor {
#ExceptionHandler(NoHandlerFoundException.class)
public String handle(Exception ex) {
return "404";//this is view name
}
}
For this to work, set throwExceptionIfNoHandlerFound property to true for DispatcherServlet in web.xml:
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
You can also pass some objects to error view, see javadoc for this.
The most clean solution since spring 4.2 RC3 is using the new createDispatcherServlet hook within the class extending AbstractDispatcherServletInitializer (or indirectly through extending AbstractAnnotationConfigDispatcherServletInitializer) like this:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
/* ... */
#Override
protected DispatcherServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
final DispatcherServlet dispatcherServlet = super.createDispatcherServlet(servletAppContext);
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
return dispatcherServlet;
}
}
Then you can use a global #ControllerAdvice (a class that is annotated with #ControllerAdvice) as described in the reference docs. Within the advice you can handle the NoHandlerFoundException with an #ExceptionHandler as described here.
This could look something like this:
#ControllerAdvice
public class NoHandlerFoundControllerAdvice {
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<String> handleNoHandlerFoundException(NoHandlerFoundException ex) {
// prepare responseEntity
return responseEntity;
}
}
Simple answer for 100% free xml:
Set properties for DispatcherServlet
public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {AppConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
boolean done = registration.setInitParameter("throwExceptionIfNoHandlerFound", "true"); // -> true
if(!done) throw new RuntimeException();
}
}
Create #ControllerAdvice:
#ControllerAdvice
public class AdviceController {
#ExceptionHandler(NoHandlerFoundException.class)
public String handle(Exception ex) {
return "redirect:/404";
}
#RequestMapping(value = {"/404"}, method = RequestMethod.GET)
public String NotFoudPage() {
return "404";
}
}
Use code-based Servlet container initialization as described in the doc and override registerDispatcherServlet method to set throwExceptionIfNoHandlerFound property to true:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() may not return empty or null");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext);
// throw NoHandlerFoundException to Controller
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
}
Then create an exception handler:
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(Exception.class)
public String handleException(Exception e) {
return "404";// view name for 404 error
}
}
Don't forget about using #EnableWebMvc annotation on your Spring configuration file:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages= {"org.project.etc"})
public class WebConfig extends WebMvcConfigurerAdapter {
...
}
In your web configuration class,
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter
Declare a bean as follows,
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container)
{
ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401.html");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html");
container.addErrorPages(error401Page,error404Page,error500Page);
}
};
}
Add the mentioned html files(401.html .etc) to /src/main/resources/static/ folder.
Hope this helps
For Java config there is a method setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) in DispatcherServlet. By settting it to true I guess you are doing same thing
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
then you can can this NoHandlerFoundException.class in controller advice as stated in above answer
it will be like something
public class WebXml implements WebApplicationInitializer{
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext context = getContext();
servletContext.addListener(new ContextLoaderListener(context));
DispatcherServlet dp = new DispatcherServlet(context);
dp.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", dp);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping(MAPPING_URL);
}
}
The solution proposed in comments above really works:
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration)
{
registration.setInitParameter("throwExceptionIfNoHandlerFound", "true");
}
A solution for Spring 5 and Thymeleaf 3.
In MyWebInitializer, enable exception throwing with setThrowExceptionIfNoHandlerFound(). We need to do casting to DispatcherServlet.
#Configuration
public class MyWebInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
...
#Override
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
var dispatcher = (DispatcherServlet) super.createDispatcherServlet(servletAppContext);
dispatcher.setThrowExceptionIfNoHandlerFound(true);
return dispatcher;
}
}
Create a controller advice with #ControllerAdvice and add error message to the ModealAndView.
#ControllerAdvice
public class ControllerAdvisor {
#ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView handle(Exception ex) {
var mv = new ModelAndView();
mv.addObject("message", ex.getMessage());
mv.setViewName("error/404");
return mv;
}
}
Create 404 error template, which displays the error message. Based on my configuration, the file is src/main/resources/templates/error/404.html.
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Resource not found</title>
</head>
<body>
<h2>404 - resource not found</h2>
<p>
<span th:text="${message}" th:remove="tag"></span>
</p>
</body>
</html>
For completeness, I add the Thymeleaf resolver configuration. We configure the Thymeleaf templates to be in templates directory on the classpath.
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.zetcode"})
public class WebConfig implements WebMvcConfigurer {
#Autowired
private ApplicationContext applicationContext;
...
#Bean
public SpringResourceTemplateResolver templateResolver() {
var templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
return templateResolver;
}
...
}
In springboot it is even simplier. Because of Spring autoconfiguration stuff, spring creates a bean org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties. This class is annotated with #ConfigurationProperties(prefix = "spring.mvc") and therefore it will seek for properties with spring.mvc prefix.
Part from javadoc:
Annotation for externalized configuration. Add this to a class definition or a
* #Bean method in a #Configuration class if you want to bind and validate
* some external Properties (e.g. from a .properties file).
You just have to add to your i.e. application.properties file following properties:
spring.mvc.throwExceptionIfNoHandlerFound=true
spring.resources.add-mappings=false //this is for spring so it won't return default handler for resources that not exist
and add exception resolver as follows:
#ControllerAdvice
public class ExceptionResponseStatusHandler {
#ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView handle404() {
var out = new ModelAndView();
out.setViewName("404");//you must have view named i.e. 404.html
return out;
}
}

Spring MVC link with .jsp/.jsp suffix returns 404

I have a simple page called headers.jsp in my Spring-MVC (4.1.5.RELEASE) web-app which i would like to call up from my index page like this:
<body>
<c:url value="/headers" var="headersUrl1"/>
<c:url value="/headers.jsp" var="headersUrl2"/>
<c:url value="/headers.jspx" var="headersUrl3"/>
<ul>
<li>Works!</li>
<li>Does not work!</li>
<li>Does not work!</li>
</ul>
The first url (to /headers) works fine but the other two (with suffix .jsp/jspx) do not; result is a 404. I have a need for the links to work (in real life they come from a legacy database which i have to use). I have spent quite some time googling but found no solution as yet.
The requests to links are handled by the following controller code:
#Controller
public class HeadersViewController {
#RequestMapping(value = {"/headers"}, method = RequestMethod.GET)
public String getHeadersPage() {
return "headers";
}
#RequestMapping(value = {"/headers**"}, method = RequestMethod.GET)
public String getHeaderPages() {
return "headers";
}
#RequestMapping(value = {"/headers.jspx"}, method = RequestMethod.GET)
public String getHeaderPageJspx() {
return "headers";
}
#RequestMapping("/{name}.jspx")
public String getPageByName(#PathVariable("name") String name) {
return name;
}
}
None of the controller methods get called when requesting /headers.jsp or /headers.jspx.
Strangely enough when using spring-test and mock-mvc they do get fired and tests to check the mentioned url`s passes.
Any help greatly appreciated! The rest of the config code is given below.
Dispatcherservlet init:
public class DispatcherServletInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { SecurityConfiguration.class, WebMvcConfiguration.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected Filter[] getServletFilters() {
return new Filter[] { new HiddenHttpMethodFilter() };
}
}
MvcConfiguration:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"nl.demo.web.controller"})
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/login");
registry.addViewController("/logout");
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver=new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/");
viewResolver.setSuffix(".jsp");
viewResolver.setOrder(0);
return viewResolver;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
Security config:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/images/**", "/js/**", "/css/**", "/fonts/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/index")
.failureUrl("/login?error=1")
.permitAll()
.and()
.httpBasic()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout=1")
.permitAll();
}
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
String user = "test";
String password = "blabla";
String[] roleList = "USER,SUPERVISOR".split(",");
auth.inMemoryAuthentication()
.withUser(user)
.password(password)
.roles(roleList);
}
}
The most likely cause of your issue is that the *.jsp and *.jspx extensions are normally handled by the container. If you for example checkout tomcat's conf/web.xml you'll notice a configuration
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
Similar goes for other containers. That is why your tests are working, yet your real app request fail.
Your best option would be to implement a Filter, that will be mapped to the same pattern, and that will redirect the headers.jsp and headers.jspx, to be handled by the controller, while leaving the other requests to be processed by the container as they would normally be, you have an example at this question
The problem is that your servlet is mapped to / and not to /*. / is a special mapping meaning that Spring MVC DispatcherServlet receives all requests that have not been managed by anything else.
As JSP and JSF are automatically registered servlets, the servlet container tries to pass the request to a JSP (resp. JSF) file and if it does not find it immediately sends a 404 and does not try to pass it to the DispatcherServlet.
You should map the dispatcher servlet to /*. But beware in that case, all URL will be given to the dispatcher servlet, and you will have to do a special treatment for serving static resources : you must add an override for addResourceHandlers to your Spring MVC configuration file (extract from Spring Framework Reference Manual - Serving of static resources) :
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/");
}

Categories

Resources