Spring boot - find username from session cookie value - java

I am currently working on a spring boot application, and I've got a handler which takes the HttpServletRequest as an argument.
I was wondering, is it possible to invoke a bean that - provided the session cookie - can return the information of who made the request? (e.g. username)

In the end I found this way to make the code work in the desired way.
package org.my.package;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
#Slf4j
#Configuration
public class CsrfDeniedHandlerConfig {
#AllArgsConstructor
static class CsrfDeniedHandler implements AccessDeniedHandler {
private final AccessDeniedHandler accessDeniedHandler;
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
if (accessDeniedException.getClass().equals(InvalidCsrfTokenException.class)) {
SecurityContextImpl securityContext = (SecurityContextImpl) request.getSession().getAttribute(SPRING_SECURITY_CONTEXT_KEY);
User user = (User) securityContext.getAuthentication().getPrincipal();
log.error("Invalid CSRF token request from {}: {}", user.getUsername().toUpperCase(), accessDeniedException.getMessage());
}
accessDeniedHandler.handle(request, response, accessDeniedException);
}
}
#Bean
public AccessDeniedHandler csrfDeniedHandler() {
return new CsrfDeniedHandler(new AccessDeniedHandlerImpl());
}
}

Related

problems with JWT Authentication using Spring Security

I'm trying to set up JWT Authentication
I've been following this https://www.freecodecamp.org/news/how-to-setup-jwt-authorization-and-authentication-in-spring/
I can successfully create new user, problems start when I try to log in......
I keep getting response status 403 Forbidden
JWTAuthenticationFilter
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.flashcards.entities.UserEntity;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
setFilterProcessesUrl("/login");
}
u/Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
System.out.println(req);
try {
UserEntity creds = new ObjectMapper().readValue(req.getInputStream(), UserEntity.class);
System.out.println(creds);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(),
creds.getPassword(), new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
u/Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException {
String token = JWT.create().withSubject(((UserEntity) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.sign(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes()));
String body = ((UserEntity) auth.getPrincipal()).getUsername() + " " + token;
System.out.println(body);
res.getWriter().write(body);
res.getWriter().flush();
}
}
JWTAuthorizationFilter
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
u/Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader(SecurityConstants.HEADER_STRING);
if (header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
// Reads the JWT from the Authorization header, and then uses JWT to validate
// the token
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(SecurityConstants.HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes())).build()
.verify(token.replace(SecurityConstants.TOKEN_PREFIX, "")).getSubject();
if (user != null) {
// new arraylist means authorities
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
WebSecurity
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
u/SuppressWarnings("deprecation")
u/EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserDetailsService userService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
u/Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and().authorizeRequests().antMatchers( HttpMethod.POST, "/users/**").permitAll()
.anyRequest().authenticated().and().addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
u/Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
u/Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}
UserController
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.flashcards.entities.UserEntity;
import com.example.flashcards.repositories.UserRepository;
u/CrossOrigin
u/RestController
u/RequestMapping("/users")
public class UserController {
u/Autowired
private UserRepository userRepo;
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// u/Transactional(rollbackFor = Exception.class)
u/PostMapping("/new")
public Long saveUser(#RequestBody UserEntity user) {
UserEntity newUser = user;
newUser.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepo.save(newUser).getUserId();
}
}
when I send POST request with JSON body with user info to http://localhost:8080/users/new everything is peachy, new user gets created and saved to database
when I send POST request with JSON body with username and password to http://localhost:8080/login I keep getting 403 Forbidden
If anyone could point me in the right direction it would be greatly appreciated!
I'm a beginner and this is my very first time dealing with Spring Security.
Thank you for the hint ;)
I got it to work now. The problem was that in JWTAuthenticationFilter, in successfulAuthentication method I was trying to use my UserEntity when it was supposed to be User from Spring's UserServiceDetail once I made that change it started working :)

Spring Boot test Filter

