Spring Security Always returning 403 forbidden, Access denied - spring

I want to enable admin to access admin page and do admin stuff, but when I try to do that by setting that the url with /admin/** can only be accessed by user with role admin, it returns 403 Forbidden, access denied. But the user has authorities set to ROLE_ADMIN I checked. What am I doing wrong?
My Controller for user login
#RestController
public class UserController {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthorityService authorityService;
#Autowired
private UserAuthorityService userAuthorityService;
#Autowired
TokenUtils tokenUtils;
#Autowired
private UserService userService;
#RequestMapping(value = "/api/login", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> login(#RequestBody LoginDTO loginDTO) {
try {
// System.out.println(loginDTO.getUsername() + " " + loginDTO.getPassword());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
loginDTO.getUsername(), loginDTO.getPassword());
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails details = userDetailsService.loadUserByUsername(loginDTO.getUsername());
return new ResponseEntity<String>(tokenUtils.generateToken(details), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid login", HttpStatus.BAD_REQUEST);
}
}
#RequestMapping(value = "/api/register", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> register(#RequestBody RegisterDTO registerDTO) {
try {
System.out.println(registerDTO);
User user = userService.findUserByUsername(registerDTO.getUsername());
// // Check if user with that username exists
if(user != null){
// User with that username is found
return new ResponseEntity<String>("User with that username exists", HttpStatus.BAD_REQUEST);
}
// We need to save the user so his ID is generated
User newUser = userService.saveUser(new User(registerDTO));
UserAuthority userAuthority = userAuthorityService.save(new UserAuthority(newUser, authorityService.findOneByName("User")));
Set<UserAuthority> authorities = new HashSet<>();
authorities.add(userAuthority);
newUser.setUserAuthorities(authorities);
User savedUser = userService.save(newUser);
return new ResponseEntity<String>("You have registered successfully with username " + savedUser.getUsername(), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid register", HttpStatus.BAD_REQUEST);
}
}
}
I can say that I test my app with postman and login and registration are working fine. When the user is logged in I can the token with the correct data and users authorities, but why when I try to access /admin/building/add url it is returning 403 error?
My Controller for adding building for admin page:
#RestController
public class BuildingController {
#Autowired
private BuildingService buildingService;
#RequestMapping(value = "/admin/building/add", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> addBuilding(#RequestBody BuildingDTO buildingDTO) {
try{
Building newBuilding = new Building(buildingDTO);
return new ResponseEntity<String>(newBuilding.getName(), HttpStatus.OK);
}catch (Exception ex) {
return new ResponseEntity<String>("Data was not valid", HttpStatus.BAD_REQUEST);
}
}
}
My SecurityConfiguration.java
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(
AuthenticationManagerBuilder authenticationManagerBuilder)
throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService).passwordEncoder(
passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationTokenFilter authenticationTokenFilterBean()
throws Exception {
AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
authenticationTokenFilter
.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/index.html", "/view/**", "/app/**", "/", "/api/login", "/api/register").permitAll()
// defined Admin only API area
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest()
.authenticated()
.and().csrf().disable();
//if we use AngularJS on client side
// .and().csrf().csrfTokenRepository(csrfTokenRepository());
//add filter for adding CSRF token in the request
httpSecurity.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
// Custom JWT based authentication
httpSecurity.addFilterBefore(authenticationTokenFilterBean(),
UsernamePasswordAuthenticationFilter.class);
}
/**
* If we use AngularJS as a client application, it will send CSRF token using
* name X-XSRF token. We have to tell Spring to expect this name instead of
* X-CSRF-TOKEN (which is default one)
* #return
*/
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
I should mention that I am using Angularjs for frontend, but even so I can login and the correct authorities are displayed for that user. But for some reason I can not access the admin page, even if I login as admin.
Also I tried .hasAuthority("ROLE_ADMIN") and .hasRole("ROLE_ADMIN")(which displays an error for ROLE_) and so I changed it to .hasRole("ADMIN") but it is still not working.
In the database the role for admin is saved as ROLE_ADMIN.

Try like this :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
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;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static String REALM="MY_TEST_REALM";
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("tom").password("abc123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").hasRole("ADMIN")
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need sessions to be created.
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
return new CustomBasicAuthenticationEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
}
For a complet configuration example : Secure Spring REST API using Basic Authentication

import org.springframework.context.annotation.Configuration;
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;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();// We don't need sessions to be created.
}
}
This did it for me. Now I am able to submit my post requests successfully

