so I was setting up security for my API where you have to log in with username/password obtain JWT token and then use that for everything else.
This is how I set up SecurityWebFilterChain:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/login", "/")
.permitAll()
.and()
.addFilterAt(UsernamePasswordAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.pathMatchers("/api/**")
.authenticated()
.and()
.addFilterAt(bearerAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION);
return http.build();
}
as you can see here, I set up two custom filters. One for username/password and another for Bearer token (JWT).
This is how I set up UsernamePassword filter, where I provide my custom authManager with custom UserDetails service, encoder and successhandler:
private AuthenticationWebFilter UsernamePasswordAuthenticationFilter() {
UserDetailsRepositoryReactiveAuthenticationManager authManager;
AuthenticationWebFilter usernamePasswordAuthenticationFilter;
ServerAuthenticationSuccessHandler successHandler;
authManager = new UserDetailsRepositoryReactiveAuthenticationManager(applicationUserService);
authManager.setPasswordEncoder(passwordEncoder);
successHandler = new AuthenticationSuccessHandler();
usernamePasswordAuthenticationFilter = new AuthenticationWebFilter(authManager);
usernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
return usernamePasswordAuthenticationFilter;
}
This actually works, I can issue GET request with curl or Postman like this:
curl -v -u username:password localhost:8080/login
and get my JWT token back. awesome. Just one problem. By default this filter uses
ServerHttpBasicAuthenticationConverter
What I actually want:
i need to send POST request with username and password in request body in simple JSON structure, like this:
{
"username":"usr"
"password":"pass"
}
and use that to create Authentication object.
This is what I tried:
I created and added custom Converter to my AuthenticationFilter:
usernamePasswordAuthenticationFilter.setServerAuthenticationConverter(new UsernamePasswordAuthenticationConverter());
UsernamePasswordAuthenticationConverter:
public class UsernamePasswordAuthenticationConverter implements ServerAuthenticationConverter {
#Override
public Mono<Authentication> convert(ServerWebExchange exchange) {
...
}
}
This is where Im stuck for hours.
Question1
How do I get username/password from JSON body of request and produce Mono<Authentication> here? I'm pretty new to reactive programming, sorry if this is stupid question.
Question2
Only GET requests seesm to even reach this custom UsernamePasswordAuthenticationConverter. Why? any POST requests get rejected before that and I don't understand why.
Related
I am pretty new to Spring-boot,
I am building micro-service which will simply forward request to other system for processing (JSON to XML).
For this, along with request I need to set username and password, so as I far I Googled I found below snippet only.
#Configuration
#EnableWebSecurity
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private MyBasicAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user1").password(passwordEncoder().encode("user1Pass"))
.authorities("ROLE_USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/securityNone").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
http.addFilterAfter(new CustomFilter(),
BasicAuthenticationFilter.class);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Here, it is hard coded, we don't require this, also don't want to maintain database. I am trying to get username and password of Auth tab of Postman (Basic Authorization) in controller.
So that, we can simply forward it with request.
The code snippet you gave is for the use-case, that your service authenticates the user.
But as I understood you just want to forward the credentials to another service and let the downstream service handle the authentication.
So, the credentials are sent with the "Authorization" HTTP header [1].
If you want to access them, you can simply get it from the request (HttpServletRequest.java [2,3]) like below:
public ResponseEntity<DemoClass> getDemo(HttpServletRequest request) {
final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
// ...
The value you get is Base64[4] encoded, so you have to decode it.
For example, basic authorization with username "username" and password "password" looks like that:
Basic dXNlcm5hbWU6cGFzc3dvcmQ=
First, the prefix "Basic" has to be removed and then you have just the username and password Base64-encoded.
After decoding it is:
username:password
A simpler approach would be to just take the Authorization header from the user request and put it into your request.
For example, like below:
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
Request request = new Request.Builder()
.url("http://localhost:8080/")
.method("GET", null)
.addHeader("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=") // Client credentials from the header
.build();
Note: I just took that from the postman example (Java - OkHttp).
For more information:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html
https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html#getHeader-java.lang.String-
https://en.wikipedia.org/wiki/Base64
I achieved it in following simple way.
import org.apache.commons.codec.binary.Base64;
#PostMapping("/servce-uri")
public ResponseEntity<Result> announce(#RequestHeader("Authorization") String authentication,#Valid #RequestBody RequestDTO requestDto) {
String pair=new String(Base64.decodeBase64(authentication.substring(6)));
String userName=pair.split(":")[0];
String password=pair.split(":")[1];
// call to service
return ResponseEntity.status(HttpStatus.OK).body(result);
}
I've looked at pretty much all of the blogs and SO posts on this topic and I'm not seeing a solution. I have a WebSecurityConfigurerAdapter that looks like this:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
String issuerUri = "issuer url";
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers(HttpMethod.GET, "/test").hasRole("Task.Write")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer
.jwt(jwt ->
jwt.decoder(JwtDecoders.fromIssuerLocation(issuerUri))
)
);
}
I have a controller that looks like this:
#GetMapping(value="/test")
public ApplicationResponse test(#AuthenticationPrincipal Jwt jwt) {
// Map<String, Object> x = jwt.getClaims();
return new ApplicationResponse("ok", "ok");
}
However when I hit this endpoint with Postman with a valid JWT I get a 403 error. I have tried prefixing the role with ROLE_ and Role_ and tried numerous other things but it always is 403.
What's weird too is if I do a permitAll() instead of the authenticate and let the JWT go through. I can look into the JWT object in the controller. I see that the role is there. So why does this WebSecurityConfigurerAdapter always throw a 403 when the JWT is valid and the role is there?
I noticed that the roles are located in the claim in the JWT. Maybe I need to get it from here? I don't see how to get roles from claims in the configure method:
By default, Spring Security converts the items in the scope or scp claim and uses the SCOPE_ prefix. You can change both conventions by defining a custom JwtAuthenticationConverter bean.
To export authorities from a roles scope and use the ROLE_ prefix, you can define the following converter, so that you can use methods like hasRole("Task_Write").
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
I am developing a microservice ecosystem using spring-boot. The microservices which are in place at the moment :
Spring Cloud Gateway - Zuul (responsible also for authorization requests downstream for microservices - extracting tokens from requests and validates whether the user has the right role to perform requests),
SSO using spring security LDAP ( responsible for authenticate user and generate JWT tokens) , SSO has also just a login page using thymeleaf
Web interface using Thymeleaf without login page ( not sure if I should use here spring security, at the moment)
Another microservice which provides data to web ui based on request from the browser
Discovery services using Eureka
The idea is filtering all the requests on the gateway for validating and forward the requests. If the user is not authenticated or token is experied then forward the user to SSO for login.
The firewall will expose only the port on Gateway side then others one will be theirs ports blocked using firewall rules.
Now i am blocked without knowing where to go or if I should move the SSO together with the gateway ( conceptually wrong but it might be a workaround if i do not find any solution)
Following the issue : The user hits the gateway (ex. http://localhost:7070/web) then the gateway forward the user to (ex. http://localhost:8080/sso/login), after the credentials have been validated , the SSO creates the JWT tokens and add it to the Header of response.
Afterwards the SSO redirect the request back to the gateway (ex. http://localhost:7070/web).
Until here, everything works fine but when the request reaches the gateway there is no 'Authorization' header on request which means NO JWT token.
So the gateway should extract the token, check the credentials and forward the request to the Web interface (ex. http://localhost:9090)
I am aware that using Handler on SSO to redirect request won't work at all due to 'Redirect' from spring will remove the token from the header before redirect.
But I do not know whether there is another way to set again the JWT on the header after Spring has removed it from the request or not.
Is there any conceptually issue on the architecture side? How can I forward the JWT to the gateway for being checked?
SSO
#EnableWebSecurity
public class SecurityCredentialsConfig extends WebSecurityConfigurerAdapter {
#Value("${ldap.url}")
private String ldapUrl;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
// Stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.loginPage("/login")
// Add a handler to add token in the response header and forward the response
.successHandler(jwtAuthenticationSuccessHandler())
.failureUrl("/login?error")
.permitAll()
.and()
// handle an authorized attempts
.exceptionHandling()
.accessDeniedPage("/login?error")
.and()
.authorizeRequests()
.antMatchers( "/dist/**", "/plugins/**").permitAll()
.anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.userSearchFilter("uid={0}")
.groupSearchBase("ou=groups")
.groupSearchFilter("uniqueMember={0}")
.contextSource()
.url(ldapUrl);
}
#Bean
public AuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
return new JwtAuthenticationSuccessHandler();
}
}
public class JwtAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Autowired
private JwtConfig jwtConfig;
#Autowired
private JwtTokenService jwtTokenService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
String token = jwtTokenService.expiring(ImmutableMap.of(
"email", auth.getName(),
"authorities", auth.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.map(Object::toString)
.collect(Collectors.joining(","))));
response.addHeader(jwtConfig.getHeader(), jwtConfig.getPrefix() + token);
DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if(defaultSavedRequest != null){
getRedirectStrategy().sendRedirect(request, response, defaultSavedRequest.getRedirectUrl());
}else{
getRedirectStrategy().sendRedirect(request, response, "http://localhost:7070/web");
}
}
}
Gateway
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtConfig jwtConfig;
#Value("${accessDeniedPage.url}")
private String accessDeniedUrl;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable() // Disable CSRF (cross site request forgery)
// we use stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.loginPage("/sso/login")
.permitAll()
.and()
// handle an authorized attempts
// If a user try to access a resource without having enough permissions
.exceptionHandling()
.accessDeniedPage(accessDeniedUrl)
//.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
// Add a filter to validate the tokens with every request
.addFilterBefore(new JwtTokenAuthenticationFilter(jwtConfig), UsernamePasswordAuthenticationFilter.class)
// authorization requests config
.authorizeRequests()
.antMatchers("/web/**").hasAuthority("ADMIN")
// Any other request must be authenticated
.anyRequest().authenticated();
}
}
#RequiredArgsConstructor
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 1. get the authentication header. Tokens are supposed to be passed in the authentication header
String header = request.getHeader(jwtConfig.getHeader());
// 2. validate the header and check the prefix
if(header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request, response); // If not valid, go to the next filter.
return;
}
// If there is no token provided and hence the user won't be authenticated.
// It's Ok. Maybe the user accessing a public path or asking for a token.
// All secured paths that needs a token are already defined and secured in config class.
// And If user tried to access without access token, then he/she won't be authenticated and an exception will be thrown.
// 3. Get the token
String token = header.replace(jwtConfig.getPrefix(), "");
try { // exceptions might be thrown in creating the claims if for example the token is expired
// 4. Validate the token
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
String email = claims.get("email").toString();
if(email != null) {
String[] authorities = ((String) claims.get("authorities")).split(",");
final List<String> listAuthorities = Arrays.stream(authorities).collect(Collectors.toList());
// 5. Create auth object
// UsernamePasswordAuthenticationToken: A built-in object, used by spring to represent the current authenticated / being authenticated user.
// It needs a list of authorities, which has type of GrantedAuthority interface, where SimpleGrantedAuthority is an implementation of that interface
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
email, null, listAuthorities
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()));
// 6. Authenticate the user
// Now, user is authenticated
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
// In case of failure. Make sure it's clear; so guarantee user won't be authenticated
SecurityContextHolder.clearContext();
}
// go to the next filter in the filter chain
chain.doFilter(request, response);
}
}
#Component
public class AuthenticatedFilter extends ZuulFilter {
#Override
public String filterType() {
return PRE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
final Object object = SecurityContextHolder.getContext().getAuthentication();
if (object == null || !(object instanceof UsernamePasswordAuthenticationToken)) {
return null;
}
final UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
final RequestContext requestContext = RequestContext.getCurrentContext();
/*
final AuthenticationDto authenticationDto = new AuthenticationDto();
authenticationDto.setEmail(user.getPrincipal().toString());
authenticationDto.setAuthenticated(true);
authenticationDto.setRoles(user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList())); */
try {
//requestContext.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, (new ObjectMapper()).writeValueAsString(authenticationDto));
requestContext.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, (new ObjectMapper()).writeValueAsString("authenticationDto"));
} catch (JsonProcessingException e) {
throw new ZuulException("Error on JSON processing", 500, "Parsing JSON");
}
return null;
}
}
There is an issue about JWT. It is called "Logout Problem". First you need to understand what it is.
Then, check TokenRelay filter (TokenRelayGatewayFilterFactory) which is responsible for passing authorization header to downstream.
If you look at that filter, you will see that JWTs are stored in ConcurrentHashMap (InMemoryReactiveOAuth2AuthorizedClientService). The key is session, the value is JWT. So, session-id is returned instead of JWT header as the response provided.
Until here, everything works fine but when the request reaches the
gateway there is no 'Authorization' header on request which means NO
JWT token.
Yes. When the request comes to gateway, TokenRelay filter takes session-id from request and find JWT from ConcurrentHashMap, then it passes to Authorization header during downstream.
Probably, this flow is designed by spring security team to address JWT logout problem.
I expose to you my problem.
I have a webapp that call a REST API to find the cart of the client.
The application and the API are secured with spring security and a SSO Keycloak.
Actually, my webapp is functional, if my don't protect my api, all are ready.
But when i want to secure my api with role, i have every time an error 401 error: unauthorized. In fact is good, my api is secured, but the client that have role "USER" can't access to his cart.
When i attempt to take my bearer token to keycloak, is a good token (past in jwt.io). I attempted to use curl, but the result is same.
I use Feign in my webapp to call the API.
My Keycloak configuration in my api
#Configuration
#EnableWebSecurity
#ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public static class KeycloakConfigurationAdapter extends KeycloakWebSecurityConfigurerAdapter{
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakConfigResolver KeycloakConfigResolver(){
return new KeycloakSpringBootConfigResolver();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
.addFilterBefore(keycloakAuthenticationProcessingFilter(), X509AuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/paniers/**").hasAuthority("USER")
.antMatchers("/commandes/**").hasRole("USER")
.anyRequest().permitAll();
}
}
My .properties for API
keycloak.auth-server-url=http://myserver:port/auth
keycloak.realm=wild_adventures
keycloak.resource=ms-commande
keycloak.credentials.secret=#######-####-####-####-###########
keycloak.bearer-only=true
My method in the controller of the api
#ApiOperation(value = "Récupère le panier avec la liste des évenements réservés ou renvoie un 404 NotFound")
#GetMapping(value = "paniers/{clientUuid}")
public Panier recupererPanier(#PathVariable(value = "clientUuid") String clientUuid) {
Panier panier = this.panierManager.getPanierByClientUuid(clientUuid);
if(panier == null)
throw new PanierInexistantException("Le panier n'a pas été trouvé");
return panier;
}
Thx for your help.
I noticed, that you mixed hasAuthority and hasRole methods in your security config. Should be either hasAuthority("ROLE_USER") or hasRole("USER"). Have you checked if "/commandes/**" resource works for you?
Ok after some research, i have find to reason of my problem.
First, Zuul secure the Authorization header of my request...
To solve that, i added this line in my properties "zuul.sensitiveHeaders: Cookie, Set-Cookie" (So, Zuul just secure Cookie and Set-Cookie).
Next, in my code i send the good token, but i needed to precise "Bearer " before my token string.
Maybe that solution can help someone :)
I am using spring security along with java config
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/*").hasRole("ADMIN")
.and()
.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(new SimpleUrlAuthenticationFailureHandler());
I am using PostMan for testing my REST services. I get 'csrf token' successfully and I am able to login by using X-CSRF-TOKEN in request header. But after login when i hit post request(I am including same token in request header that i used for login post request) I get the following error message:
HTTP Status 403 - Could not verify the provided CSRF token because your session was not found.
Can any one guide me what I am doing wrong.
According to spring.io:
When should you use CSRF protection? Our recommendation is to use CSRF
protection for any request that could be processed by a browser by
normal users. If you are only creating a service that is used by
non-browser clients, you will likely want to disable CSRF protection.
So to disable it:
#Configuration
public class RestSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
Note: CSRF protection is enabled by default with Java Configuration
try this: #Override protected boolean sameOriginDisabled() { return true;}
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
...
// Determines if a CSRF token is required for connecting. This protects against remote
// sites from connecting to the application and being able to read/write data over the
// connection. The default is false (the token is required).
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
source: WebSocket Security: Disable CSRF within WebSockets
Disabling CSRF protection is a bad idea.
Spring will automatically generate a new CSRF token after each request, and you need to include it in all HTTP requests with side-effects (PUT, POST, PATCH, DELETE).
In Postman you can use a test in each request to store the CSRF token in a global, e.g. when using CookieCsrfTokenRepository
pm.globals.set("xsrf-token", postman.getResponseCookie("XSRF-TOKEN").value);
And then include it as a header with key X-XSRF-TOKEN and value {{xsrf-token}}.
Came to same error just with POST methods, was getting 403 Forbidden "Could not verify the provided CSRF token because your session was not found."
After exploring some time found solution by adding #EnableResourceServer annotation to config.
Config looks like that (spring-boot.version -> 1.4.1.RELEASE, spring-security.version -> 4.1.3.RELEASE, spring.version -> 4.3.4.RELEASE)
#Configuration
#EnableWebSecurity
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(inMemoryUserDetailsManager()).passwordEncoder(passwordEncoder());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.csrf().disable();
http.authorizeRequests().anyRequest()
.permitAll();
}
private InMemoryUserDetailsManager inMemoryUserDetailsManager() throws IOException {
// load custom properties
Properties properties = new Properties();
return new InMemoryUserDetailsManager(properties);
}
private PasswordEncoder passwordEncoder() {
return new TextEncryptorBasedPasswordEncoder(textEncryptor());
}
private TextEncryptor textEncryptor() {
return new OpenSslCompatibleTextEncryptor();
}
}
I get this error message (HTTP Status 403 - Could not verify the provided CSRF token because your session was not found.) when I do a JS fetch AJAX call without using the credentials: "same-origin" option.
Wrong way
fetch(url)
.then(function (response) { return response.json(); })
.then(function (data) { console.log(data); })
Correct way
fetch(url, {
credentials: "same-origin"
})
.then(function (response) { return response.json(); })
.then(function (data) { console.log(data); })
This is an old question but this might help someone. I had the similar issue and this is how I was able to resolve it.
In order for the CSRF to work with the REST API you need to obtain a CSRF token via API before every single call and use that token. Token is different every time and cannot be re-used.
Here is the controller to get the CSRF token:
#RequestMapping(value = "/csrf", method = RequestMethod.GET)
public ResponseEntity<CSRFDTO> getCsrfToken(HttpServletRequest request) {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
return ResponseEntity.ok(CSRFDTO.builder()
.headerName(csrf.getHeaderName())
.token(csrf.getToken())
.build());
}
Additionally, you might consider configuring your Spring app to disable the CSRF for the REST API endpoints. To quote an article I've read somewhere:
I'm very certain that CSRF tokens on a REST endpoint grant zero additional protection. As such, enabling CSRF protection on a REST endpoint just introduces some useless code to your application, and I think it should be skipped.
Hope this helps.
I have solved it by adding the last attribute in my login page,maybe it will do yo a favor.
<%# page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false"%>