I am developing an Ionic app based on a Spring backend.
I implemented Spring Security with JWT authentication. My app will have a chat room where users can talk each other in private or public chat. So, I am implementing a WebSocket system in order to get all updates in real time.
This is my Security Configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
private AuthenticationManager authenticationManager;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
// configurazione Cors per poter consumare le api restful con richieste ajax
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*");
configuration.setAllowedMethods(Arrays.asList("POST, PUT, GET, OPTIONS, DELETE"));
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().cors().and()
.authorizeRequests()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/image/**").permitAll()
.antMatchers("/socket/**").permitAll()
.antMatchers("/public/**").permitAll().and()
.authorizeRequests().anyRequest().authenticated().and();
httpSecurity.headers().cacheControl();
}
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
}
This is my WebSocket configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/chat")
.enableSimpleBroker("/subscribe");
}
}
In this condition, I am currently facing this error:
Access to XMLHttpRequest at
'http://localhost:8080/SpringApp/socket/info?t=1547732425329' from
origin 'http://localhost:8100' has been blocked by CORS policy: 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'. The credentials mode of requests initiated by the
XMLHttpRequest is controlled by the withCredentials attribute.
Each call is working (i am perfectly authorized with jwt) but the WebSocket can't work.
So, I tried to simply remove the .cors() in configure method in my security configuration class. This lead me to an opposite problem:
error in chrome
Indeed, now WebSocket works perfectly, instead each api call gives me 401.
What's the correct way to resolve this problem?
Thank you
Yeah, I got the same error when I was working in a related issue in one of my projects. The solution was that I had to set the allowed-origin header value to the URL of my application. The wildcard value (*) is not allowed if you send credentials.
Related
I'm in the process of re-learning Spring security in Spring Boot 3. Things changed a little and for some reason the same settings working for WebSecurityConfigurerAdapter's config method will not work in SecurityFilterChain.
HERE IS SOME CODE FROM PREVIOUS SETUPS- WORKING
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AppUserService userService;
private final PasswordEncoder bCryptPasswordEncoder;
public SecurityConfig(AppUserService userService, PasswordEncoder bCryptPasswordEncoder) {
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
private static final String[] SWAGGER = {
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/v3/api-docs/**",
"/swagger-ui/**"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors(c -> {
CorsConfigurationSource cs = request -> {
CorsConfiguration cc = new CorsConfiguration();
cc.setAllowedOrigins(List.of("*"));
cc.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
cc.setAllowedHeaders(List.of("Origin", "Content-Type", "X-Auth-Token", "Access-Control-Expose-Header",
"Authorization"));
cc.addExposedHeader("Authorization");
cc.addExposedHeader("User-Name");
return cc;
};
c.configurationSource(cs);
});
http.headers().frameOptions().disable();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.authorizeRequests().antMatchers("/login/**").permitAll();
http.authorizeRequests().antMatchers("/h2-console/**").permitAll();
http.authorizeRequests().antMatchers(SWAGGER).permitAll();
http.authorizeRequests().antMatchers("/users/password-reset-request").permitAll();
http.authorizeRequests().antMatchers("/users/password-change").permitAll();
http.authorizeRequests().antMatchers("/users/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers("/favorites/**").hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(GET).hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(POST).hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(PUT).hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(DELETE).hasAnyAuthority("MODERATOR");
http.authorizeRequests().anyRequest().authenticated();
http.addFilter(new CustomAuthenticationFilter(authenticationManager()));
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected UserDetailsService userDetailsService() {
return userService;
}
}
NOW SINCE
WebSecurityConfigurerAdapter
is no longer available:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final AuthenticationProvider authenticationProvider;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().disable();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.authorizeHttpRequests().requestMatchers("/h2-console/**").permitAll();
http.authorizeHttpRequests().requestMatchers("/auth/**").permitAll();
http.authorizeHttpRequests().anyRequest().authenticated();
http.authenticationProvider(authenticationProvider);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Long story short- previously working
http.headers().frameOptions().disable();
http.authorizeRequests().antMatchers("/h2-console/**").permitAll();
Will not work as
http.headers().frameOptions().disable();
http.authorizeHttpRequests().requestMatchers("/h2-console/**").permitAll();
Application.properties for H2 database are exact same, copied. I didn't changed default url for H2. It seems its the Spring Security standing in the way.
Please advice what to do.
Do you have any knowlage if anything changed for H2 setup since previous Spring Boot?
EDIT: If I simply http.authorizeHttpRequests().anyRequest().permitAll();, the console will work. It must be security related
The H2ConsoleAutoConfiguration will register a Servlet for H2's Web Console, therefore, the servletPath property is needed in order to use the MvcRequestMatcher, like so:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) {
// ...
MvcRequestMatcher h2RequestMatcher = new MvcRequestMatcher(introspector, "/**");
h2RequestMatcher.setServletPath("/h2-console");
http.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(h2RequestMatcher).permitAll()
// ...
);
}
In summary, we are permitting every (/**) request under the h2-console servlet path.
Another option is to use PathRequest.toH2Console() as shown in the Spring Boot H2 Console's documentation, which in turn will create an AntPathRequestMatcher for you.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) {
// ...
http.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toH2Console()).permitAll()
// ...
);
}
This problem has also been answered in the project's repository
I'm trying to setup a simple HTTP basic authentication mechanism for accessing REST endpoints in an application.
Basically, all endpoints starting with /api/internal shall be secured with HTTP basic authentication, while further configurations shall secure other paths with e.g. OAuth2.
The problem is that, for example, a GET request to /api/internal/test is allowed even when the client does not provide any credentials in the request header.
This is my current security configuration class:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Order(1)
#Configuration
#EnableWebSecurity
public static class InternalApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
final PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user")
.password(passwordEncoder.encode("password"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.mvcMatcher("/api/internal/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
}
// Other security configuration follow here...
}
After having spent some more time on this problem, I found that the authentication works when adding the following to the chain:
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
I have a problem with the spring Cors.
I get this error on chome:
Access to XMLHttpRequest at 'http://localhost:8080/api/informationWS' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
My file WebSecurityConfigurerAdapter
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LoginService loginService;
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(loginService)
.passwordEncoder(this.passwordEncoderAutentication());
}
#Bean
public PasswordEncoder passwordEncoderAutentication() {
String idForEncode = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
return passwordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
My file ResourceServerConfigurerAdapter
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/informationWS").permitAll()
.antMatchers(HttpMethod.POST, "/api/work").authenticated()
.anyRequest().denyAll();
}
}
I tried to work with Cors in the two ways below, but neither of them worked, generating the same error
My file cors
#Configuration
#EnableWebMvc
public class Cors implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200");
}
}
My file Cors2
#Configuration
public class Cors {
#Bean
public FilterRegistrationBean<CorsFilter> corsFilterFilterRegistrationBean(){
List<String> host = Arrays.asList("http://localhost:4200");
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(host);
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", corsConfiguration);
CorsFilter corsFilter = new CorsFilter(source);
FilterRegistrationBean<CorsFilter> filter = new FilterRegistrationBean<>(corsFilter);
filter.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filter;
}
}
What you could try/check:
check if the application code is executed - maybe server stops execution for some reason, and so your spring code cannot add a header.
maybe there is preflight request and server does not allow it (so again server stopped execution and your backend code could not send the header)
maybe you yourself stop script somewhere before the header is added, like System.exit(0);
maybe there is redirect to code which does not add header, for example some exception
try running the request from Postman - you should not get the error and maybe you will see something surprising.
does this .antMatchers(HttpMethod.GET, "/api/informationWS") really match the request? Maybe there is a way to add wildcard just for testing and see if it works? Are you sending GET request?
More details, technologies different but concept same: https://dariuscoder.com/2021/09/16/how-to-debug-cors/
I'm having an issue with spring boot application authenticated using keycloak, I have this application sitting behind a haproxy and have tried to completely disable cors on the spring app and manage this on the proxy side, however Im still having issues with cors.
"It does not have HTTP ok status"
Note I'm using an older version of the spring boot keycloak plugin due to the original application using spring boot version 1.5.10
Please find attached some of the configuration options I have explored:
Case no 1: Disable cors on spring app -------------------------------------------------
This setup returns http status ok not present on response preflight header
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
public SecurityWithoutCsrfConfig() {
super();
}
// Submits the KeycloakAuthenticationProvider to the AuthenticationManager
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
// Specifies the session authentication strategy
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**",
"/v2/api-docs",
"/swagger-ui.html",
"/swagger2-ui.html",
"/springfox/**",
"/v2/swagger.json",
"/_twilio/**",
"/webjars/**",
"/configuration/**",
"/swagger-resources/**");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/mypath/**").hasAnyRole("USER")
.antMatchers("/mypath_admin/**").hasAnyRole("USER_ADMIN", "ADMIN")
.anyRequest().fullyAuthenticated()
.and().httpBasic().and().cors().disable();
http.headers().cacheControl();
}
Case no 2 : set the headers on spring app side -----------------------------------------------
SecurityConfig
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
public SecurityWithoutCsrfConfig() {
super();
}
// Submits the KeycloakAuthenticationProvider to the AuthenticationManager
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
// Specifies the session authentication strategy
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**",
"/v2/api-docs",
"/swagger-ui.html",
"/swagger2-ui.html",
"/springfox/**",
"/v2/swagger.json",
"/_twilio/**",
"/webjars/**",
"/configuration/**",
"/swagger-resources/**");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/mypath/**").hasAnyRole("USER")
.antMatchers("/mypath_admin/**").hasAnyRole("USER_ADMIN", "ADMIN")
.anyRequest().fullyAuthenticated()
.and().httpBasic();
http.headers().cacheControl();
}
WebConfig
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* Adds Cross Origin Resource Sharing filter
* #return CorsFilter
*/
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
For case 2 i encounter problems with the allowed origin headers. My haproxy config is standard setup you would find on any of the official haproxy site.
Any solutions or suggestions to the above is much appreciated...
Preferably a solution to the http status on the preflight response.
To debug the situation fuuther I added a logger to the configure method in my securityconfig class, as this is where I suspected the requests were breaking down
LOG.info(" --executing: configure(HttpSecurity)");
The factI had a logger in this class now meant that more config details would be printed to the output of the application, so I built the jar, copied it to the server and ran my jar file. When I reloaded the request that originally gave me the cors error (http okay status not present on pre-flight) the logs printed out an SSL handshake exception, this told me that somewhere in my app configuration it was set to only expect connections via https or a secure port. However this was not the case, I was using haproxy exposed on a secure port, forwarding the connections to the port the jar was listening on (8080).
So I double checked all of the configs for security throughout the app, and in my application properties I discovered I had the wrong setting for
keycloak.ssl-required=all
So I removed this setting, deployed the app and voila, issue averted. Hope this comes in as a help to anyone who has similar issues.
I would like to test my application (Angular) in production. So I transfered all necessary files and I successfully started the Spring-Boot app (jar).
Angular is sending requests to 127.0.0.1 :
apiHost: 'http://127.0.0.1:8080/'
And the Configurer is like following :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#CrossOrigin("*")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name = "userService")
private UserDetailsService userDetailsService;
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().
authorizeRequests()
.antMatchers("/generate-token", "/signup","/config","/saveConfig/","/get/file/*").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
This gives the following errors :
Failed to load http://127.0.0.1:8080/salesStats: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://yzyzyzyz.com' is therefore not allowed access. The response had HTTP status code 503.
Am I doing something wrong ?