Try this in SecurityConfig:
.antMatchers("/api/admin").access("hasRole('ADMIN')")
.antMatchers("/api/user").access("hasRole('ADMIN') or hasRole('USER')")

Related

My authentication exceptions are not handled

I am currently trying to create a fullstack app, with Angular 14 and spring boot,
i am stack with authentication.
my problem is that i use my own form to get the password and the username from the user, then trying to authenticate in the backend, i created an Authentication Filter, in which i override the attemptAuthentication() method, which recives a JSON object containing the username and password,
Then i test if the username exists if not i throw UserNotFoundException , if the password is wrong i throw BadCredentialsException then if everything went well i return an authentication object, here is the method:
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// JSON body authentication
try {
System.err.println("attempting authentication");
LoginBody loginBody = new ObjectMapper().readValue(request.getInputStream(), LoginBody.class);
AppUser user = this.userService.loadUserByUsername(loginBody.getUsername());
if (user == null) {
throw new UserNotFoundException("No user with this username") {
};
}
if ( user.getPassword().equals(passwordEncoder.encode(loginBody.getPassword()))) {
throw new BadCredentialsException("Bad credentials") {
};
}
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginBody.getUsername(),loginBody.getPassword()));
} catch (Exception e) {
System.err.println(e.getMessage());
throw new AuthenticationException(e.getMessage()) {
} ;
}
i have created an exeption handler which works fine for my controller methods whith have the endpoint /api/... , but not for the authentication with the endpoint /auth/login, all it returns is the HTTP status 403 (forbidden) like in this image
here is my exception handler class
package com.webapps.Focus.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class UserExceptionController {
#ExceptionHandler(value = UserNotFoundException.class)
public ResponseEntity<Object> exception(UserNotFoundException exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
}
#ExceptionHandler(value = BadCredentialsException.class)
public ResponseEntity<Object> exception(BadCredentialsException exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
}
}
I appreciate your help.
According to this article, Exceptionhandler doesn't handle spring security exceptions, like AuthenticationException, hence nothing except UNAUTHORIZED status is shown as an answer,
one solution is to create a customized implementation for AuthenticationFailureHandler interface, then override onAuthenticationFailureonAuthenticationFailure() method, in which you use your own exception handling like in this example:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
#Component("userAuthFailureHandler")
public class UserAuthenticationFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
try {
Map<String, String> status = new HashMap<>();
status.put("status", HttpStatus.UNAUTHORIZED.toString());
status.put("value", HttpStatus.UNAUTHORIZED.value() + "");
status.put("reason", HttpStatus.UNAUTHORIZED.getReasonPhrase());
status.put("error", exception.getMessage());
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
new ObjectMapper().writeValue(response.getOutputStream(), status);
}catch (Exception e) {
throw e;
}
}
}
Then in SecurityConfig class, consider injecting a bean with Qualifier("userAuthFailureHandler") , then set the attribute AuthenticationFailureHandler of your AuthenticationFilter to that bean:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
private AuthenticationFailureHandler failureHandler;
private AuthenticationEntryPoint authEntryPoint;
public SecurityConfig(...
#Qualifier("delegatedAuthenticationEntryPoint") AuthenticationEntryPoint authEntryPoint,
#Qualifier("userAuthFailureHandler")AuthenticationFailureHandler failureHandler) {
...
this.authEntryPoint = authEntryPoint;
this.failureHandler = failureHandler;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure the stateless authentication
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
...
JWTAuthenticationFilter authenticationFilter = new JWTAuthenticationFilter(authenticationManagerBean(), userService, passwordEncoder);
authenticationFilter.setFilterProcessesUrl("/auth/login");
authenticationFilter.setAuthenticationFailureHandler(this.failureHandler);
http.addFilter(authenticationFilter);
http.addFilterBefore(new JWTAuthorisationFilter(), UsernamePasswordAuthenticationFilter.class);
// allow security exceptions handling to component with qualifier delegatedAuthenticationEntryPoint
http.exceptionHandling().authenticationEntryPoint(authEntryPoint);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Then delegate security exception handling to your ow implementation of AuthenticationEntryPoint like below
//This class will help handle security exceptions that couldn't be handled by ControllerAdvice
#Component("delegatedAuthenticationEntryPoint")
public class DelegatedAuthenticationEntryPoint implements AuthenticationEntryPoint {
private HandlerExceptionResolver resolver;
public DelegatedAuthenticationEntryPoint( #Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
resolver.resolveException(request, response, null, authException);
}
}
I had the same problem. It happened because of anyRequest().authenticated() in Security Configuration: "/error" page is blocked too. So u should write something like this: authorizeHttpRequests(auth -> auth.requestMatchers("/error").permitAll() or authorizeHttpRequests().requestMatchers("/error").permitAll() as you wish.

401 Unauthorized on every Request made to REST API [duplicate]

This question already has an answer here:
Custom security is not working when extending the WebSecurityConfigurerAdapter class in different package
(1 answer)
Closed 3 years ago.
I get a 401 unauthorized no matter what ever the request I try. I am even unable to get into #PostMapping("/signup") and print to the console. Spring doesn't show any errors. I only get 401 unauthorized when I try POST to signup with PostMan. Here, I am posting the code for WebSecurityConfig and REST Controller to signup with my REST API.
To access the whole code, please visit https://github.com/BhargaviNadendla/Discussion-Forum--Spring-Boot-Angular
WebSecurityConfig.java:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsService;
#Autowired
private JwtAuthEntryPoint unauthorizedHandler;
#Bean
public JwtAuthTokenFilter authenticationJwtTokenFilter() {
return new JwtAuthTokenFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
#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().
authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().formLogin().disable();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
RestController:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/auth")
public class AuthRestAPIs {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
UserDAO userRepository;
#Autowired
RoleDAO roleRepository;
#Autowired
PasswordEncoder encoder;
#Autowired
JwtProvider jwtProvider;
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginForm loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtProvider.generateJwtToken(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return ResponseEntity.ok(new JwtResponse(jwt, userDetails.getUsername(), userDetails.getAuthorities()));
}
#PostMapping("/signup")
public ResponseEntity<?> registerUser(#Valid #RequestBody SignUpForm signUpRequest) {
System.out.println("In rest----------------------------------");
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
return new ResponseEntity<>(new ResponseMessage("Fail -> Username is already taken!"),
HttpStatus.BAD_REQUEST);
}
if (userRepository.existsByEmail(signUpRequest.getEmail())) {
return new ResponseEntity<>(new ResponseMessage("Fail -> Email is already in use!"),
HttpStatus.BAD_REQUEST);
}
// Creating user's account
User user = new User(signUpRequest.getName(), signUpRequest.getUsername(), signUpRequest.getEmail(),
encoder.encode(signUpRequest.getPassword()));
Set<String> strRoles = signUpRequest.getRole();
Set<Role> roles = new HashSet<>();
strRoles.forEach(role -> {
switch (role) {
case "admin":
Role adminRole = roleRepository.findByName(RoleName.ROLE_ADMIN)
.orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
roles.add(adminRole);
break;
default:
Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
.orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
roles.add(userRole);
}
});
user.setRoles(roles);
userRepository.save(user);
return new ResponseEntity<>(new ResponseMessage("User registered successfully!"), HttpStatus.OK);
}
}
AuthTokenFilter.java
package com.springboot.forumforall.jwtauth.security.jwt;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import com.springboot.forumforall.jwtauth.security.services.UserDetailsServiceImpl;
public class JwtAuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtProvider tokenProvider;
#Autowired
private UserDetailsServiceImpl userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = getJwt(request);
if (jwt != null && tokenProvider.validateJwtToken(jwt)) {
String username = tokenProvider.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Can NOT set user authentication -> Message: {}", e);
}
filterChain.doFilter(request, response);
}
private String getJwt(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.replace("Bearer ", "");
}
return null;
}
}
Your problem is here:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
..
}
Which sould be:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.anyRequest().authenticated()
.antMatchers("/api/auth/**").permitAll()
..
}

