I am learning web development and I have frontend vue.js app, which is making request to the spring boot back end. When I make a requst from vue to the spring boot with JWT authorization token, I get CORS error. I tried to put a @CrossOrigin(origin="*") anotation to my controller classes, but it didn´t help. I made a CorsConfig class, but that didn´t help neither. I still get CORS error:
"Access to fetch at 'http://127.0.0.1:8081/api/v1/posts/userPosts' from origin 'http://localhost:8080' 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 resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled."
"PostService.js:12 GET http://127.0.0.1:8081/api/v1/posts/userPosts net::ERR_FAILED"
My CorsConfig class:
package com.blog.security.config;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/v1/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
My security config class:
package com.blog.security.config;
import com.blog.user.User;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(@NotNull HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers("/api/v1/posts","/api/v1/auth/register","/api/v1/auth/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
}
My controller:
package com.blog.post;
import com.blog.post.exceptions.PostNotModifiableException;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/posts")
public class PostController {
private final PostService postService;
public PostController(PostService postService){
this.postService = postService;
}
@ExceptionHandler(PostNotModifiableException.class)
public ResponseEntity<PostResponse> handleException(PostNotModifiableException e){
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(PostResponse.builder().postResponse(e.getMessage()).build());
}
@ExceptionHandler(ExpiredJwtException.class)
public ResponseEntity<PostResponse> handleException(ExpiredJwtException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(PostResponse.builder().postResponse(e.getMessage()).build());
}
@ExceptionHandler(JwtException.class)
public ResponseEntity<PostResponse> handleException(JwtException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(PostResponse.builder().postResponse(e.getMessage()).build());
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<PostResponse> handleException(RuntimeException e){
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(PostResponse.builder().postResponse(e.getMessage()).build());
}
@GetMapping
public List<Post> getPosts(){return postService.getAllPosts();}
@GetMapping("userPosts")
public ResponseEntity<?> getUserPosts(HttpServletRequest request) {
try {
List<Post> posts = postService.getUserPosts(request);
return ResponseEntity.ok(posts);
} catch (ExpiredJwtException e) {
String errorMessage = "JWT token has expired";
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(errorMessage);
}
}
@PostMapping("createPost")
public ResponseEntity<PostResponse> addPost(@RequestBody PostRequest postRequest, HttpServletRequest request){
System.out.println("Saving post");
postService.addPost(postRequest, request);
return ResponseEntity.ok().body(PostResponse.builder().postResponse("Post created").build());
}
@PutMapping("update/{postId}")
public ResponseEntity<PostResponse> updatePost(@PathVariable("postId") Integer postId, @RequestBody PostRequest updateRequest, HttpServletRequest request) throws Exception {
postService.updatePost(postId, updateRequest, request);
return ResponseEntity.ok().body(PostResponse.builder().postResponse("Post updated").build());
}
@DeleteMapping("delete/{postId}")
public ResponseEntity<PostResponse> deletePost(@PathVariable("postId") Integer postId, HttpServletRequest request) throws Exception{
System.out.println("Deleting post");
postService.deletePost(postId, request);
return ResponseEntity.ok().body(PostResponse.builder().postResponse("Post deleted").build());
}
}
My vue service for posts:
export default{
async fetchData() {
let response = await fetch("http://127.0.0.1:8081/api/v1/posts")
let data = await response.json()
return data;
},
async fetchUserData() {
let response = await fetch("http://127.0.0.1:8081/api/v1/posts/userPosts",{
method:"GET",
headers:{
"Authorization": "Bearer " + sessionStorage.getItem("JWT"),
"Content-Type": "application/json",
}
})
let data = await response.json()
return data;
},
async savePost(head, body) {
console.log(sessionStorage.getItem("JWT"))
await fetch("http://127.0.0.1:8081/api/v1/posts/createPost",{
method:"POST",
headers:{
"Authorization": "Bearer " + sessionStorage.getItem("JWT"),
"Content-Type": "application/json",
},
body:JSON.stringify({
"head":head,
"body":body
})
})
return "Post created";
}
};
I tried to configure the CORS globally or locally for controller class or even for every request mapping, but nothing works.
If I changed the requestmatcher in my security config to:
.requestMatchers("/api/v1/**","/api/v1/auth/register","/api/v1/auth/login")
to acces everything without athentication, it works. But if I leave it to everything esle than registering and login to authenticated only, it does not.