AuthenticationException throws with correct credentials - spring

I'm trying to secure a REST API with JWT and I have a problem during the sign in.
UserService.java
private final UserRepository repository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
private final AuthenticationManager authenticationManager;
#Autowired
public UserService(UserRepository repository, PasswordEncoder passwordEncoder,
JwtTokenProvider jwtTokenProvider, AuthenticationManager authenticationManager) {
this.repository = repository;
this.passwordEncoder = passwordEncoder;
this.jwtTokenProvider = jwtTokenProvider;
this.authenticationManager = authenticationManager;
}
public String signin(String email, String password) {
try {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(email, password);
authenticationManager.authenticate(token);
return jwtTokenProvider.createToken(email, Collections.singletonList(ROLE_CLIENT));
} catch (AuthenticationException e) {
throw new CustomException("Invalid email/password supplied", HttpStatus.UNPROCESSABLE_ENTITY);
}
}
public String signup(UsersEntity user) {
if (!repository.existsByEmail(user.getEmail())) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
repository.save(user);
return jwtTokenProvider.createToken(user.getEmail(), Collections.singletonList(ROLE_CLIENT));
} else {
throw new CustomException("Email is already in use", HttpStatus.UNPROCESSABLE_ENTITY);
}
}
It successfully signs up user, but when I'm trying to login it always throws an AuthenticationException on authenticationManager.authenticate(token);.
What m I doing wrong?
Full stacktrace

According to the stack traces you provided, the BadCredentialsException is thrown by the method additionalAuthenticationChecks from the DaoAuthenticationProvider class. Either the authentication token you provide is null or either the presented password extractred from this authentication token doesn't match with the password returned by the getPassword method of the userDetails object.
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}

Related

Spring application ALLWAYS responds with status 200

