简体   繁体   中英

JWT token filter not working for spring security for mobile OTP based login

I am trying to implement SMS otp based login in spring boot application. I am not using username and password. I am able to generate the JWT but subsequent request containing JWT in header is not letting me access resources with below error.

 {
        "timestamp": "2020-09-08T00:32:58.576+00:00",
        "status": 403,
        "error": "Forbidden",
        "message": "Access Denied",
        "path": "/hello"
}

My User class is below.
User.class

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import lombok.Data;

@Entity
@Data
public class User{
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String userName;
    private String phoneNumber;
}

I am using below jwt dependency

<dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.5.1</version>
</dependency>

Here is my JWT Token Filter.

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.java.nikitchem.dao.UserDao;
import com.java.nikitchem.exception.ResourceNotFoundException;
import com.java.nikitchem.model.User;
import com.java.nikitchem.serviceImpl.TokenProvider;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private TokenProvider tokenProvider;
    
    @Autowired
    private UserDao userDao;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        final String authHeader = request.getHeader("Authorization");
        String phoneNumber = null;
        String jwt = null;
        
        if(authHeader != null && authHeader.startsWith("Bearer ")) {
            jwt = authHeader.substring(7);
            phoneNumber = tokenProvider.getUserIdFromToken(jwt);
        }
        
        if(phoneNumber!= null && SecurityContextHolder.getContext().getAuthentication() == null) {
            User user = null;
            try {
                user = userDao.getUserByPhone(phoneNumber);
                if(tokenProvider.validateToken(jwt, user)) {
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user, null);
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            } catch (ResourceNotFoundException e) {
                // TODO Auto-generated catch block
                e.getMessage();
            }
            
            
            
        }
        filterChain.doFilter(request,response);
    }
}

Below is my TokenProvider.class

TokenProvider.class

public class TokenProvider {

    private static final Logger logger = LoggerFactory.getLogger(TokenProvider.class);

    private AuthConfig authConfig;

    public TokenProvider(AuthConfig authConfig) {
        this.authConfig = authConfig;
    }

    public String createTokenForUser(User user) {
        Map<String, Object> claims = new HashMap<>();
        return generateToken(claims, user.getPhoneNumber());

    }

    private String generateToken(Map<String, Object> claims, String phoneNumber) {
        return Jwts.builder().setClaims(claims).setSubject(phoneNumber).setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + (1000 * 60 * 60 * 438)))
                .signWith(SignatureAlgorithm.HS256, authConfig.getTOKEN_SECRET()).compact();
    }
    
    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }
    
    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(authConfig.getTOKEN_SECRET()).parseClaimsJws(token).getBody();
    }

    public String getUserIdFromToken(String token) {
        Claims claims = Jwts.parser().setSigningKey(authConfig.getTOKEN_SECRET()).parseClaimsJws(token).getBody();

        return claims.getSubject();
    }
    
    public Date extractExpiration(String token) {
        return extractClaim(token,Claims::getExpiration);
    }
    
    public String extractUserId(String token) {
        return extractClaim(token, Claims::getSubject);
    }
    
    public <T> T extractClaim(String token, Function<Claims, T> claimResolver) {
        final Claims claims = extractAllClaims(token);
        return claimResolver.apply(claims);
    }
    
    public boolean validateToken(String authToken, User user) {
        try {
            Jwts.parser().setSigningKey(authConfig.getTOKEN_SECRET()).parseClaimsJws(authToken);
            final String phoneNumber = getUserIdFromToken(authToken);
            return (!isTokenExpired(authToken) && phoneNumber.equals(user.getPhoneNumber()));
        } catch (SignatureException ex) {
            logger.error("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            logger.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            logger.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            logger.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims string is empty.");
        }
        return false;
    }
}

SecurityConfig.class

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.cors().and().csrf().disable().authorizeRequests().antMatchers("/auth", "/authenticate").permitAll()
                .anyRequest().authenticated()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // Add our custom Token based authentication filter
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
    
}

In JwtRequestFilter.class ,

if (phoneNumber != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(phoneNumber);
            if (tokenProvider.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails,
                        null, userDetails.getAuthorities());
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);

            }
            
        }
        filterChain.doFilter(request, response);

I was trying to pass User object to UsernamePasswordAuthenticationToken, which was breaking Spring Security FilterChain execution, giving 403 error.

After replacing User object to UserDetails.User object with empty string as password ie

new org.springframework.security.core.userdetails.User(user.getPhoneNumber(),"", new ArrayList<>());

(as i am not working with passwords in User.class ) and passing to UsernamePasswordAuthenticationToken for creating AuthenticationToken.

That worked perfectly fine. Thanks to code_mechanic for your help :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM