H2 access denied - spring-security

#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfiguration {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.headers()
.frameOptions().disable()
.and()
.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/h2/**").permitAll()
.requestMatchers("/api/v1/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
I have this config class. I placed .requestMatchers("/h2/**").permitAll() in the code but when I visit the http://localhost:8080/h2 I take:
Access to localhost was denied
the JwtAuthenticationFilter:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
#Override
protected void doFilterInternal(
#NonNull HttpServletRequest request,
#NonNull HttpServletResponse response,
#NonNull FilterChain filterChain
) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);
userEmail = jwtService.extractUsername(jwt);
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
The doFilterInternal method is the main method of the filter. It checks if the incoming request has an "Authorization" header with a JWT starting with "Bearer ", and if so, extracts the user email from the JWT using the JwtService and loads the user details using the UserDetailsService. If the JWT is valid and the user details are successfully loaded, an Authentication object is created and set in the SecurityContextHolder. The filter chain is then continued with the filterChain.doFilter(request, response) call.

Related

How does JwtUsernameAndPasswordAuthenticationFilter work in this example?

Below is my example of Configuration, as far as I understand, JwtUsernameAndPasswordAuthenticationFilter works first ( it gets username, password from request, checks if they are correct and provides a token ), then - JwtTokenVerifier.
I have a few questions:
Is JwtUsernameAndPasswordAuthenticationFilter checks requests every time for containing username and password? If not, when does it check it? Once per what?
Why do we create Authentication object in JwtTokenVerifier class with no password ( just username and Authorities ) and put it in Context?
P.S. I do appreciate your answers! And know how dumb the question may seems to be.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, secretKey))
.addFilterAfter(new JwtTokenVerifier(secretKey, jwtConfig),JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "index", "/css/*", "/js/*").permitAll()
.antMatchers("/api/**").hasRole(STUDENT.name())
.anyRequest()
.authenticated();
}
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JwtConfig jwtConfig;
private final SecretKey secretKey;
public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager,
JwtConfig jwtConfig,
SecretKey secretKey) {
this.authenticationManager = authenticationManager;
this.jwtConfig = jwtConfig;
this.secretKey = secretKey;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
);
Authentication authenticate = authenticationManager.authenticate(authentication);
return authenticate;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
String token = Jwts.builder()
.setSubject(authResult.getName())
.claim("authorities", authResult.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusDays(jwtConfig.getTokenExpirationAfterDays())))
.signWith(secretKey)
.compact();
response.addHeader(jwtConfig.getAuthorizationHeader(), jwtConfig.getTokenPrefix() + token);
}
}
public class JwtTokenVerifier extends OncePerRequestFilter {
private final SecretKey secretKey;
private final JwtConfig jwtConfig;
public JwtTokenVerifier(SecretKey secretKey,
JwtConfig jwtConfig) {
this.secretKey = secretKey;
this.jwtConfig = jwtConfig;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader(jwtConfig.getAuthorizationHeader());
if (Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) {
filterChain.doFilter(request, response);
return;
}
String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");
try {
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
Claims body = claimsJws.getBody();
String username = body.getSubject();
var authorities = (List<Map<String, String>>) body.get("authorities");
Set<SimpleGrantedAuthority> simpleGrantedAuthorities = authorities.stream()
.map(m -> new SimpleGrantedAuthority(m.get("authority")))
.collect(Collectors.toSet());
Authentication authentication = new UsernamePasswordAuthenticationToken(
username,
null,
simpleGrantedAuthorities
);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (JwtException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", token));
}
filterChain.doFilter(request, response);
}
}
JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter, and by default its triggered when you make a "POST" call to "/login". This can be changed by calling the setFilterProcessesUrl(<PATH_HERE>) in the JwtUsernameAndPasswordAuthenticationFilter's constructor.
You don't keep the password in the Authentication object simply because it's not needed, and it's safer to keep the password off-memory because anyone with access to the memory dump can retrieve the password.

Authorization and TestRestTemplate

I'm using a default Spring login form and REST API for other data. Sessions are enabled and used.
All urls (except /login form ) are protected.
So how to test the protected #RestController methods using TestRestTemplate? (I could make an additional request to /api/login to get Cookie and then generate and add Headers, but there is no REST endpoint for login, only a form-base authencation).
Also, is the #WithMockUser annotation only for MockMvc (and can't be used with TestRestTemplate)?
Steps
Clone spring security example repo git clone https://github.com/spring-guides/gs-securing-web.git
Added RestControllerIT
Added csrf().disable() to WebSecurityConfig. This test will not pass if csrf enabled
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RestControllerIT {
#Autowired
TestRestTemplate testRestTemplate;
#LocalServerPort
int localPort;
#Test
public void test(){
String securedUrl = "http://localhost:" + localPort + "/hello";
String loginUrl = "http://localhost:" + localPort + "/login";
String username = "user";
String password = "password";
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", username);
form.set("password", password);
ResponseEntity<String> loginResponse = testRestTemplate.postForEntity(
loginUrl,
new HttpEntity<>(form, new HttpHeaders()),
String.class);
String cookie = loginResponse.getHeaders().get("Set-Cookie").get(0);
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", cookie);
ResponseEntity<String> responseFromSecuredEndPoint = testRestTemplate.exchange(securedUrl, HttpMethod.GET, new HttpEntity<>(headers), String.class);
assertEquals(responseFromSecuredEndPoint.getStatusCode(), HttpStatus.OK);
assertTrue(responseFromSecuredEndPoint.getBody().contains("Hello World!"));
}
}
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}

JWT Bearer token not received?

I want to use Spring security to perform authentication and authorization on 2 separated server.
I've authenticated successfully and received a JWT token on the first server.
Now I am sending a request to the second server with the JWT authorization token, but the server can't see it; basically getHeader() from getJwtFromRequest(HttpServletRequest request) returns null.
This is the server code:
//imports...
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailsService customUserDetailsService;
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability")
.permitAll()
.antMatchers(HttpMethod.GET, "/api/polls/**", "/api/users/**")
.permitAll()
.anyRequest()
.authenticated();
// Add our custom JWT security filter
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
//imports...
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
// bearerToken prints null!
System.out.println("AUTHORIZATION: "+bearerToken);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
But the token is present on the request:
The authorization header was stripped on the edge server

SPRING-SECURITY Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’

I have my service work and deployed with no problem, but i need to add more feature [Realtime Notification]. I'm using SockJS, StompJS, Spring security, and LDAP authentication.
Here is my AuthenticationFilter, i'm using token as my passcode it generated after login into LDAP.
#Log4j2
public class AuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JWTUtil jwtUtil;
private final String authHeader = "token";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
//CORS
response.addHeader("Access-Control-Allow-Origin", "*");
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.addHeader("Access-Control-Allow-Headers", "token");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
}
final String authHeader = request.getHeader(this.authHeader);
if (authHeader != null) {
String token = authHeader;
try {
Claims claims = jwtUtil.getAllClaimsFromToken(token);
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
new User(claims.getSubject()),
null,
authorities
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
log.debug("Error ", e);
}
}
if (!request.getMethod().equalsIgnoreCase("OPTIONS")) {
chain.doFilter(request, response);
}
}
}
Here is my WebSecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public UnauthorizedHandler unauthorizedHandler() throws Exception {
return new UnauthorizedHandler();
}
#Bean
public ForbiddenHandler forbiddenHandler() throws Exception {
return new ForbiddenHandler();
}
#Bean
public AuthenticationFilter authenticationFilterBean() throws Exception {
return new AuthenticationFilter();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler()).and()
.exceptionHandling().accessDeniedHandler(forbiddenHandler()).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// allow auth url
.antMatchers("/login","/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**", "/notif/**", "/mealnotif/**", "/topic/**", "/websocket/**", "/resources/**", "/META-INF/resources/**").permitAll()
.anyRequest().authenticated();
// custom JWT based security filter
httpSecurity.addFilterBefore(authenticationFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
And the last one is my WebSocketConfig
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/mealnotif");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/notif").setAllowedOrigins("*")
.withSockJS();
}
}
But in return My Angular project always return Cross-Origin Request Blocked
CORS Reason ERROR
How do i solve this?
i can solve this by adding whitelist of allowed origin in my application.properties
management.endpoints.web.cors.allowed-origins=http://localhost,http://localhost:4200
by this properties i managed to giving properly response to client
response

Spring Security Access Denied Handler is called when the HttpServletResponse is already committed

I have an HTTP API, protected with Spring Security and JWT.
I get a 401 when I'm trying to access a protected resource.
I get the resource if I'm authenticated (JWT is valid) and I have the correct role. The resource is protected with #PreAuthorize("hasRole('USER')").
The issue I have is that when I don't have the correct role I'd like to return a 403 (in the following code it is a 401 for the sake of testing).
But right know I get a 500 because of the AccessDeniedException which is thrown when the role is incorrect.
The weird thing is that it goes to my JwtAccessDeniedHandler custom code but the response is already committed (isCommitted() == true) so whenever I try to set the status etc it does nothing.
Do you have any ideas about what could be misconfigured or missing?
Config:
#Slf4j
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ObjectMapper objectMapper;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(
jwtAuthenticationFilter(joseHelper(jsonWebKey())),
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new JwtAccessDeniedHandler());
}
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(JoseHelper joseHelper) {
return new JwtAuthenticationFilter(joseHelper);
}
#Bean
public JoseHelper joseHelper(PublicJsonWebKey key) {
return new JoseHelper(key);
}
#Bean
public PublicJsonWebKey jsonWebKey() throws IOException, JoseException {
return RsaJwkGenerator.generateJwk(2048);
}
private void sendUnauthorized(HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.setContentType("application/json");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiError apiError = ApiError.builder()
.code(HttpStatus.UNAUTHORIZED.name())
.message(HttpStatus.UNAUTHORIZED.getReasonPhrase())
.httpStatus(HttpStatus.UNAUTHORIZED)
.build();
httpServletResponse.getWriter().print(objectMapper.writeValueAsString(apiError));
}
private class JwtAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
log.info("accessDeniedHandler", e);
sendUnauthorized(httpServletResponse);
}
}
private class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
sendUnauthorized(httpServletResponse);
}
}
}
Filter:
#Slf4j
#Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String BEARER = "Bearer ";
private JoseHelper joseHelper;
#Autowired
public JwtAuthenticationFilter(JoseHelper joseHelper) {
this.joseHelper = joseHelper;
}
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String header = httpServletRequest.getHeader("Authorization");
if (header == null || !header.startsWith(BEARER)) {
log.error("JWT token is not valid");
filterChain.doFilter(httpServletRequest, httpServletResponse);
return;
}
final String encryptedToken = header.substring(BEARER.length());
try {
final String decryptedJwt = joseHelper.decryptJwt(encryptedToken);
final String verifiedJwt = joseHelper.verifyJwt(decryptedJwt);
final JwtClaims jwtClaims = joseHelper.parse(verifiedJwt);
List<SimpleGrantedAuthority> authorities = jwtClaims.getStringListClaimValue("userRoles")
.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwtClaims, null, authorities);
SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (JoseException | InvalidJwtException | MalformedClaimException e) {
log.error("JWT token is not valid", e);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
}
The issue was because I use Jersey apparently. I didn't really had time to investigate the why right now.
Once I registered an exception mapper in my JerseyConfig I was able to capture and handle the AccessDeniedException correctly.
And from that point the access denied handler is not called anymore and becomes useless.
A bit weird, but there is probably a good reason.

Resources