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.