问题描述
我正在尝试在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类在下面。
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.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;
}
}
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);
}
}
解决方法
在 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的帮助:)