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.
Related
I try to do the security of my project adapting the baeldung documentation and the previous code, and I want to know the normal method of this. I have the following configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Bean
public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception{
return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder)
.and()
.build();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.cors()
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/user/create", "/api/login", "/swagger-ui/**", "/v3/api-docs/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
then I have this token provider
#Component
public class JtwTokenProvider {
#Value("${app.jwt-secret}")
private String jwtSecret;
#Value("${app.jwt-expiration-milliseconds}")
private int jwtExpirationInMs;
public String generateToken(Authentication authentication) {
String username = authentication.getName();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
String token = Jwts.builder().setSubject(username).setIssuedAt(new Date())
.setExpiration(expiryDate).signWith(SignatureAlgorithm.HS512, jwtSecret).compact();
return token;
}
public String getUsernameOfJwt(String token){
Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
return claims.getSubject();
}
public boolean validateToken(String token){
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
}catch (SignatureException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid JWT signature");
}catch (MalformedJwtException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid JWT token");
}catch (ExpiredJwtException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Expired JWT token");
}catch (UnsupportedJwtException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unsupported JWT token");
}catch (IllegalArgumentException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "JWT claims string is empty");
}
}
}
The token filter is the same and this manner is okay? What others exist?
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JtwTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//Get JWT from request
String token = getJwtFromRequest(request);
if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
// Get username from token
String username = tokenProvider.getUsernameOfJwt(token);
// Load user associated with the token
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//Set the authentication in the SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.replace("Bearer ", "");
}
return null;
}
}
Then I used the userdetails service
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username).orElseThrow(
() -> new NotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), getAuthority(user.getRoles()));
}
private Collection<? extends GrantedAuthority> getAuthority(Set<Role> roles){
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}
Finally here is my authentication, this is safe? what is the standart manner of do the same?
#Service
public class LoginServiceImpl implements LoginService{
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JtwTokenProvider jwtTokenProvider;
#Override
public JwtAuthResponse login(LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.generateToken(authentication);
return new JwtAuthResponse(jwt);
}
}
I was waiting your responses thanks :D
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
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?
After updating my spring boot from 2.0.3 to 2.2.1 somehow my Spring Security configuration stop working.
setup is as follow - I would like to have all my request processed by request filter where JWT token is validated and UserDetails are created. So config is pretty easy:
#Configuration
#EnableWebSecurity
public class NoAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
}
and filter class
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class JwtRequestFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(JwtRequestFilter.class);
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtUtils jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String requestTokenHeader = "";
try{
requestTokenHeader = WebUtils.getCookie(request, "token").getValue();
} catch (NullPointerException ex ){}
if (requestTokenHeader != null && requestTokenHeader.contains(".")) {
jwtToken = requestTokenHeader;
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
log.error("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
log.error("JWT Token has expired");
}
}
if (username != null ) {
UserDetails userDetails = null;
userDetails = this.jwtUserDetailsService.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);
}
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
chain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String[] AUTH_WHITELIST = {
"/v2/auth/",
"/api/packetdiscovery"
};
String path = request.getServletPath();
return (StringUtils.startsWithAny(path, AUTH_WHITELIST));
}
}
and by debugging I can see that everything work smoothly until request reach Controller where NullPointerException Is thrown on Principal
#PreAuthorize("hasAuthority('ROLE_USER')")
#GetMapping(value = "/userinfo")
public ResponseEntity<SessionOwner> getSesstionOwner(Principal user) {
return dashboardService.getSessionOwner(user.getName());
}
Could anyone give me advice on how to handle it?
Just started using Jwt tokens when securing my microservices and keep getting a null token in my JwtTokenFilter class when a request is sent in but don't know where from, and finding it hard to understand why?
JwtTokenFilter.class
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtTokenProvider.resolveToken(request);
System.out.println("Token: " + token);
try {
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
SecurityContextHolder.clearContext();
response.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}
filterChain.doFilter(request, response);
}
SecurityConfig.class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.antMatchers("/auth/register").permitAll()
.antMatchers("/auth/{username}").permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}
JwtTokenProvider.class
#Component
public class JwtTokenProvider {
#Value("$security.jwt.token.secret-key")
private String secretKey;
private long validityInMilliseconds = 3600000;
#Autowired
private CustomUserDetails customUserDetails;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String username, List<Role> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).collect(Collectors.toList()));
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = customUserDetails.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
If you need any more classes shown just ask, thanks for the help.
Realised the problem was how I registered it with my Gateway microservice in the spring configuration. All sorted thanks for the help.