Spring nowa sesja na każdy request

0

Piszę restową logikę logowania w Springu Security.
Wszystko działa poprawnie kiedy korzystam z postmana jadnak są problemy kiedy requesty są wysyłane z frontendu (javascript fetchApi)

Na początku wyłączyłem formularz logowania udostępniany przez springa kozystając z tego poradnika: https://nullpointerexception.pl/spring-security-uwierzytelnienie-przy-pomocy-jsona/ zmieniłem jedynie datasource na jdbc.
Obecnie spring dalej wystawia endpoint /login z którego korzystam.

Kiedy wysyłam requesty postmanem spring najpierw tworzy sesje a kolejne requesty wykonywane są na niej.
Zapytanie po zalogowaniu wykonuje się prawidłowo.

spring security debug:

**Request 1**

Request received for POST '/login':

org.apache.catalina.connector.RequestFacade@41847bfc

servletPath:/login
pathInfo:null
headers: 
content-type: application/json
user-agent: PostmanRuntime/7.26.8
accept: */*
postman-token: 9d7d27fb-8d06-4259-8d47-99751bee367a
host: localhost:8080
accept-encoding: gzip, deflate, br
connection: keep-alive
content-length: 47
cookie: JSESSIONID=9AD55BC610CF3C4FB698DCF9B79A87E1

Hibernate: select employee0_.id as id1_0_, employee0_.enabled as enabled2_0_, employee0_.name as name3_0_, employee0_.password as password4_0_, employee0_.phone_number as phone_nu5_0_, employee0_.position as position6_0_, employee0_.surname as surname7_0_, employee0_.username as username8_0_ from employee employee0_ where employee0_.username like ?
2021-05-06 18:35:20.602  INFO 17220 --- [nio-8080-exec-1] Spring Security Debugger   

New HTTP session created: EA08877AE6F40A62A509D4C60F9942C6

**Request 2**

Request received for POST '/createNewEmployee':

org.apache.catalina.connector.RequestFacade@41847bfc

servletPath:/createNewEmployee
pathInfo:null
headers: 
content-type: application/json
user-agent: PostmanRuntime/7.26.8
accept: */*
postman-token: 5abb6ac3-429c-4615-bbf3-7bd4f2603628
host: localhost:8080
accept-encoding: gzip, deflate, br
connection: keep-alive
content-length: 124
cookie: JSESSIONID=EA08877AE6F40A62A509D4C60F9942C6

Hibernate: insert into employee (enabled, name, password, phone_number, position, surname, username) values (?, ?, ?, ?, ?, ?, ?)

Kiedy requesty wychodzą z frontendu za każdym razem tworzona jest nowa sesja.
Logowanie wykonuje się pomyślnie ale na endpoincie wymagającym bycia zalogowanym (createNewEmployee) dostaje http: 401.

**Request 1**

Request received for POST '/login':

org.apache.catalina.connector.RequestFacade@6a73369

servletPath:/login
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0
accept: */*
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
referer: http://127.0.0.1:3000/
content-type: application/json
origin: http://127.0.0.1:3000
content-length: 36
connection: keep-alive

Hibernate: select employee0_.id as id1_0_, employee0_.enabled as enabled2_0_, employee0_.name as name3_0_, employee0_.password as password4_0_, employee0_.phone_number as phone_nu5_0_, employee0_.position as position6_0_, employee0_.surname as surname7_0_, employee0_.username as username8_0_ from employee employee0_ where employee0_.username like ?
2021-05-06 18:32:37.731  INFO 17112 --- [nio-8080-exec-1] Spring Security Debugger         

New HTTP session created: 04C83E20CBAAB357CB03DF7C6FE0165E

**Request 2**

Request received for POST '/createNewEmployee':

org.apache.catalina.connector.RequestFacade@6a73369

servletPath:/createNewEmployee
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0
accept: */*
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
referer: http://127.0.0.1:3000/
content-type: application/json
origin: http://127.0.0.1:3000
content-length: 91
connection: keep-alive

New HTTP session created: F745FFEB3400D3B028AFFCACA41E5BFB

