i'm developing an e-commerce in Java and Spring boot. I've tried to implemented JWT, but everytime i tried to authenticate my user login it returns a BadCredentialsException, how could i fix it?
WebSecurityConfig class
`public class WebSecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
private final ApplicationConfig applicationConfig;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)throws Exception{
http
.cors()
.and()
.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/api/v1/auth/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(applicationConfig.authenticationProvider())
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}`
Authentication method
`public AuthenticationResponse authenticate(AuthenticationRequest request) {
try{
System.out.println(request.getUsername() + " "+ request.getPassword());
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
request.getUsername(), request.getPassword()
));
}catch(BadCredentialsException e){
System.out.println(e.getMessage());
}
var usuario = clienteRepository.findByUsername(request.getUsername())
.orElse(empresaRepository.findByUsername(request.getUsername())
.orElseThrow(() -> new UsernameNotFoundException("Usuário não encontrado!")));
System.out.println(usuario);
var jwtToken = jwtService.generateToken(usuario);
return AuthenticationResponse.builder()
.token(jwtToken)
.build();
}`
`public class AuthenticationRequest {
private String username;
private String password;
}`
JWTFilterClass
`public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsServiceImpl 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 jwtToken;
final String username;
/* ################
JWT Filter Chain
################
Caso o header não tenha autorização (ou seja, nulo) ou não comece com Bearer
(token gerado a partir da autenticação do usuário) irá finalizar o método
*/
if(authHeader == null || !authHeader.startsWith("Bearer")){
filterChain.doFilter(request, response);
return;
}
jwtToken = authHeader.substring(7); // extraindo o token JWT
username = jwtService.extractUsername(jwtToken);
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if(jwtService.isTokenValid(jwtToken, userDetails)){
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails,
null,
userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}`
UserDetailsServiceImpl
`public class UserDetailsServiceImpl implements UserDetailsService {
private ClienteRepository clienteRepository;
private EmpresaRepository empresaRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var usuario = clienteRepository.findByUsername(username)
.orElse(empresaRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Usuário não encontrado!")));
if(usuario == null) throw new UsernameNotFoundException(username);
Set<GrantedAuthority> authoritySet = new HashSet<>();
authoritySet.add(new SimpleGrantedAuthority(usuario.getTipoUsuario().name()));
return new org.springframework.security.core.userdetails.User(usuario.getUsername(), usuario.getPassword(), authoritySet);
}
}`
In my app, i have two types of user (Client and Company as Empresa in PTBR), so everytime i tried to authenticate their login i receive a Bad Credentials error with a 403 Forbidden, even though my inputs are correct.
Related
I created a Spring Boot Rest Api with custom JWT authentication (that I'll change someday with Keycloak).
The custom filter is this:
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;
}
// Extract token
jwt = authHeader.substring(7);
// Extract username or email from JWT token
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 security configuration:
public class SecurityConfiguration {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{
httpSecurity
.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers("/api/v1/auth/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
}
The problem is that when I encounter any exception during runtime (IllegalArgumentException, RuntimeException etc.) all of them are returned to the caller as a "403 Forbidden".
I want to return the actual exception to whoever made the call.
What am I missing here?
You need to allow access to /error.
.requestMatchers("/api/v1/auth/**", "/error").permitAll()
How can I solve JWT error in my spring code?
ERROR security.jwt.AuthEntryPointJwt - Unauthorized error: Full authentication is required to acccess this resource
ERROR security.jwt.AuthTokenFilter - Cannot set user authentication {}
There is some error in my WebSecurityConfig.java file showing WebSecurityConfigurerAdapter is deprecated due which my authenticationManagerBean() is also deprecated.
Authcontroler.java Class
#CrossOrigin("*")
#RestController
#RequestMapping("/api/auth")
public class AuthController {
#Autowired
private UserRepo userRepo;
#Autowired
private RoleRepo roleRepo;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private JwtUtils jwtUtils;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private EmailService emailService;
// POST request for adding user
#PostMapping("/register")
public ResponseEntity<?> registerUser(#Valid #RequestBody SignupRequest signupRequest) {
if (userRepo.existsByEmail(signupRequest.getEmail())) {
return ResponseEntity
.badRequest()
.body(new MessageResponse("Error: Email is already in use!"));
}
User user = new User(signupRequest.getEmail(),
signupRequest.getName(),
passwordEncoder.encode(signupRequest.getPassword()),
signupRequest.getAddress());
//signupRequest.setRole(new ArraySet<>(Set.of("ROLE_USER","")));
signupRequest.setRole("ROLE_ADMIN");
String strRoles = signupRequest.getRole();
Set<Role> roles = new HashSet<>();
if (strRoles==null) {
Role userRole = roleRepo.findByRoleName("ROLE_ADMIN")
.orElseThrow(()-> new RuntimeException("Error: role not found"));
roles.add(userRole);
}
else {
// strRoles.forEach(e->{
// switch (e) {
// case "admin":
// Role roleAdmin = roleRepo.findByRoleName("ROLE_ADMIN")
// .orElseThrow(()-> new RuntimeException("Error: role not found"));
// roles.add(roleAdmin);
// break;
//
// default:
// Role roleUser = roleRepo.findByRoleName("ROLE_USER")
// .orElseThrow(()-> new RuntimeException("Error: role not found"));
// roles.add(roleUser);
// break;
}
user.setRoles(roles);
userRepo.save(user);
emailService.sendTextEmail(user.getEmail());
return ResponseEntity
.status(201)
.body(new MessageResponse("User created successfully"));
}
// POST request for validating user
#PostMapping("/authenticate")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getEmail(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateToken(authentication);
UserDetailsImpl userDetailsImpl = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetailsImpl.getAuthorities()
.stream()
.map(i->i.getAuthority())
.collect(Collectors.toList());
return ResponseEntity.ok(new JwtResponse(jwt,
userDetailsImpl.getId(),
userDetailsImpl.getEmail(),
roles));
}
#GetMapping("/")
public ResponseEntity<?> getUser() {
UserDetailsImpl userDetailsImpl = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
Long id = userDetailsImpl.getId();
Optional<User> optional = userRepo.findById(id);
return ResponseEntity.ok(optional.get());
}
}
AuthEntryPointJwt.java Class
#Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
logger.error("Unauthorized error: {}", authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<String, Object> body = new HashMap<>();
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
body.put("error", "Unauthorized");
body.put("message", authException.getMessage());
body.put("path", request.getServletPath());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), body);
}
}
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandeler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// TODO Auto-generated method stub
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// TODO Auto-generated method stub
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.cors().and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandeler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests().antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/food/**").permitAll()
.antMatchers("/api/cart/**").permitAll()
.antMatchers("/api/order/**").permitAll()
.antMatchers("/api/payment/**").permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/api/users/**")
.permitAll().anyRequest().authenticated();
http.headers().frameOptions().disable();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
// super.configure(http);
}
}
AuthTokenFilter.java Class
public class AuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
private static final Logger LOGGER = LoggerFactory.getLogger(AuthTokenFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// TODO Auto-generated method stub
try {
String jwt = parseJwt(request);
if (jwt!=null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUsernameFromJwtToken(jwt);
UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
} catch (Exception e) {
// TODO Auto-generated catch block
LOGGER.error("Cannot set user authentication {}", e);
}
filterChain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7, headerAuth.length());
}
return null;
}
}
Thankyou
You need a configuration file that allows users to talk to a login endpoint. Within this endpoint you may authenticate a user to get past the authentication barrier.
Example configuration within spring boot security:
#Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/register").permitAll() // <- allow requests to this reqister endpoint
.anyRequest().authenticated(); // <- all other requests needs full authentication;
http.addFilterAfter(
new AuthTokenFilter(), BasicAuthenticationFilter.class); // <- add your JWT authentication filter into the chain
}
}
Another important step is to integrate your Filter into the filter chain of incomming requests.
You may have a look at this example: Spring Security / Getting Started. Another good resource is always Baeldung Config / Baeldung Filter
Within this configuration you can also integrate your AuthEntryPointJwt.
Any ideas what is the issue? This happened after upgrading Gradle, Spring framework and all dependencies to the latest version. Tried by using #Lazy on Beans but still getting an error. Please check the uploaded images for a better understanding. Thanks in advance.Error message
JwtRequestFilter file
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
//Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
JwtUserDetailsService file
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
#Autowired
private PasswordEncoder bcryptEncoder;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> user = userRepository.findByUsername(username);
if (!user.isPresent()) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
return new org.springframework.security.core.userdetails.User(
user.get().getUsername(),
user.get().getPassword(),
getAuthorities(user.get())
);
}
public User getUser(){
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
String username = userDetails.getUsername();
return userRepository.findByUsername(username).get();
}
private Set<GrantedAuthority> getAuthorities(User user) {
Set<GrantedAuthority> authorities = new HashSet<>();
user.getAuthorities().forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
return authorities;
}
WebSecurityConfig.java file
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
private CorsFilter corsFilter;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/api/user/login");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.and()
.authorizeRequests()
.antMatchers("/admin/user/login", "/api/user/login", "/management/users/register", "/v2/api-docs", "/swagger-ui.html").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/admin/**").hasAuthority("Admin")
.antMatchers("/api/**").hasAuthority("User")
// all other requests need to be authenticated
.anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
CorsFilter getCorsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addExposedHeader("Authorization, Content-Type, Error");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
I'm guessing that your problem is due to your use of property injection.
Use constructor injection instead (everywhere), and your problem will likely go away.
I am currently working on a spring boot project that has multiple security (authentication) configurations combined:
a rest api with http basic auth
a rest api with jwt auth.
a web (form login) with 2fa auth.
The problem I am experiencing is, that the configurations cannot be entirely seperated. More specific: The authenticiation providers are accumulated (in the provider manager), which prevents my preferred setup from working correctly.
What happens is this: In config (3) i have a custom 2fa authentication provider which checks if the credentials (both password and 2FA code!) are entered correctly. If the password is correct, but the 2fa code is not, it exits (catches) with a (authentication or bad credentials) exception. However, as it exits the 2fa authentication provider, it goes back to the provider manager. The latter has another (DAO) auth provider at hand, and checks the credentials again: but this time: only password! not the 2fa code!). As the password is ok, it authorized the request.
So for short: I am experiencing a problem where the first authentication provider does NOT authorize, but the second does (because that one does not take the 2fa code into account)!
I have been at this for days, and i cannot seem to get this right.
As an alternative i have now opted for a solution using a 2fa custom filter. But is not my preferred solution, as it gives me some frontend problems (i first have to authorize the username/password, and only after that i can check the 2fa code).
Is there a solution using my 2fa auth provider? I would sort of wish that the auth providers would not get accumulated, so that when the first auth. provider exits with bad credentials, the auth procedure ends with a 'bad credentials'.
My config class:
WebSecurityConfig.java
#EnableWebSecurity
#Configuration
#AllArgsConstructor
public class WebSecurityConfig_backup extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
private HttpBasicAuthenticationExceptionHandler authenticationExceptionHandler;
private JwtAuthenticationProvider jwtAuthenticationProvider;
private MfaAuthenticationDetailsSource mfaAuthenticationDetailsSource;
private MfaAuthenticationProvider2 mfaAuthenticationProvider;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Configuration
#Order(1)
public class ApiV1WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v1/**")
.httpBasic()
.authenticationEntryPoint(authenticationExceptionHandler)
.and()
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth .userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(2)
public class ApiV2WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v2/**")
.addFilter(bearerTokenAuthenticationFilter())
.addFilter(credentialsAuthenticationFilter())
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.authorizeRequests()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
private BearerTokenAuthenticationFilter bearerTokenAuthenticationFilter() throws Exception {
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private CredentialsAuthenticationFilter credentialsAuthenticationFilter() throws Exception {
CredentialsAuthenticationFilter filter = new CredentialsAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, ex) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
ResponseWriterUtil.writeErrorResponse(response, ex.getMessage());
};
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jwtAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(3)
public class FormWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//authorisation
http//.addFilterBefore(googleTfaFilter, SessionManagementFilter.class)
.authorizeRequests(authorize -> {
authorize
.mvcMatchers("/", "/login", "/logout", "/registrationPage", "/register").permitAll()
.mvcMatchers(HttpMethod.GET,
"/others1", "/others2").hasAnyRole("USER", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/others1", "/others2").hasAnyRole("USER", "ADMIN");
})
.formLogin()
.loginPage("/login").permitAll()
.usernameParameter("email")
.loginProcessingUrl("/authenticate")
.defaultSuccessUrl("/")
.failureUrl("/login?error")
.authenticationDetailsSource(mfaAuthenticationDetailsSource)
.and()
.logout()
.deleteCookies("JSESSIONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.logoutSuccessUrl("/login?logout").permitAll()
.and()
.sessionManagement()
.sessionFixation().migrateSession()
.and()
.headers().frameOptions().sameOrigin()
.and()
.csrf();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(mfaAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
}
auth provider for config 2:
#Slf4j
#RequiredArgsConstructor
#Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(BearerTokenAuthenticationToken.class);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken bearerToken = (BearerTokenAuthenticationToken) authentication;
Authentication auth = null;
try {
//validate the token
Jwts.parser().setSigningKey(JWT_TOKEN_SECRET).parseClaimsJws(bearerToken.getToken());
JwtTokenUtil jwtTokenUtil = new JwtTokenUtil(bearerToken.getToken());
String username = jwtTokenUtil.getUsernameFromToken();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
log.debug("Authentication token: " + auth);
} catch (IllegalArgumentException e) {
throw new UserServiceAuthenticationException("Invalid token");
} catch (ExpiredJwtException e) {
throw new UserServiceAuthenticationException("Token expired");
} catch (SignatureException e) {
throw new UserServiceAuthenticationException("Invalid signature");
}
return auth;
}
}
Auth provider for config (3)
#Slf4j
#AllArgsConstructor
#Component
public class MfaAuthenticationProvider2 implements AuthenticationProvider {
private UserRepo userRepository;
private GoogleAuthenticator googleAuthenticator;
private PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String verficationCode = ((MfaAuthenticationDetails) authentication.getDetails()).getUserMFaCode();
User user = userRepository.findByEmail(authentication.getName()).stream().findFirst().orElse(null);
if(user == null || !passwordEncoder.matches(password, user.getPassword())){
throw new BadCredentialsException("Invalid username or password");
}
try {
if(!googleAuthenticator.authorizeUser(user.getUsername(), Integer.parseInt(verficationCode))){
throw new BadCredentialsException("Invalid verification code.");
}
} catch (Exception e) {
throw new BadCredentialsException("Authentication failed. Please try again.");
}
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
My current alternative solution for config 3:
#Slf4j
#Component
public class GoogleTfaFilter2 extends OncePerRequestFilter {
private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private final GoogleTfaFailureHandler googleTfaFailureHandler = new GoogleTfaFailureHandler();
private final RequestMatcher urlIs2fa = new AntPathRequestMatcher("/verify2fa");
private final RequestMatcher urlIs2fa2 = new AntPathRequestMatcher("/register2fa");
private final RequestMatcher urlResource = new AntPathRequestMatcher("/resources/**");
private final RequestMatcher api = new AntPathRequestMatcher("/api/**");
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
StaticResourceRequest.StaticResourceRequestMatcher staticResourceRequestMatcher =
PathRequest.toStaticResources().atCommonLocations();
if (urlIs2fa.matches(request) || urlResource.matches(request) || urlIs2fa2.matches(request)||
staticResourceRequestMatcher.matcher(request).isMatch() || api.matches(request)) {
filterChain.doFilter(request, response);
return;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)){
log.debug("Processing 2FA Filter");
if (authentication.getPrincipal() != null && authentication.getPrincipal() instanceof User) {
User user = (User) authentication.getPrincipal();
if (!user.getMfaPassed()) {
log.debug("2FA Required");
request.getRequestDispatcher("/verify2fa").forward(request, response);
return;
}
}
}
filterChain.doFilter(request, response);
}
}
I'm using LDAP authentication provider (active directory) + JWT authorization filter.
I have my custom user object implementing UserDetails, also my user service extends UserDetailsService. But when I do:
Usuario principal = (Usuario) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Principal is just a string (the username), not my user object.
This is my configuration:
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final LdapProperties ldapProperties;
private final LdapUserMapper ldapUserMapper;
private final UserService userService;
public SecurityConfig(LdapProperties ldapProperties, LdapUserMapper ldapUserMapper, UserService userService) {
this.ldapProperties = ldapProperties;
this.ldapUserMapper = ldapUserMapper;
this.userService = userService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Entry points
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.GET, "/v2/api-docs",
"/configuration/ui",
"/swagger-resources",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/swagger-resources/**",
"/swagger-ui.html").permitAll()
//TODO review
.anyRequest().authenticated();
// JwtWebSecurityConfigurer... TODO ?
// Filters
http.addFilter(new AuthenticationFilter(authenticationManager())); // ldap
http.addFilter(new AuthorizationFilter(authenticationManager())); // jwt
http.cors();
http.csrf().disable();
// No session will be created or used by spring security
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Need to provide Authorization header
http.httpBasic();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthenticationProvider());
auth.userDetailsService(userService);
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
corsConfiguration.setAllowedMethods(Arrays.asList(CorsConfiguration.ALL));
//TODO configure properly
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
#Bean
public AbstractLdapAuthenticationProvider ldapAuthenticationProvider() {
String urls = "";
for (String url : ldapProperties.getUrls()) {
urls += url + " ";
}
urls = urls.trim();
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
ldapProperties.getBaseEnvironment().get("domain"),
urls,
ldapProperties.getBase()
);
provider.setUserDetailsContextMapper(ldapUserMapper);
provider.setConvertSubErrorCodesToExceptions(true);
// comment to connect as anonymous
provider.authenticate(
new UsernamePasswordAuthenticationToken(ldapProperties.getUsername(), ldapProperties.getPassword())
);
return provider;
}
}
LdapUserMapper:
#Component
public class LdapUserMapper implements UserDetailsContextMapper {
private final UserService userService;
#Autowired
public LdapUserMapper(UserService userService) {
this.userService = userService;
}
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Usuario result = (Usuario) userService.loadUserByUsername(username);
//TODO compare roles ? set bloqueado ? ...
return result;
}
#Override
public void mapUserToContext(UserDetails userDetails, DirContextAdapter dirContextAdapter) {
}
}
It was my authorization filter.
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String token = req.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.replace("Bearer ", "");
Authentication authentication = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(final String token) {
try {
DecodedJWT decodedToken = JWT.require(Algorithm.HMAC512(jwtSecret)).build().verify(token);
String username = decodedToken.getSubject();
return new UsernamePasswordAuthenticationToken(username , null, new ArrayList<>());
} catch (JWTVerificationException ex) {
log.error(ex.getMessage());
}
return null;
}
I wasn't aware that I only was passing the username as the Principal object. So I changed it to this:
Usuario usuario = (Usuario) userService.loadUserByUsername(decodedToken.getSubject());
I also removed
auth.userDetailsService(userService);
From AuthenticationManagerBuilder config. That was wrong. My auth provider is LDAP, not the DB.