5

I'm trying to connect Spring Security to my project. Created the Security Config class

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtTokenProvider jwtTokenProvider;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    
    public SecurityConfig(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable()
                .cors().and().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/auth/api/v1/user/register").permitAll()
                .antMatchers("/auth/api/v1/admin/*").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .apply(new JwtConfigurer(jwtTokenProvider));
    }
}

I am sending a request from the browser for registration

http://localhost:15001/auth/api/v1/user/register

and I get an answer:

Access to XMLHttpRequest at 'http://localhost:15001/auth/api/v1/user/register' 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 resource.

According to the Spring documentation, I add the corsConfigurationSource method:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtTokenProvider jwtTokenProvider;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    
    public SecurityConfig(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable()
                .cors().and().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/auth/api/v1/user/register").permitAll()
                .antMatchers("/auth/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .apply(new JwtConfigurer(jwtTokenProvider));
    }
}

I am sending a request from the browser for registration

http://localhost:15001/auth/api/v1/user/register

and I still get the same error

Access to XMLHttpRequest at 'http://localhost:15001/auth/api/v1/user/register' 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 resource.

Why didn't the error disappear?

I know there is another way to add on the controller

@CrossOrigin(origins="http://localhost:4200″)

You can also add to the header. but I want to figure out why this method doesn't work.

pom.xml

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.4.12</version>
    <relativePath/>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

  <dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>${jwt.version}</version>
  </dependency>
  <dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>${postgres.version}</version>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
  </dependency>
</dependencies>

Here is the code where I form the answer

final UserDto userDto = userToUserDtoConverter.convert(optionalUser.get());
if (password.equals(UserUtils.encryptText(optionalAuth.get().getPassword()))) {
  final HttpHeaders responseHeaders = new HttpHeaders();
  final String token = jwtTokenProvider.createToken(optionalAuth.get().getName());
  return confirmUserRegister(userDto, "Пользователь авторизован",
    HttpStatus.OK, responseHeaders);
}

protected ResponseEntity<DataResponse<UserDto>> confirmUserRegister(
    UserDto userDto, String message, HttpStatus httpStatus, HttpHeaders responseHeaders) {
  final DataResponse<UserDto> response = new DataResponse<>();
  response.setStatus(StatusType.SUCCESSFUL);
  response.setData(userDto);
  response.addMessage(httpStatus.value(), message);
  return new ResponseEntity<>(response, responseHeaders, httpStatus);
}

Here is the link to the project enter link description here

The project is still very raw. And this is a copy of the project, so you can edit as you like

alex
  • 324
  • 1
  • 8
  • 28
  • Hi @alex. May I know the front end ? Are you using Angular / React ? – Gaurav Feb 23 '22 at 12:08
  • On the frontend I use Angular – alex Feb 23 '22 at 12:56
  • Okay. When you finally deploy in production, is it going to be on different servers / domains / paths ? On local env, spring and angular run on two different ports, so browser understands them as two **separate** entities and hence does not allows you to access API. – Gaurav Feb 23 '22 at 13:46
  • This is a training project. Therefore, I do not plan to deploy it on the server yet. And then how does the Angular + Spring bundle work in other projects? – alex Feb 23 '22 at 13:52
  • Show your request with headers. I guess you didn't allow all headers. – dur Feb 24 '22 at 21:09
  • I have added the response generation code. Is there anything else you need? – alex Feb 25 '22 at 05:24

3 Answers3

3

If this is a local environment, you don't need to configure Spring, instead you modify angular configuration.

Create a file proxy.conf.json in your project's src/ folder.

Add the following content to the new proxy file:

{
  "/api": {
    "target": "http://localhost:3000",
    "secure": false
  }
}

In the CLI configuration file, angular.json, add the proxyConfig option to the serve target:

...

"architect": {
  "serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "options": {
      "browserTarget": "your-application-name:build",
      "proxyConfig": "src/proxy.conf.json"
    },
...

To run the development server with this proxy configuration, call ng serve.

More details are here. The trouble with configuring Spring CORS is that:

  • you are trying to solve a development environment specific problem
  • this may leak CORS configuration into production setup where they aren't required unless you do actually want CORS set up.

Now, what to do in production ?

It actually depends on how you bundle your code.

If your UI + Java code is going to be in same deployable, WAR or JAR, you don't need to configure CORS because they will be deployed on same domain (https://apps.example.com) or on same context root (https://apps.example.com/app).

You do need to configure CORS when your UI and java code is not on same domain or you want apps deployed on other domains (https://apps.example.**com**) to access your APIs from their page.

Please note in Spring, when you set

configuration.setAllowCredentials(true);

Spring does not accepts:

configuration.setAllowedOrigins(Arrays.asList("*"));

You are required to configure required origins one by one like this:

configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
Gaurav
  • 3,614
  • 3
  • 30
  • 51
  • And if I upload the application to the server, will I need to configure CORS there? – alex Feb 23 '22 at 14:55
  • 1
    @alex: I updated answer with the statement. deployments will go for Java code + UI in same deployable, so no CORS configuration will be requried, unless you want your APIs to be accessible by scripts on another domain. – Gaurav Feb 23 '22 at 16:27
  • And then why do we need CARS in Spring if all this can be done without it? – alex Feb 24 '22 at 12:30
  • 1
    Because there are scenarios where granting CORS access is required. For exmaple, you can't make AJAX calls from `www.google.com` to `www.apple.com` because domains are different. But lets assume `www.apple.com` wants its APIs to be accessible from `www.google.com`, then you have to configure CORS. – Gaurav Feb 24 '22 at 15:57
  • I have a question then. If I want to upload my application to a real server. In order for this application to work in conjunction with Angular, will I need to configure CORS? I understand that this is a little beyond the scope of this question and I will credit you with points regardless of the answer to this question. I may have to form another question on this topic, but that's when I get to uploading the application to the server. – alex Feb 25 '22 at 04:49
  • Will my code work in this case or not? – alex Feb 25 '22 at 05:45
  • 1
    As I said in the answer, it depends if you are going to deploy these into a single bundle - (WAR/JAR). If two different WARs / JARS on two different ports or IPs, then yes. You will have to do CORS configuration. It single JAR / WAR, then don't require. In my experience, you will want to go ahead with bundling both Angular / Java code in one bundle if business functionalties offered are cohesive. – Gaurav Feb 25 '22 at 06:37
  • I'm not an advanced developer. I do not know how to combine Angular and Java into one package. Where can I read about it or see examples of how it is done? I planned to run it all in Docker. Angular in one container. Java (jar file) in another container. – alex Feb 25 '22 at 07:09
  • 1
    I recently answered another question here slighly off topic but contains relevant configuration to bundle angular and java into one. https://stackoverflow.com/a/71262067/1988798 By the way, given two docker containers, you will be required to CORS. Just to let you know in case you don't know, you never deploy angular code directly. You build the final dist via - `ng build --prod` and then deploy the resultant code in dist/project-name-directory on a web server. `ng serve` is a development time only server without any production optimizations. – Gaurav Feb 25 '22 at 07:15
  • I tried to do as it says here, but it's still a mistake. Probably something else is the problem – alex Mar 01 '22 at 15:26
  • Hi Alex. Set up a GitHub repo and let me check. – Gaurav Mar 01 '22 at 21:44
  • I added a link to the repository – alex Mar 02 '22 at 08:18
  • I have initiated a pull request. Please check. – Gaurav Mar 02 '22 at 19:17
  • I ran your code. There is no CORS error. But now there is a 403 error. – alex Mar 03 '22 at 16:25
  • When trying to access the POST http://localhost:15001/auth/api/v1/user/register I get 403. Although it states that access is allowed to all users – alex Mar 03 '22 at 16:40
  • Can you create another issue for it ? Most probably your are running into CSRF issue. Just try with http.csrf().disable() in your security config. – Gaurav Mar 03 '22 at 17:26
  • And please accept the answer if it answers your question. – Gaurav Mar 03 '22 at 17:26
  • http.csrf().disable() is installed on me. This can be seen in my sources. OK, I will create a new question on this topic. – alex Mar 04 '22 at 05:26
0

You configuration should be like this.

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("*"));
    configuration.setAllowCredentials(true);
    configuration.setAllowedHeaders(Arrays.asList("Access-Control-Allow-Headers", "Access-Control-Allow-Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", "Origin", "Cache-Control", "Content-Type", "Authorization"));
    configuration.setAllowedMethods(Arrays.asList("DELETE", "GET", "POST", "PATCH", "PUT"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}
Sanjay
  • 2,481
  • 1
  • 13
  • 28
0

you have to update your configure method with this :

Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable()
                .**cors().disable().and().csrf().disable()**
                .
                .
                .
    }
Kıvılcım
  • 391
  • 3
  • 8