W podobnym temacie https://stackoverflow.com/questions/42710057/fetch-cannot-set-cookies-received-from-the-server znalazłem informację że bez parametru credentials: 'include' w header requestu, pliki cookie nie mogą być przesyłane między aplikacjami (frontend mam na localhost:3000 a backend na localhost:8080).
Problem w tym że jak dodaje "credentials: 'include'" w header to dostaje "Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘http://localhost:8080/login’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’)"

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    UserDetailsService userDetailsService;
    AuthenticationSuccessHandler authenticationSuccessHandler;
    AuthenticationFailureHandler authenticationFailureHandler;

    public SecurityConfig(UserDetailsService userDetailsService, AuthenticationSuccessHandler authenticationSuccessHandler, AuthenticationFailureHandler authenticationFailureHandler) {
        this.userDetailsService = userDetailsService;
        this.authenticationSuccessHandler = authenticationSuccessHandler;
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
            http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
            http.csrf().disable();
            http.authorizeRequests()
                    .antMatchers(HttpMethod.GET, "/").permitAll()
                    .antMatchers(HttpMethod.POST, "/login").permitAll()
                    .antMatchers(HttpMethod.GET, "/secured").authenticated()
                    .antMatchers(HttpMethod.POST, "/createNewEmployee").authenticated()
                    .antMatchers(HttpMethod.GET,"/getListOfEmployees").hasRole("ADMIN")
                    .and()
                    .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                    .exceptionHandling()
                    .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
    }

    @Bean
    public JsonAuthenticationFilter authenticationFilter() throws Exception {
        JsonAuthenticationFilter jsonFilter = new JsonAuthenticationFilter();
        jsonFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        jsonFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
        jsonFilter.setAuthenticationManager(super.authenticationManager());
        return jsonFilter;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Nie wiem czy można dodać CrossOrgin() dla endpointu /login bo spring wystawia go domyślnie więc próbowałem dodać ustawienia globalnie korzystając z tego wpisu: https://docs.spring.io/spring-security/site/docs/4.2.19.BUILD-SNAPSHOT/reference/html/cors.html

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    UserDetailsService userDetailsService;
    AuthenticationSuccessHandler authenticationSuccessHandler;
    AuthenticationFailureHandler authenticationFailureHandler;

    public SecurityConfig(UserDetailsService userDetailsService, AuthenticationSuccessHandler authenticationSuccessHandler, AuthenticationFailureHandler authenticationFailureHandler) {
        this.userDetailsService = userDetailsService;
        this.authenticationSuccessHandler = authenticationSuccessHandler;
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
            //http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
            http.cors().and().csrf().disable();
            http.authorizeRequests()
                    .antMatchers(HttpMethod.GET, "/").permitAll()
                    .antMatchers(HttpMethod.POST, "/login").permitAll()
                    .antMatchers(HttpMethod.GET, "/secured").authenticated()
                    .antMatchers(HttpMethod.POST, "/createNewEmployee").authenticated()
                    .antMatchers(HttpMethod.GET,"/getListOfEmployees").hasRole("ADMIN")
                    .and()
                    .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                    .exceptionHandling()
                    .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
    }

    @Bean
    public JsonAuthenticationFilter authenticationFilter() throws Exception {
        JsonAuthenticationFilter jsonFilter = new JsonAuthenticationFilter();
        jsonFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        jsonFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
        jsonFilter.setAuthenticationManager(super.authenticationManager());
        return jsonFilter;
    }

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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Tym razem przy tak wyglądającym SecurityConfig dostaje http: 403, Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/login. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)

Pokażę jeszcze motodę wywołującą fetch.

async function loginRequest(loginCredentials) {
  const response = await fetch("http://localhost:8080/login", {
    method: "POST",
    credentials: 'include',
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(loginCredentials)
  });
  return response;
}

Jak można skonfigurować springa żeby akceptował takie requesty?
Proszę o pomoc.

0

Zmieniłem trochę beana CorsConfigurationSource.

@Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://127.0.0.1:3000"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

Teraz dostaje komunikat "Cookie “JSESSIONID” will be soon rejected because it has the “SameSite” attribute set to “None” or an invalid value, without the “secure” attribute. To know more about the “SameSite“ attribute, read https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite" ale przestało krzyczeć o Access-Control-Allow-Origin.

Jak można w springu wysłać w response cookie o właściwościach: sameSite: 'none', secure: true?

0

Wygląda na to że komunikat "Cookie “JSESSIONID” will be soon rejected because it has the “SameSite” attribute set to “None” or an invalid value, without the “secure” attribute. nie przeszkadza (przynajmniej na ten moment) w działaniu i wszystko na ten moment wydaje się działać dobrze :)

1

Siema, zrób proxowanie na aplikacji frontendowej. Nie będziesz miał problemów z corsami
Poniżej przykład:
Apache -> httpd.conf

  ProxyPreserveHost on

	ProxyPass / http://apkaBackendowa:PORT/
	ProxyPassReverse / http://apkaBackendowa:PORT/

Te dwa moduły musisz odkomentować
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

Jak robisz frontend w react możesz sobie dodać proxy na czas developmentu lokalnie w
package.json

{
  "name": "Nazwa projektu",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://apkBackendowa:PORT",   <----
}

jak lokalnie odpalasz backed to pewnie

  "proxy": "http://localhost:PORT", 

1 użytkowników online, w tym zalogowanych: 0, gości: 1