How can I add newly signed up user in the Spring boot security config?

I am working Spring-Boot, Spring Security with basic Authentication. I will send login url from my client application written in AngularJS via RESTful API call.
Everything works as expected. All the users in the DB configured in the SecurityConfiguration.java as below.
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
List<User> users = userService.getUsers();
for (User user : users) {
auth.inMemoryAuthentication().withUser(user.getUserName()).password(user.getPassword())
.roles(user.getRole().getName());
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/server/rest/secure/**")
.hasRole("ADMIN").and()
.httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint());
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
return new CustomBasicAuthenticationEntryPoint();
}
CustomBasicAuthenticationEntryPoint.java
import java.io.IOException;
import java.io.PrintWriter;
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.www.BasicAuthenticationEntryPoint;
public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName() + "");
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 : " + authException.getMessage());
response.setHeader("WWW-Authenticate", "FormBased");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("MY_TEST_REALM");
super.afterPropertiesSet();
}
}
So If I signup a new user which will inserted in the DB but not added in the above implementation. So authentication fails.
How can refresh the above implementation whenever i'm and doing signup of a new user
When doing authentication with db, you should do the following:
#Service("userDetailsService")
#Transactional
public class MUserDetailService implements UserDetailsService {
#Autowired
AppUserDao appUserDao;
#Override
public UserDetails loadUserByUsername(final String appUserName) throws UsernameNotFoundException {
AppUser appUser = appUserDao.findByName(appUserName);
if (appUser == null) throw new UsernameNotFoundException(appUserName);
else{
return new User(appUser.getUsername(),appUser.getPassword(),appUser.getActive(),true,true,true,getGrantedAuthorities(appUser));
}
}
private List<GrantedAuthority> getGrantedAuthorities(AppUser appUser){
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (Authority authority : appUser.getAuthorities()){
authorities.add(new SimpleGrantedAuthority(authority.getAuthorityName()));
}
return authorities;
}
}
and then define SecurityConfiguration as follows:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}

Spring security - encoded password gives me Bad Credentials

I'm using Spring security and use an encoder to encode passwords.
So, in my Spring Security Config I have auto wired the
PasswordEncoder passwordEncoder() and added it to the DaoAuthenticationProvider, this is my Spring Security config
package it.besmart.easyparking.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
#Autowired
CustomSuccessHandler customSuccessHandler;
#Autowired
CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
#Autowired
DataSource dataSource;
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#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.authorizeRequests().antMatchers("/", "/home", "/user/**").permitAll()
.antMatchers("/spots/**", "/parks/**", "/floors/**", "/lights/**", "/sensors/**", "/illumination/**",
"/occupation/**", "/movement/**", "/map/**", "/include/**")
.access("hasRole('USER') or hasRole('ADMIN') or hasRole('PARK')").antMatchers("/admin/**")
.access("hasRole('ADMIN') and hasRole('PARK')").antMatchers("/updatePassword")
.hasAuthority("CHANGE_PASSWORD_PRIVILEGE").
and().formLogin().loginPage("/login").successHandler(customSuccessHandler)
.failureHandler(customAuthenticationFailureHandler).usernameParameter("email")
.passwordParameter("password").and().rememberMe().rememberMeParameter("remember-me")
.tokenRepository(persistentTokenRepository()).tokenValiditySeconds(86400).and().csrf().and()
.exceptionHandling().accessDeniedPage("/Access_Denied");
}
#Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
tokenRepositoryImpl.setDataSource(dataSource);
return tokenRepositoryImpl;
}
#Bean
public RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
}
When i pass data from my DTO to the Model I simply do the following
user.setPassword(passwordEncoder.encode(accountDTO.getPassword()));
And in my DB i see the encoded password, for instance something like $2a$10$vVCWjKltOiYO0nPYT1qYI.z4TSk2QJqViDOqRfmoB6BAgldF4vAmm
But when i try to login i'm getting
org.springframework.security.authentication.BadCredentialsException: Bad credentials
When i see logs, i find this
o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt
My password field in the DB is varchar(100) so i think there is enough space to store it...
If i change the encoded password in the DB with a decoded one, i can login...
this is my CustoUserDetailsService
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
UserRepository repository;
private final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
try {
User user = repository.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException("No user found with username: " + email);
}
logger.debug("user: " + user.toString());
return new org.springframework.security.core.userdetails.User(user.getEmail(),
user.getPassword(), user.isEnabled(), accountNonExpired, credentialsNonExpired, accountNonLocked,
getAuthorities(user));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private List<GrantedAuthority> getAuthorities(User user) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getUserProfile().getType()));
// System.out.print("authorities :"+authorities);
return authorities;
}
}
The exception has been thrown by UserDetailsService.loadUserByUsername() method at the return position because of the wrong instantiated User object that hasn't received the encoded password in the corresponding field.
The correct code would look like this:
...
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserEntity user = userRepository.findByUsername(username);
if (user == null)
throw new UsernameNotFoundException("Bad credentials");
return new User(
user.getUsername(),
user.getPassword(), // shall to be the already BCrypt-encrypted password
getAuthorities());
}
The org.springframework.security.authentication.BadCredentialsException: Bad credentials will be thrown once the user.getPassword() isn't well formed BCrypt hashsum.
The password encoder may be registered like this:
#Autowired
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
That's how it works.
The PasswordEncoder should be set like this:
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Also you need to use the passwordEncoder for the users and clients.
You can check this repository https://github.com/dzinot/spring-boot-2-oauth2-authorization-jwt
It uses Spring Boot 2, JWT tokens and the Users and Clients are stored in the database.

#EventListener for AuthenticationSuccessEvent or InteractiveAuthenticationSuccessEvent not fired

I have this listener in the context of Spring:
package listeners;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.stereotype.Component;
import services.UserService;
import services.security.CustomUserDetails;
/**
*
* #author sergio
*/
#Component
public class AuthenticationSuccessEventHandler{
private static Logger logger = LoggerFactory.getLogger(AuthenticationSuccessEventHandler.class);
#Autowired
private UserService userService;
#EventListener({AuthenticationSuccessEvent.class, InteractiveAuthenticationSuccessEvent.class})
public void processAuthenticationSuccessEvent(AbstractAuthenticationEvent e) {
logger.info("Autenticación realizada ....");
// Actualizamos la útltima fecha de acceso
String username = ((CustomUserDetails) e.getAuthentication().getPrincipal()).getUsername();
logger.info("Actualizando último acceso para user: " + username);
userService.updateLastLoginAccess(username, new Date());
}
}
This is successfully created in context, according to the Spring debug messages.
DEBUG DefaultListableBeanFactory:448 - Creating instance of bean 'authenticationSuccessEventHandler'
2016-12-11 11:33:29 DEBUG InjectionMetadata:72 - Registered injected element on class [listeners.AuthenticationSuccessEventHandler]: AutowiredFieldElement for private services.UserService listeners.AuthenticationSuccessEventHandler.userService
When I authenticate correctly in the application, no event is released by Spring Security and therefore this Event Listener is not called.
My Spring Security configuration is this
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/signup").anonymous()
.antMatchers("/admin/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/admin/login").permitAll()
.usernameParameter("username").passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout"))
.logoutSuccessUrl("/admin/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();
}
}
SecurityWebApplicationInitializer
package config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
*
* #author sergio
*/
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
I am using Spring Security 4.2.0.RELEASE.
You may need to register the event-publishing infrastructure (eg. by configuring a DefaultAuthenticationEventPublisher).
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationEventPublisher(authenticationEventPublisher())
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public DefaultAuthenticationEventPublisher authenticationEventPublisher() {
return new DefaultAuthenticationEventPublisher();
}
}
This is how i achieved it.
1) In your Application class, expose your application listener like
#Bean
public ApplicationListener applicationListener(){
return new AuthSuccessApplicationListener();
}
2) Implement AuthSuccessApplicationListener for example
public class AuthSuccessApplicationListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent>{
#Autowired(required=false)
HttpSession httpSession;
#Autowired
Environment env;
/**
* Handle an application event.
*
* #param appEvent the event to respond to
*/
#Override
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent appEvent) {
if (appEvent!=null) {
LdapUserDetailsImpl ldapUserDetailsImpl = (LdapUserDetailsImpl) appEvent.getAuthentication().getPrincipal();
try {
if (ldapUserDetailsImpl != null) {
logger.info("Session Created for " + ldapUserDetailsImpl.getUsername());
if (httpSession.getAttribute("adminUser") == null) {
// check user is admin and set into session
if (isAdminUser(ldapUserDetailsImpl.getUsername())) {
httpSession.setAttribute("adminUser", "ADMIN_USER");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(auth.getAuthorities());
// Add the ROLE_ADMIN into Authorities
authorities.add(new SimpleGrantedAuthority(SecurityConfig.ADMIN));
// Create a new Authentication based on current principal and authorities and set into Security Context
Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), authorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
}
} catch (Exception e) {
logger.error("Exception occurred : " + e.getMessage());
}
}
}
Here's how the Spring Security docs explain it (at the time of writing, Spring Security is at version 5.6.1):
To listen for these events, you must first publish an AuthenticationEventPublisher. Spring Security’s DefaultAuthenticationEventPublisher will probably do fine:
#Bean
public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher appEventPublisher) {
return new DefaultAuthenticationEventPublisher(appEventPublisher);
}
See https://docs.spring.io/spring-security/reference/servlet/authentication/events.html
In spring-security version 5.6.0 only UsernamePasswordAuthenticationFilter fires InteractiveAuthenticationSuccessEvent. As option You may extends AbstractAuthenticationProcessingFilter or do it by own a success handler implementation. Example:
class LoggingAuthenticationSuccessHandler extends WebFilterChainServerAuthenticationSuccessHandler {
#Autowired
private AuthenticationEventPublisher eventPublisher;
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
this.eventPublisher.publishAuthenticationSuccess(authentication);
return super.onAuthenticationSuccess(webFilterExchange,authentication);
}
}

Resources