I am using the HTTPBasic authentication scheme in my spring security, and I would like to log all failed and successful login attempts. It seems like the general way to do so is to call a method upon login failure, kind of like this...
.and().formLogin().failureHandler(//method to call upon failure);
However, this requires the form login, and I am using HTTPBasic. How would we set this up so that it would have a failurehandler on an HTTPBasic authentication scheme?
SecurityConfiguration.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.service.UserService;
#SuppressWarnings("deprecation")
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/welcome").permitAll().antMatchers("/secured")
.authenticated().and().formLogin()
.failureHandler(new SimpleUrlAuthenticationFailureHandler()).permitAll().and().httpBasic();
}
}
LoginFailureHandler.java
package com.config;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
#Component
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String email = request.getParameter("email");
String redirectURL = "/login?error&email=" + email;
// if (exception.getMessage().contains("OTP")) {
// redirectURL = "/login?otp=true&email=" + email;
// } else {
// Customer customer = customerService.getCustomerByEmail(email);
// if (customer.isOTPRequired()) {
// redirectURL = "/login?otp=true&email=" + email;
// }
// }
super.setDefaultFailureUrl(redirectURL);
super.onAuthenticationFailure(request, response, exception);
}
}
Related
I am getting the following error when trying to test my controller. I know the error is being thrown by something in the security configuration, just not sure what it is exactly. org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'springSecurityFilterChain' is expected to be of type 'javax.servlet.Filter' but was actually of type 'org.springframework.beans.factory.support.NullBean'.
This is the test class
package com.chama.chamaservice.user;
import com.chama.chamaservice.auth.CustomAuthenticationFilter;
import com.chama.chamaservice.auth.CustomAuthorizationFilter;
import com.chama.chamaservice.config.SecurityConfig;
import org.junit.Before;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = UserController.class)
class UserControllerTest {
#MockBean
private WebSecurityConfiguration webSecurityConfiguration;
#MockBean
private UserService userService;
#MockBean
private UserDetailsService userDetailsService;
#MockBean
private SecurityConfig securityConfig;
#MockBean
private WebApplicationContext context;
#MockBean
private CustomAuthorizationFilter customAuthorizationFilter;
#Autowired
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#Test
#WithMockUser(username = "254704760842", roles = {"PLATFORM_SUPER_ADMIN"})
#DisplayName("Should list all users GET: /api/users/")
void shouldGetUsers() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().is(200))
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE));
}
}
Note that the test runner actually reaches the test class and the error is thrown at this line mockMvc.perform(get("/api/users"))
This is the controller under test
#GetMapping("")
#JsonView(Views.Public.class)
#PreAuthorize("hasAuthority('CAN_VIEW_ALL_PLATFORM_MEMBERS')")
public ResponseEntity<List<User>> getUsers() {
return ResponseEntity.ok().body(userService.getUsers());
}
This is my custom authorisation filter class.
package com.chama.chamaservice.auth;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.chama.chamaservice.config.ApplicationUserRole;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
#Slf4j
public class CustomAuthorizationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getServletPath().equals("/api/login") || request.getServletPath().equals("/api/refresh/token")) {
filterChain.doFilter(request, response);
} else {
// Generate new access token from refresh token
String authorizationHeader = request.getHeader(AUTHORIZATION);
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
try {
String token = authorizationHeader.substring("Bearer ".length());
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String phoneNumber = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = Arrays.stream(roles)
.map(ApplicationUserRole::valueOf) // <- parsing every String to ApplicationUserRole
.map(ApplicationUserRole::getGrantedAuthorities) // <- converting every ApplicationUserRole to a set of GrantedAuthority
.flatMap(Collection::stream) // <- converting stream of sets to a stream of GrantedAuthority
.collect(Collectors.toList());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(phoneNumber, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception e) {
log.error("Error logging in: {}", e.getMessage());
response.setHeader("error", e.getMessage());
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
} else {
filterChain.doFilter(request, response);
}
}
}
}
And finally, this is my security config class
package com.chama.chamaservice.config;
import com.chama.chamaservice.auth.CustomAuthenticationFilter;
import com.chama.chamaservice.auth.CustomAuthorizationFilter;
import lombok.RequiredArgsConstructor;
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.method.configuration.EnableGlobalMethodSecurity;
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.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
/*
* Setting custom login endpoint
* */
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean());
customAuthenticationFilter.setFilterProcessesUrl("/api/login");
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
.antMatchers("/api/login/**", "/api/token/refresh/**").permitAll()
.antMatchers("/api/users/save/**").permitAll()
.antMatchers("/api/daraja/register-url/validation/**").permitAll()
.antMatchers("/api/daraja/register-url/confirmation/**").permitAll()
.anyRequest()
.authenticated()
.and();
http.addFilter(customAuthenticationFilter);
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManager();
}
}
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 :)
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.
My webapp is based on Spring and I enabled Spring Security. Most of it is working as expected but there's one problem remaining.
When I click on the /logout link in my running application I get redirected to /login?timeout=true instead of /login?logout. I'm sure it's only some small misconfiguration but I seem to be missing it though.
So here's my configuration:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
SecUserDetailsService userDetailsService;
#Autowired
AuthenticationSuccessHandler myAuthenticationSuccessHandler;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
http
.authorizeRequests()
.antMatchers("/someprotectedpage").hasRole("USER")
.and()
.sessionManagement()
.invalidSessionUrl("/login?timeout=true")
.and()
.formLogin()
.loginPage("/login")
.successHandler(myAuthenticationSuccessHandler)
;
}
/**
* User management via MongoDB
*/
#Autowired
public void configAuthBuilder(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
}
Controller:
package com.mycompany.webapp.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.mycompany.webapp.WebAppIdentifier;
import com.mycompany.webapp.config.security.SecUserDetails;
#Controller
public class LoginController {
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(#RequestParam(value = "error", required = false) String error,
#RequestParam(value = "logout", required = false) String logout,
#RequestParam(value = "timeout", required = false) String timeout, Model model) {
if (error != null) {
model.addAttribute(WebAppIdentifier.LOGIN_ERROR, true);
}
if (logout != null) {
model.addAttribute(WebAppIdentifier.LOGOUT, true);
}
if (timeout != null) {
model.addAttribute(WebAppIdentifier.TIMEOUT, true);
}
return "login";
}
#RequestMapping(value="/logout", method = RequestMethod.GET)
public String logout (HttpSession session, HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "redirect:/login?logout";
}
}
Versions:
spring.version "4.2.6.RELEASE"
spring.security.version "4.1.0.RELEASE"
Do you know what the problem might be? Thanks! Appreciate your help.
You don't need a "logout" controller, Spring security already provides one that can be configured with the fluid builder :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout");
(...)
}
Using annotation and java configuration it is not quite clear to me how to register an overridden filter for spring security.
What I want to achieve is to do an auto login without showing a Login form since at that time the user will already be authenticated. Therefore will only be reading a header param and use spring security for authorization purposes.
This is a simplified version of what I'm trying to, and the Spring security works correctly, except for sometimes showing the login screen.
Bootstrapping the BypassLoginFilter is all I need to get this going. Also read somewhere that http auto config should be off for this kind of behaviour, but not sure how to implement in pure java configuration.
SecurityWebApplicationInitializer.java
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer{
}
SecurityConfig .java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/*").permitAll()
.anyRequest().hasRole("USER").and()
.formLogin()
.permitAll();
http.addFilterBefore(new BypassLoginFilter(), LogoutFilter.class);
//.and().anonymous().disable();
}
#Override
#Autowired
protected void registerAuthentication(AuthenticationManagerBuilder auth) {
try {
auth.inMemoryAuthentication().withUser("user").password("password")
.roles("USER").and().withUser("admin").password("password")
.roles("USER", "ADMIN");
} catch (Exception e) {
e.printStackTrace();
}
}
}
BypassLoginFilter.java
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
public class BypassLoginFilter extends AbstractAuthenticationProcessingFilter{
private static String HEADER_IS_ADMIN = "isAdmin";
public BypassLoginFilter()
{
super("/*");
}
//Never gets executed
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
boolean isAdmin = Boolean.valueOf(request.getHeader(HEADER_IS_ADMIN));
PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken("","",getAuthorities(isAdmin));
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
return getAuthenticationManager().authenticate(authRequest);
}
private List<GrantedAuthority> getAuthorities(boolean isAdmin)
{
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
if(isAdmin){
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
return authorities;
}
}
You can try following approach. Let's say you have a YourUser class which looks like that:
public class YourUser extends org.springframework.security.core.userdetails.User{
...
public String getStartPage(){ return "/userhomepage"; }
...
}
Then you need to declare authentication handler:
#Component
public class YourAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof YourUser) {
final YourUser user = (YourUser) authentication.getPrincipal();
return user.getStartPage();
}else {
return "/defaultPageForNonAuthenticatedUsers";
}
}
}
And use it in the security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// rest calls are ommited
http.successHandler(successHandler());
}
#Bean
public AuthenticationSuccessHandler successHandler() throws Exception {
return new YourAuthenticationSuccessHandler();
}
}