Why I get cors error when submitting a request to secured resource in spring boot? - spring-boot

I have implemented spring security in my app using jwt token, I have the following configuration in spring security:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true)
public class MSSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/companies/UnAuth/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/companies/Auth/**").authenticated()
.antMatchers("/companies/Auth/Update").authenticated()
.antMatchers("/companies/Auth/Delete").authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
I have the following cors annotation on the relevant controller:
#CrossOrigin(origins = "http://localhost:4200", maxAge = 3600)
#RestController
#RequestMapping("/companies")
#Slf4j
public class CompanyController {
I tried to add the following to the http interceptor in angular:
authReq.headers.set("Access-Control-Allow-Origin", "http://localhost:4200");
authReq.headers.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
When submitting the request from Angular 9 app I can't pass the security and I get cors error:
`Access to XMLHttpRequest at 'http://localhost:9001/companies/Auth/Update' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resourc`e.

The request doesn't contain the 'Access-Control-Allow-Origin' header, you should add it in the headers, it allows remote computers to access the content you send via REST.
If you want to allow all remote hosts to access your api content you should add it like so:
Access-Control-Allow-Origin: *
Your can also specify a specific host:
Access-Control-Allow-Origin: http://example.com
You should modify your dependencies in the pom.xml file and allow CORS headers, appart from the Access-Control-Allow-Origin headers there are a few more that you will need to add to the request, seek more info here:
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

Related

403 after login with OAuth 2

I am using Spring Security 5 and I implemented the login but everytime I try to call other URL after login I get a 403 Unhautorized. My doFilterInternal is not even called (it is for the login though).
It gets on org.springframework.security.web.session.SessionManagementFilter#doFilter but it has no security context or authentication present neither a session.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
private CustomOAuth2UserService customOAuth2UserService;
#Autowired
private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
#Autowired
private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;
#Autowired
private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;
#Bean
public TokenAuthenticationFilter tokenAuthenticationFilter() {
return new TokenAuthenticationFilter();
}
/*
By default, Spring OAuth2 uses HttpSessionOAuth2AuthorizationRequestRepository to save
the authorization request. But, since our service is stateless, we can't save it in
the session. We'll save the request in a Base64 encoded cookie instead.
*/
#Bean
public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() {
return new HttpCookieOAuth2AuthorizationRequestRepository();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.requestMatchers( "/auth/login").permitAll()
.anyRequest().authenticated();
return http.build();
}
}
HttpCookieOAuth2AuthorizationRequestRepository
#Component
public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request";
public static final String REDIRECT_URI_PARAM_COOKIE_NAME = "redirect_uri";
private static final int cookieExpireSeconds = 180;
#Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
return CookieUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME)
.map(cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest.class))
.orElse(null);
}
#Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
if (authorizationRequest == null) {
CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME);
return;
}
CookieUtils.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds);
String redirectUriAfterLogin = request.getParameter(REDIRECT_URI_PARAM_COOKIE_NAME);
if (StringUtils.isNotBlank(redirectUriAfterLogin)) {
CookieUtils.addCookie(response, REDIRECT_URI_PARAM_COOKIE_NAME, redirectUriAfterLogin, cookieExpireSeconds);
}
}
#Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) {
return this.loadAuthorizationRequest(request);
}
// #Override
// public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
// return this.loadAuthorizationRequest(request);
// }
public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) {
CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME);
}
}
You are missing the resource-server configuration in your HTTP config with either JWT decoder or token introspection ("opaqueToken" in spring security configuration DSL). Sample configuration from this tutorials I wrote:
#Bean
SecurityFilterChain filterChain(HttpSecurity http,
Converter<Jwt, AbstractAuthenticationToken> authenticationConverter,
ServerProperties serverProperties)
throws Exception {
// Enable OAuth2 with custom authorities mapping
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter);
// Enable and configure CORS
http.cors().configurationSource(corsConfigurationSource());
// State-less session (state in access-token only)
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Disable CSRF because of state-less session-management
http.csrf().disable();
// Return 401 (unauthorized) instead of 302 (redirect to login) when
// authorization is missing or invalid
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
});
// Route security: authenticated to all routes but actuator and Swagger-UI
// #formatter:off
http.authorizeHttpRequests()
.requestMatchers("/actuator/health/readiness", "/actuator/health/liveness", "/v3/api-docs", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
.anyRequest().authenticated();
// #formatter:on
return http.build();
}
OAuth2 login is for clients (server side rendered UI with template engine like Thymeleaf or JSF) and requires sessions (and CSRF protection), not for resource-servers (REST APIs) which should respond to unauthorized requests to secured resources with 401 (unauthorized) and not 302 (redirect to login). Use a certified OpenID client lib in your client to manage redirection to authorization server, token acquisition and refreshing, and requests authorization (setting of Authorization header with access-token).
You asked for SessionCreationPolicy.STATELESS and it's what you get.
If there is no "state", there is no recording of the fact that the user has logged in successfully. It's a setup for REST services not for UI interaction.

Spring Security does not reject requests when missing HTTP basic authentication header

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)

Spring erro Cors

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/

Spring Custom Security With MySQL And JPA Giving 403 Access Denied

I am trying to access my rest api on postman by providing authentication using UserDetailsService, but each time I am firing the request every time request giving 403 Access Denied. The behavior is same for POST and GET method. I have read the other issues logged on forum but every answers says it is due to CSRF, I disabled it but issue remains same.
Complete code is on : https://github.com/afulz29/spring-security-demo.git
Please help me, I am struggling with this issue since 3 days.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer{
#Autowired
UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http
.authorizeRequests()
.antMatchers("/api/**").authenticated().anyRequest().hasAnyRole("ADMIN");
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
}
#RestController
#RequestMapping("/api")
public class UserController {
#Autowired
private UserService userService;
#GetMapping(path = "/users")
public User getUserById(#RequestParam("userId") Integer userId) {
return userService.getUserById(userId);
}
#PostMapping(path = "/users", consumes = MediaType.APPLICATION_JSON_VALUE)
public User addUser(#RequestBody User user) {
return userService.addUser(user);
}
}
I see couple of problems with your security config:
BASIC AUTH is not enabled but you are trying to do Basic Auth in postman
Do the following to enable Basic Auth
http
.authorizeRequests()
...
.and()
.httpBasic();
I guess the POST /api/users is a user registration endpoint. You must whitelist this endpoint so that anyone can register
http
.authorizeRequests()
.antMatchers( HttpMethod.POST,"/api/users").permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().hasAnyRole("ADMIN")
.and()
.httpBasic();
Test:
Create user
POST: localhost:8080/api/users
{
"userName" : "user1",
"password": "pass"
}
Get user info
GET: localhost:8080/api/users?userId=1 //use the correct ID
With Basic Auth: userName = user1, password = pass
BONUS Feedback:
User.userName --> you might want to make this field unique
#Repository this annotation is not required in your Repository interfaces
UserService interface. I don't see any reason to use the interface and impl.

CORS problems with Spring Security and Websocket

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.

Resources