下面就为你详细讲解“SpringBoot JWT实现token登录刷新功能”的完整攻略。
什么是JWT
JWT即Json Web Token,是基于JSON格式的令牌,包含有用户的一些身份信息和一些验证信息。在用户登录后,服务器会生成一个JWT给前端返回,在之后的请求中,前端只需在HTTP头中携带该令牌即可实现状态保持。
实现流程
首先,我们需要在项目中引入JWT的依赖,以Maven为例,添加以下依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
其中,jjwt-api是API接口,jjwt-impl是实现,jjwt-jackson是用于JSON序列化和反序列化的库。
在编写后端登录接口时,我们需要先对用户进行密码验证,验证通过后,我们需要生成一个JWT并将其返回给前端。代码示例:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtTokenUtil {
//定义过期时间为1小时
@Value("${jwt.expiration}")
private long expiration;
@Value("${jwt.secret}")
private String secret;
//生成JWT
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", username);
claims.put("created", new Date());
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
}
其中,@Value注解从配置文件中获取属性值,用于生成JWT时的过期时间和加密密钥。生成JWT时,我们需要用到Jwts工具类,将用户信息放入JWT的claims中,并设置JWT过期时间和签名。
在后续的请求中,前端需要在HTTP头中携带该JWT,后端需要验证该JWT是否正确以及其是否过期。代码示例:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
@Value("${jwt.secret}")
private String secret;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
if (checkJWTToken(request)) {
Claims claims = validateToken(request);
if (claims.get("authorities") != null) {
setUpSpringAuthentication(claims);
} else {
SecurityContextHolder.clearContext();
}
}
filterChain.doFilter(request, response);
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
return;
}
}
private Claims validateToken(HttpServletRequest request) {
String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
return Jwts.parser().setSigningKey(secret.getBytes())
.parseClaimsJws(jwtToken).getBody();
}
private boolean checkJWTToken(HttpServletRequest request) {
String authenticationHeader = request.getHeader(HEADER);
if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX))
return false;
return true;
}
private void setUpSpringAuthentication(Claims claims) {
List<String> authorities = (List) claims.get("authorities");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
claims.getSubject(), null, authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
在该过滤器中,我们通过验证JWT是否存在、是否以正确的格式携带、验证JWT的签名和过期时间来确认JWT的合法性。如果合法,我们将通过JWT中的用户信息设置SpringSecurity的authentication信息,以保证请求的安全性。
实现刷新功能
JWT有一个明显的缺点,即一旦JWT过期了,就必须重新向服务器请求新的JWT。但这样做的话会影响用户体验。为了提高用户体验,我们可以实现JWT的刷新功能。
刷新功能的实现非常简单,当JWT快要过期时,我们生成一个新的JWT,新JWT的过期时间为原JWT的两倍。这样,在旧JWT即将过期之前,前端可以使用新JWT请求服务器,避免重新登录。代码示例:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
//省略代码
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
@Value("${jwt.refreshTime}")
private long refreshTime;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
if (checkJWTToken(request)) {
Claims claims = validateToken(request);
if (claims.get("authorities") != null) {
setUpSpringAuthentication(claims);
} else {
SecurityContextHolder.clearContext();
}
if (checkRefreshToken(claims)) {
response.setHeader("Authorization", refreshJWTToken(request));
}
}
filterChain.doFilter(request, response);
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
return;
}
}
private boolean checkRefreshToken(Claims claims) {
Date expirationDate = claims.getExpiration();
Date now = new Date(System.currentTimeMillis());
long timeDiff = expirationDate.getTime() - now.getTime();
if (timeDiff <= refreshTime * 1000 && timeDiff > 0) {
return true;
}
return false;
}
private String refreshJWTToken(HttpServletRequest request) {
String authToken = request.getHeader(HEADER).replace(PREFIX, "");
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(authToken).getBody();
if (claims.getSubject() == null || claims.get("authorities") == null) {
throw new RuntimeException("JWT claims is empty.");
}
String subject = Jwts
.builder()
.setClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 2))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
return PREFIX + subject;
}
}
在该过滤器中,我们添加了两个配置属性,一个是refreshTime,指定了延长JWT的时间,如果当前JWT的过期时间小于refreshTime设定值,我们就会在原JWT的基础生成一个 token,该 token 的过期时间是原 token 的两倍,接下来将新的 token 返回给用户。稍微读懂代码就会知道,JWT token 是由原 token的claims重新组装成新的 token的。
以上就是“SpringBoot JWT实现token登录刷新功能”的实现攻略。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot JWT实现token登录刷新功能 - Python技术站