Hello Heros, I am currently developping a spring boot backend application that supports Spring-securit's JWT authentification.
My problem started afeter implimenting the required classes to get the JWT authentification, but the problem started there.
here's the classes that i added , the first code snippet is about the configuration class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private AuthEntryPointJWT authEntryPointJWT;
#Bean
public AuthTokenFilter authentificationJwtTokenFilter(){
return new AuthTokenFilter();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(authEntryPointJWT).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/api/auth/**").permitAll()
.antMatchers("/test/**").permitAll()
.anyRequest().authenticated();
http.authenticationProvider((authenticationProvider()));
http.addFilterBefore(authentificationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
the second is the implimentation of the filter
public class AuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try{
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)){
String username = jwtUtils.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
} catch (Exception e) {
logger.error("Cannot set user authentification : {}", e);
}
}
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;
}
}
the jwthelper class
#Component
public class JwtUtils {
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
private final String jwtSecret="change_me";
private int jwtExpirationMs=3600;
public String generateJwtToken(Authentication authentication) {
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
return Jwts.builder()
.setSubject((userPrincipal.getUsername()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
and this is the controller method for the authentifcation
#PostMapping("/login")
public JwtResponse authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getUsername()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateJwtToken(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream()
.map(item -> item.getAuthority())
.collect(Collectors.toList());
System.out.println(jwt);
JwtResponse returnStatement = new JwtResponse(jwt, userDetails.getUsername(), roles);
return returnStatement;
}
Now im pretty sure the problem is somewhere in the reauest filtration process, i just fail to see where exactly. Please help :)
In the implementation of your AuthTokenFilter, in the method doFilterInternal, you're not calling filterChain.doFilter(request, response);
This method must always be called in a filter, otherwise the application won't continue in its chain of filters and return a default response, 200 in your case.

Authentication filter, doesn't work after migration to SecurityFilterChain

I've decided to migrate from extending WebSecurityConfigurerAdapter to SecurityFilterChain, and I've met problems with AuthenticationFilter. (With an old method configuration was working).
Everytime, when I'm trying to login by hitting api with postman (/api/users/login), I'm getting 401 HttpStatus in opposite to my expectations (before migration I was getting jwt tokens.
(Credentials are correct :d)
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
class SecurityConfiguration {
private final UserDetailsService userDetailsService;
private final SuffixConfiguration suffixConfiguration;
private final AuthorizationService authorizationService;
private final AuthenticationService authenticationService;
private String loginURL = "/api/users/login";
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = authenticationManager(http.getSharedObject(AuthenticationConfiguration.class));
AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager, authenticationService);
authenticationFilter.setFilterProcessesUrl(loginURL);
http.headers().cacheControl();
http.csrf().disable();
http.cors();
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, loginURL).permitAll()
.antMatchers("/api/users/register").permitAll()
.antMatchers("/api/users/refreshToken").permitAll();
http
.addFilter(authenticationFilter)
.addFilterBefore(new AuthorizationFilter(authorizationService), UsernamePasswordAuthenticationFilter.class);
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.authorizeRequests()
.antMatchers("/api/department/add-moderator")
.hasAnyAuthority("[ROLE_ADMIN]");
return http.build();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
Here is the code of AuthorizationService
#Slf4j
#Service
public class AuthorizationServiceImpl implements AuthorizationService {
#Override
public void tryAuthorize(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader(AUTHORIZATION).substring(TOKEN_PREFIX.length());
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET.getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> authorities.add(new SimpleGrantedAuthority(role)));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception exception) {
log.error("Logging exception: d" + exception.getMessage());
throw exception;
}
}
}
And this is AuthenticationService
#Slf4j
#RequiredArgsConstructor
public class AuthenticationServiceImpl implements AuthenticationService {
private final TokenService tokenService;
private final UserModelMapper userModelMapper;
#Override
public UsernamePasswordAuthenticationToken createUsernameAuthenticationToken(HttpServletRequest request, HttpServletResponse response) {
try {
Map<String, String> requestMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);
String username = requestMap.get("username");
String password = requestMap.get("password");
log.info(username, password);
return new UsernamePasswordAuthenticationToken(username, password);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
public Map<Object, Object> successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
UserEntity user = (UserEntity) authResult.getPrincipal();
String issuer = request.getRequestURI();
String accessToken = tokenService.generateAccessToken(user, issuer);
String refreshToken = tokenService.generateRefreshToken(user, issuer);
Map<Object, Object> responseBody = new HashMap<>();
responseBody.put("access_token", accessToken);
responseBody.put("refresh_token", refreshToken);
responseBody.put("user", userModelMapper.mapUserEntityToLoginResponseDTO(user));
return responseBody;
}
}
Also as you suggested, code where users are saved
#RequiredArgsConstructor
#Service
#Transactional
class UserManagementServiceImpl implements UserManagementService {
private final UserRepository userRepository;
private final SuffixConfiguration suffixConfiguration;
private final DepartmentFacade departmentFacade;
private final RoleFacade roleFacade;
private final UserFindingService userFindingService;
private final UserModelMapper userModelMapper;
#Override
public UserResponseDTO registerNewUser(RegisterNewUserRequestDTO requestDTO) throws IllegalArgumentException {
checkIfUserWithGivenUsernameAlreadyExists(requestDTO.username());
UserEntity newUserEntity = createEntityToSave(requestDTO);
userRepository.save(newUserEntity);
return userModelMapper.mapUserEntityToUserResponseDTO(newUserEntity);
}
#Override
public void deleteUser(DeleteUserRequestDTO requestDTO) {
UserEntity requestingUser = userFindingService.getUserEntity(requestDTO.username());
List<RoleEntity> allowedRoles = Arrays.asList(roleFacade.findByRoleType(RoleType.ROLE_ADMIN), roleFacade.findByRoleType(RoleType.ROLE_MODERATOR));
if (requestingUser.getRoles().containsAll(allowedRoles)) {
userRepository.deleteByUsername(requestDTO.username());
} else {
throw new UserDoesNotHavePermissionException(requestingUser.getUsername());
}
}
#Override
public LoginResponseDTO login(LoginRequestDTO requestDTO) {
UserEntity userEntity = userFindingService.getUserEntity(requestDTO.username());
isCredentialsCorrect(requestDTO, userEntity);
return userModelMapper.mapUserEntityToLoginResponseDTO(userEntity);
}
private void isCredentialsCorrect(LoginRequestDTO requestDTO, UserEntity userEntity) {
if (!suffixConfiguration.bCryptPasswordEncoder().matches(requestDTO.password(), userEntity.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
}
private UserEntity createEntityToSave(RegisterNewUserRequestDTO requestDTO) throws IllegalArgumentException {
UserEntity newUserEntity = new UserEntity(requestDTO.username(), encodePassword(requestDTO.password()));
RoleEntity role = roleFacade.createRoleEntity(requestDTO.role());
newUserEntity.getRoles().add(role);
newUserEntity.getDepartmentEntities().add(departmentFacade.getDepartmentEntity(requestDTO.department()));
return newUserEntity;
}
private void checkIfUserWithGivenUsernameAlreadyExists(String username) {
userRepository.findByUsername(username).ifPresent(user -> {
throw new UsernameTakenException(username);
});
}
private String encodePassword(String password) {
if (password != null) {
return suffixConfiguration.bCryptPasswordEncoder().encode(password);
} else {
throw new EmptyPasswordException();
}
}
}
Thanks for help

Jwt login with spring boot and angular

First time posting, please excuse any mistakes in the question.
I'm building a simple website with spring boot and angular and implemented a jwt login using some help from the internet. I can't seem to make it work because the backend is not receiving the username and password credentials when logging in. Below is part of my code:
The spring boot security configuration class. When configure method is triggered, it prints out that the username and password sent are null.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests()
.antMatchers("/api/posts", "/api/forums").permitAll()
.antMatchers("/api/auth/admin/**").hasRole("ADMIN")
.antMatchers("/api/auth/**").hasAnyRole("ADMIN", "USER")
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.loginPage("/api/auth/login")
.failureHandler(new AuthenticationFailureHandler(){
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String email = request.getParameter("email");
String error = exception.getMessage();
System.out.println("A failed login attempt with email: " + email + " and password: " + request.getParameter("password") + ". Reason: " + error);
}
})
.permitAll()
.and()
.httpBasic();
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Jwt support classes
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -7858869558953243875L;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private UserDetailsService userDetailsService;
#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;
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");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
#Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -2550185165626007488L;
public static final long JWT_TOKEN_VALIDITY = 2*60*60;
#Value("${jwt.secret}")
private byte[] secret;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getIssuedAtDateFromToken(String token) {
return getClaimFromToken(token, Claims::getIssuedAt);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(secret)).build().parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
private Boolean ignoreTokenExpiration(String token) {
return false;
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY*1000)).signWith(Keys.hmacShaKeyFor(secret), SignatureAlgorithm.HS512).compact();
}
public Boolean canTokenBeRefreshed(String token) {
return (!isTokenExpired(token) || ignoreTokenExpiration(token));
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
These snippets are part of the auth controller and service classes which are triggered when a post request with login credentials is made. Debugging mysql here also shows that the request username and password are null. LoginRequest and LoginResponse used here just have username, password and username, jwtToken fields respectively.
#Controller
#RequestMapping("/api/auth")
public class AuthController {
private final AuthService authService;
#Autowired
public AuthController(AuthService authService) {
this.authService = authService;
}
#PostMapping("/login")
public ResponseEntity<LoginResponse> login(#RequestBody LoginRequest request) throws Exception {
LoginResponse jwtResponse = authService.login(request);
return new ResponseEntity<>(jwtResponse, HttpStatus.OK);
}
}
public LoginResponse login(LoginRequest request) throws Exception {
authenticate(request.getUsername(), request.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return new LoginResponse(userDetails.getUsername(), token);
}
private void authenticate(String username, String password) throws Exception {
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
SecurityContextHolder.getContext().setAuthentication(authenticate);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
Why are the credentials passed null? This is a backend issue because I checked with postman with no association with frontend code. I tried to send the body with different types like json, xxx-form encoded etc, but nothing seems to work.
Here an image of the postman request.
Is there something wrong with my code? How can I fix this?

AuthenticationProvider with passwordEncoder

When I try to use password encoder to check. It hasn't worked, I see "Bad password".
How should I receive authentication.getCredentials() to check passwords?
If I save user without encoding and try to log in, it works.
#Component
public class AuthClient implements AuthenticationProvider {
#Autowired
private ClientRepository clientRepository;
#Autowired
PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String checkPassword = passwordEncoder.encode(authentication.getCredentials().toString());
Client client = this.clientRepository.findByUsername(username);
if (client == null) {
throw new UsernameNotFoundException("Invalid username/password");
}
String password = client.getPassword();
if (!password.equals(checkPassword)) {
throw new BadCredentialsException("Bad password");
}
Collection<? extends GrantedAuthority> authorities = translate();
return new UsernamePasswordAuthenticationToken(username,password,authorities);
}
private Collection<? extends GrantedAuthority> translate() {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_CLIENT"));
return authorities;
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
you are doing it wrong if you want to test two passwords you need to use passwordEncoder like this:
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, client.getPassword())) {
throw new BadCredentialsException("Bad password");
}

Spring security with JWT always returns 401 unauthorized

1
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name = "userService")
private UserDetailsService userDetailsService;
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
/*auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select login as principal, mot_de_passe as credentials, flag_compte_actif as enabled from utilisateur where login = ?")
.authoritiesByUsernameQuery("SELECT utilisateur.login as principal, profil.designation as role FROM utilisateur INNER JOIN user_profil ON utilisateur.id_user = user_profil.iduserpk INNER JOIN profil ON user_profil.idprofilpk = profil.id_profil WHERE utilisateur.login = ? ")
.rolePrefix("ROLE_");
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());*/
auth.inMemoryAuthentication()
.withUser("admin")
.password("password")
.roles("Administrateur");
}
#Bean
public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("Akal configure method begin");
//http.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);
http.cors().and()
.csrf().disable().
authorizeRequests()
.antMatchers("/token/generate").permitAll()
.anyRequest().authenticated()
.and().formLogin().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
System.out.println("Akal configure method");
http
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
// #Bean
// public BCryptPasswordEncoder passwordEncoder(){
// return new BCryptPasswordEncoder();
// }
#Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
}
2
#RestController
#CrossOrigin("*")
public class AuthenticationController {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private UtilisateurRepository userRepo;
#PostMapping(value = "/token/generate")
public ResponseEntity<?> register(#RequestBody LoginUser loginUser) throws AuthenticationException {
System.out.println("We're in man!");
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginUser.getUsername(),
loginUser.getPassword()
)
);
System.out.println("(Username, Password): (" + loginUser.getUsername() + ", " + loginUser.getPassword() + ")");
SecurityContextHolder.getContext().setAuthentication(authentication);
final Utilisateur user = userRepo.findByLogin(loginUser.getUsername());
final String token = jwtTokenUtil.generateToken(user);
System.out.println("Token Controller Access=> Token Generated: " + token);
return ResponseEntity.ok(new AuthToken(token));
}
}
3
public class AuthToken {
private String token;
public AuthToken(){
}
public AuthToken(String token){
this.token = token;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
4
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
System.out.println("Filtering on...........................................................");
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
//response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
5
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
6
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String header = req.getHeader("Authorization");
String username = null;
String authToken = null;
if (header != null && header.startsWith("Bearer ")) {
authToken = header.replace("Bearer ","");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
logger.error("an error occured during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.warn("the token is expired and not valid anymore", e);
} catch(SignatureException e){
logger.error("Authentication Failed. Username or Password not valid.");
}
} else {
logger.warn("couldn't find bearer string, will ignore the header");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(req, res);
}
}
7
#Component
public class JwtTokenUtil implements Serializable {
static final long EXPIRATIONTIME = 864_000_000; // 10 days
static final String SECRET = "secret";
static final String TOKEN_PREFIX = "Bearer";
static final String HEADER_STRING = "Authorization";
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(Utilisateur user) {
return doGenerateToken(user.getLogin());
}
private String doGenerateToken(String subject) {
Claims claims = Jwts.claims().setSubject(subject);
claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_Administrateur")));
return Jwts.builder()
.setClaims(claims)
.setIssuer("http://devglan.com")
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (
username.equals(userDetails.getUsername())
&& !isTokenExpired(token));
}
}
8
public class LoginUser {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
I only posted these 2 classes, because honestly I have 8 configuration classes, it's gonna be a pain to read! And it's custom JWT code too, but if it's necessary to post it all, let me know.
Other than that, I just cannot identify the problem! Spring console doesn't show any errors whatsoever and when I try to request from Postman, here the outcome:
result
And when I run the request from the browser, it doesn't say 401, it just says bad credentials even though they're correct and I tried with dozens of users too to make sure
Thank you!
Update: I posted the rest of the classes because the problem might not be related to just these 2
In Spring Security 5, if you are using auth.inMemoryAuthentication(), you won't be able to use BCryptPasswordEncoder or StandardPasswordEncoder. You must use your own UserDetailsService in order to get a user and password. Or if you need to test your code, just return NoOpPasswordEncoder.getInstance() in your passwordEncoder() method
SecurityConfig.class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AccountService accountService; //your own implementation of UserDetailsService
....
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.eraseCredentials(true)
.userDetailsService(accountService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
....
}
AccountService.class
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class AccountService implements UserDetailsService {
#Autowired
private AccountRepository accountRepository; //Your database repository
#Autowired
private PasswordEncoder passwordEncoder;
#PostConstruct
protected void initialize() {
save(new Account("user", "demo", "ROLE_USER"));
save(new Account("admin", "admin", "ROLE_ADMIN"));
}
#Transactional
public Account save(Account account) {
account.setPassword(passwordEncoder.encode(account.getPassword()));
accountRepository.save(account);
return account;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountRepository.findOneByEmail(username);
if(account == null) {
throw new UsernameNotFoundException("user not found");
}
return createUser(account);
}
public void signin(Account account) {
SecurityContextHolder.getContext().setAuthentication(authenticate(account));
}
private Authentication authenticate(Account account) {
return new UsernamePasswordAuthenticationToken(createUser(account), null, Collections.singleton(createAuthority(account)));
}
private User createUser(Account account) {
return new User(account.getEmail(), account.getPassword(), Collections.singleton(createAuthority(account)));
}
private GrantedAuthority createAuthority(Account account) {
return new SimpleGrantedAuthority(account.getRole());
}
}
Account.class
#SuppressWarnings("serial")
#Entity
#Table(name = "account")
public class Account implements java.io.Serializable {
#Id
#GeneratedValue
private Long id;
#Column(unique = true)
private String email;
#JsonIgnore
private String password;
private String role = "ROLE_USER";
private Instant created;
protected Account() {
}
public Account(String email, String password, String role) {
this.email = email;
this.password = password;
this.role = role;
this.created = Instant.now();
}
public Long getId() {
return id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Instant getCreated() {
return created;
}
}
You are not using PasswordEncoder in your globalUserDetails() method. Spring security by default take encoded password. Your code should be like.
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
.withUser("admin")
.password("password")
.roles("Admin");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
You are using do not need dataSource because you are using inMemoryAuthenticatin().

Resources