NOTE: the errors below were actually caused by a typo in the filter, see #jccampanero mentions in his answer below
I've been trying a couple different approaches to testing a Filter however I'm continuously getting one error or another so I'm hoping for some direction.
Here's a dummy Filter that should just do a redirect.
package org.example.filters;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Component
public class RedirectFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) req;
chain.sendRedirect("/splash");
}
}
And a basic Spring Boot Application class:
package org.example;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class ApplicationClass {
}
I think I'm just not clear on what 'level' of Spring Boot Test I'm trying to do, here's some attempts I've done and the errors:
Option 1. Trying with mocks, for example from here
package org.example.filters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
public class LocaleFilterIntegrationTestStandalone {
#Test
public void whenNoLocaleRedirectToSplash() throws Exception {
standaloneSetup(new TestController()).addFilters(new RedirectFilter()).build().perform(get("/"))
.andExpect(status().isFound()).andExpect(redirectedUrl("/splash"));
}
#Controller
private static class TestController {
#GetMapping("/")
public String get() {
return "got it";
}
}
}
Error: java.lang.ClassCastException: class org.springframework.mock.web.MockHttpServletRequest cannot be cast to class javax.servlet.http.HttpServletResponse
Option 2, try using #WebMvcTest, which has the same issue as Option 1
package org.example.filters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
#WebMvcTest
public class LocaleFilterIntegrationTestWebMvc {
#Autowired
private MockMvc mvc;
#Test
public void noLanguageShouldRedirectToSplash() throws Exception {
mvc.perform(get("/")).andExpect(status().isFound()).andExpect(redirectedUrl("/splash"));
}
#Controller
private static class TestController {
#GetMapping("/")
public String get() {
return "got it";
}
}
}
Error: java.lang.ClassCastException: class org.springframework.mock.web.MockHttpServletRequest cannot be cast to class javax.servlet.http.HttpServletResponse
Option 3, try booting the whole context, which I think is required to be able to cast to HttpServletRequest:
package org.example.filters;
import java.net.URI;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class LocaleFilterIntegrationTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void noLanguageShouldRedirectToSplash() throws Exception {
URI uri = new URI("http", "localhost:" + port, "/", null, null);
String result = restTemplate.getForObject(uri.toString(), String.class);
// not sure how, but test for redirect...
}
#Controller
private static class TestController {
#GetMapping("/")
public String get() {
return "got it";
}
}
}
Error: class org.apache.catalina.connector.RequestFacade cannot be cast to class javax.servlet.http.HttpServletResponse
Option 4, suggested by #m-deinem
package org.example.filters;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
public class LocaleFilterIntegrationTestPlain {
private final RedirectFilter redirectFilter = new RedirectFilter();
#Test
public void noLanguageShouldRedirectToSplash() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain();
redirectFilter.doFilter(req, res, chain);
}
}
Error: java.lang.ClassCastException: class org.springframework.mock.web.MockHttpServletRequest cannot be cast to class javax.servlet.http.HttpServletResponse
First your RedirectFilter contains an error it tries to cast the incoming HttpServletRequest to a HttpServletResponse as you are using the wrong variable.
#Component
public class RedirectFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
res.sendRedirect("/splash");
}
}
Both lines use req whereas the latter should use res (which is also what the exception is quite literally telling you).
Next just write a simple unit test, you are making things overly complex by trying to write integration tests.
public class RedirectFilterTest
private final RedirectFilter filter = new RedirectFilter();
#Test
public void redirectTest() {
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain();
filter.doFilter(req, res, chain);
assertEquals("/splash", res.getRedirectedUrl());
}
Please, maybe it is just a typo, but be aware that in your RedirectFilter doFilter method you are incorrectly casting the variable req to HttpServletResponse:
package org.example.filters;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Component
public class RedirectFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
// Please, note the incorrect cast
HttpServletResponse response = (HttpServletResponse) req;
chain.sendRedirect("/splash");
}
}
Instead, it should be:
package org.example.filters;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Component
public class RedirectFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
chain.sendRedirect("/splash");
}
}
It can be very likely the reason of the ClassCastException and the failing tests.
Once solved, the solution proposed by #M. Deinum is a very good and straightforward one.

Why is Angular not sending cookies with a POST request even though I've set withCredentials to true?

