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.