英文:
JWT token filter not working for spring security for mobile OTP based login
问题
我正在尝试在Spring Boot应用程序中实现基于SMS一次性密码(OTP)的登录。我不使用用户名和密码。
我能够生成JWT令牌,但是在后续的请求中,包含在标头中的JWT令牌不允许我访问资源,出现以下错误:
{
"timestamp": "2020-09-08T00:32:58.576+00:00",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/hello"
}
我的User类如下。<br>
**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;
}
我正在使用以下JWT依赖项
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.5.1</version>
</dependency>
这是我的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.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);
}
}
以下是我的TokenProvider类
**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);
}
}
英文:
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.<br>
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);
}
}
答案1
得分: 1
在 **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);
我试图将 User 对象传递给 UsernamePasswordAuthenticationToken,这破坏了 Spring Security 的 FilterChain 执行,导致了 403 错误。
在将 User 对象替换为 UserDetails.User 对象并将密码设置为空字符串后,即:
new org.springframework.security.core.userdetails.User(user.getPhoneNumber(), "", new ArrayList<>());
(因为在 **User.class** 中不涉及密码)
然后将其传递给 UsernamePasswordAuthenticationToken 以创建 AuthenticationToken。
这完美地解决了问题。
感谢 [code_mechanic][1] 提供的帮助 :)
[1]: https://stackoverflow.com/users/1712172/code-mechanic
英文:
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 i.e.
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论