I have edited the question to make more sense. The original was:
How can I fix Angular and Spring Boot configuration with Spring JDBC authentication so that I am able to logout even with CSRF protection enabled?
With CSRF disabled for /logout:
I am able to login (receiving CSRF and JSESSIONID cookies) and logout (200 OK is received) using Postman.
I am able to login (receiving CSRF and JSESSIONID cookies) and logout (200 OK is received) using Firefox and the Angular frontend.
With CSRF enabled for /logout:
I am able to login (receiving CSRF and JSESSIONID cookies) and logout (200 OK is received) using Postman.
I am able to login using Firefox and the Angular frontend.
When trying to log out, however...
There is first a preflight request that succeeds:
Then, I see a request to /logout:
I debugged the backend and it seems Spring is unable to find a matching CSRF token in its TokenRepository. So I end up with a MissingCsrfTokenException and a 403 Forbidden. How can I fix that?
Backend:
SecurityConfiguration:
package org.adventure.configuration;
import org.adventure.security.RESTAuthenticationEntryPoint;
import org.adventure.security.RESTAuthenticationFailureHandler;
import org.adventure.security.RESTAuthenticationSuccessHandler;
import org.adventure.security.RESTLogoutSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTLogoutSuccessHandler restLogoutSuccessHandler;
#Autowired
private DataSource dataSource;
#Autowired
private AccountsProperties accountsProperties;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/h2-console/**")
.permitAll();
httpSecurity.authorizeRequests().antMatchers("/secure/**").authenticated();
httpSecurity.cors().configurationSource(corsConfigurationSource());
httpSecurity.csrf()
.ignoringAntMatchers("/h2-console/**")
.ignoringAntMatchers("/login")
//.ignoringAntMatchers("/logout")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
httpSecurity.headers()
.frameOptions()
.sameOrigin();
httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
httpSecurity.formLogin().successHandler(authenticationSuccessHandler);
httpSecurity.formLogin().failureHandler(authenticationFailureHandler);
httpSecurity.logout().logoutSuccessHandler(restLogoutSuccessHandler);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
if (Objects.nonNull(accountsProperties)) {
FirstUser firstUser = accountsProperties.getFirstUser();
if (Objects.nonNull(firstUser)) {
String name = firstUser.getName();
String password = firstUser.getPassword();
if (Objects.nonNull(name) && Objects.nonNull(password) &&
!("".equals(name) || "".equals(password))) {
jdbcUserDetailsManagerConfigurer.withUser(User.withUsername(name)
.password(passwordEncoder().encode(password))
.roles("USER"));
}
}
FirstAdmin firstAdmin = accountsProperties.getFirstAdmin();
if (Objects.nonNull(firstAdmin)) {
String name = firstAdmin.getName();
String password = firstAdmin.getPassword();
if (Objects.nonNull(name) && Objects.nonNull(password) &&
!("".equals(name) || "".equals(password))) {
jdbcUserDetailsManagerConfigurer.withUser(User.withUsername(name)
.password(passwordEncoder().encode(password))
.roles("ADMIN"));
}
}
}
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // Strength increased as per OWASP Password Storage Cheat Sheet
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
configuration.setAllowedHeaders(List.of("X-XSRF-TOKEN", "Content-Type"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
source.registerCorsConfiguration("/secure/classifieds", configuration);
source.registerCorsConfiguration("/login", configuration);
source.registerCorsConfiguration("/logout", configuration);
return source;
}
}
RESTAuthenticationEntryPoint:
package org.adventure.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
RESTAuthenticationFailureHandler
package org.adventure.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
}
}
RESTAuthenticationSuccessHandler
package org.adventure.security;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#Component
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
clearAuthenticationAttributes(request);
}
}
RESTLogoutSuccessHandler
package org.adventure.security;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#Component
public class RESTLogoutSuccessHandler implements LogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
response.setStatus(HttpServletResponse.SC_OK);
}
}
Frontend:
To /login and /logout I make POST requests with withCredentials: true, and have a HttpInterceptor configured:
import { Injectable } from '#angular/core';
import { HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler, HttpEvent } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable()
export class XsrfInterceptor implements HttpInterceptor {
constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let requestToForward = req;
let token = this.tokenExtractor.getToken() as string;
if (token !== null) {
requestToForward = req.clone({ setHeaders: { "X-XSRF-TOKEN": token } });
}
return next.handle(requestToForward);
}
}
LogoutService:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class LogoutService {
private url = 'http://localhost:8080/logout';
constructor(private http: HttpClient) { }
public logout(): Observable<any> {
return this.http.post(
this.url, { withCredentials: true }).pipe(
map(response => {
console.log(response)
})
);
}
}
In HttpClient, the POST method has a little bit different signature than a GET: https://github.com/angular/angular/blob/master/packages/http/src/http.ts
The second parameter is any request body we want to send, not the options, which are the third parameter. So withCredentials: true was simply never set correctly on the actual request.
Changing the call to:
this.http.post(
this.url, null, { withCredentials: true })
fixed the problem.

Angular - Maven/Spring Boot CORS not working

I'm am trying to set up an application that has an Angular front-end and a Maven/Spring Boot backend and have set up my first REST controller. My issue is that when I send a GET HTTP request to the backend from my Angular IDE it returns an error stating:
"Access to XMLHttpRequest at 'http://localhost:8080/api/getData' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
I'm confused as I have set up the "doFilter" to accept all requests from any origin so it shouldn't be throwing this error. My code follows:
My APIController:
package com.SSCCoursework.controller;
import com.SSCCoursework.Model.SharePrice;
import com.SSCCoursework.Model.Shares;
import com.SSCCoursework.Model.SharesList;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#CrossOrigin (origins = "http://localhost:4200", maxAge = 3600)
#RestController
public class ApiController
{
File Shares_File = new File("Shares_Data.xml");
ArrayList<Shares> shareList = new ArrayList<Shares>();
#RequestMapping(value="/api/getData", produces="application/JSON")
public Object getData()
{
Shares share1 = new Shares();
SharePrice share1_2 = new SharePrice();
share1.setCompanyName("test");
share1.setCompanySymbol("test");
share1.setNumOfShares(123);
Date date = new Date();
share1.setLastShareUpdate(date);
share1_2.setCurrency("dollar");
share1_2.setValue(12345f);
share1.setSharePrice(share1_2);
shareList.add(share1);
SharesList sharelist = new SharesList();
sharelist.setBookList(shareList);
return share1;
}
}
My SimpleCORSFilter:
package com.SSCCoursework.Security;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
#Component
public class SimpleCORSFilter implements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
HttpServletResponse res = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, Connection, User-Agent, authorization, sw-useragent, sw-version");
if (req.getMethod().equals("OPTIONS"))
{
res.setStatus(HttpServletResponse.SC_OK);
return;
}
filterChain.doFilter(request, response);
}
#Override
public void destroy()
{
}
}
My Angular code is just trying to use a GET method (this.httpClient.get('http://localhost:8080/api/getData') to print the data to the browser console but the error is preventing it from working. Am I missing a step in my backend?
you can easily define a global cors config just by adding in your main application class where you start your spring boot app
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8080")
.allowedMethods("PUT", "DELETE", "GET", "POST");
}
};
}
for more details take a look here

Remember me in REST using Spring security

I need to create "Remember me"-provided REST service. My app should receive JSON with login data, authenticate user and make it possible for app to remember the user. I've written some code snippet with few mocked #RequestMapping's and simple Spring security config, and, however, application authenticates user (because of successfulAuthentication() Filter's method invocation). But when I'm trying to send request to the secured url even after login action, it returns 401 code. I know, this is quite obvious that new request creates a new session, but is there any way to perform "remember me" behaviour without sending login info in each Request's body? Here is some of my code:
package com.checkpoint.aimer.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
public class RestSecurityFilter extends AbstractAuthenticationProcessingFilter{
public RestSecurityFilter(String url, AuthenticationManager m) {
super(url);
this.setAuthenticationManager(m);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
HttpSession session = request.getSession();
Authentication auth = this.getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken("roman", "sawawluha"));
SecurityContextHolder.getContext().setAuthentication(auth);
return auth;
}
}
Security configuration:
package com.checkpoint.aimer.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public RestSecurityFilter restSecurity() throws Exception {
RestSecurityFilter filter = new RestSecurityFilter("/auth/login_test", authenticationManagerBean());
return filter;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public UserDetailsService usr() {
return new UserSecurityService();
}
#Override
public void configure(HttpSecurity http) throws Exception{
http
.httpBasic().authenticationEntryPoint(new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException arg2) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Oops");
}
}).and()
.addFilterBefore(restSecurity(),BasicAuthenticationFilter.class )
.rememberMe().rememberMeServices(new TokenBasedRememberMeServices("key",usr()) ).and()
.authorizeRequests()
.antMatchers("/**").hasRole("USER")
.antMatchers("/auth/**").anonymous()
.and()
.csrf().disable()
.logout().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth
.userDetailsService(usr());
}
}
Do you have any ideas?
Remember me features are mostly implemented based on cookies. You can store some authentication token into cookie. I believe you can even use Session Cookies for this.
BUT REMEMBER:
You have to use HTTPS all the time
Use HttpOnly cookie attribute all the time
Use Secure cookie attribute all the time
Because cookie is send to client with every request, you need to make sure it's send via secure channel, and not accessible to cross site attacks.

Categories

Resources