The functional requirements
After a user logs in to the system, a token is issued to the user. Subsequent requests to access the system must carry the token. If the request does not carry the token or the token expires, the user is prohibited from accessing the system. If the user keeps accessing the system, the token will also need to be automatically extended.
Pay attention to
It is not recommended to use the refreshToken because there is a risk of leakage of the refreshToken. You should send a request to refresh the token using the refreshToken when the token expires.Copy the code
Functional analysis
1. Generation of token
Use the now popular JWT.
2. Automatic extension of token
To realize the automatic extension of token, the system cannot issue a token to the user, so by changing one, Generate two tokens for the user, one for API access and one for refreshToken when the token expires. And the lifetime of refreshToken is longer than the lifetime of token.
3. Protection of system resources
You can use Spring Security to secure various resources on your system.
4. How do users pass tokens
The transfer of token and refreshToken in the system is placed in the request header.
Implementation approach
1. Generate token and refreshToken
When the user logs in to the system, the background generates token and refreshToken for the user and returns them in the response header
2. The system checks whether the token is valid
-
Handle the token if it is valid
-
Token invalid, how to use refreshToken to generate a new token
The core code is as follows
1. Filter code, token judgment and re-generation
package com.huan.study.security.token;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huan.study.security.configuration.TokenProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
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.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/ * * *@author huan 2020-06-07 - 14:34
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class TokenAuthenticateFilter extends OncePerRequestFilter {
private final TokenProperties tokenProperties;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// Get the authentication header
String authorizationHeader = request.getHeader(tokenProperties.getAuthorizationHeaderName());
if(! checkIsTokenAuthorizationHeader(authorizationHeader)) { log.debug("Get the value of the Authorization header :[{}] but not issued after login in our system.", authorizationHeader);
filterChain.doFilter(request, response);
return;
}
// Obtain the real token
String realToken = getRealAuthorizationToken(authorizationHeader);
// Parse JWT token
Jws<Claims> jws = JwtUtils.parserAuthenticateToken(realToken, tokenProperties.getSecretKey());
// Token is invalid
if (null == jws) {
writeJson(response, "Authentication token is illegal");
return;
}
// Whether the token expires
if (JwtUtils.isJwtExpired(jws)) {
// Handle expiration
handleTokenExpired(response, request, filterChain);
return;
}
// Build the authentication object
JwtUtils.buildAuthentication(jws, tokenProperties.getUserId());
filterChain.doFilter(request, response);
}
/** * handle token expiration cases **@param response
* @param request
* @param filterChain
* @return
* @throws IOException
*/
private void handleTokenExpired(HttpServletResponse response, HttpServletRequest request, FilterChain filterChain) throws IOException, ServletException {
// Get the refresh token
String refreshTokenHeader = request.getHeader(tokenProperties.getRefreshHeaderName());
// Check whether refresh-token is issued in our system
if(! checkIsTokenAuthorizationHeader(refreshTokenHeader)) { log.debug("Got the value of refresh authentication header :[{}] :[{}] but not issued after login in our system.", tokenProperties.getRefreshHeaderName(), refreshTokenHeader);
writeJson(response, "Token expired, refresh Token is not issued by our system");
return;
}
/ / resolution refresh token
Jws<Claims> refreshToken = JwtUtils.parserAuthenticateToken(getRealAuthorizationToken(refreshTokenHeader),
tokenProperties.getSecretKey());
// Check whether refresh-token is invalid
if (null == refreshToken) {
writeJson(response, "Refresh Token is illegal");
return;
}
// Check whether refresh- Token has expired
if (JwtUtils.isJwtExpired(refreshToken)) {
writeJson(response, "Refresh Token expired");
return;
}
// Reissue the token
String newToken = JwtUtils.generatorJwtToken(
refreshToken.getBody().get(tokenProperties.getUserId()),
tokenProperties.getUserId(),
tokenProperties.getTokenExpireSecond(),
tokenProperties.getSecretKey()
);
response.addHeader(tokenProperties.getAuthorizationHeaderName(), newToken);
// Build the authentication object
JwtUtils.buildAuthentication(JwtUtils.parserAuthenticateToken(newToken, tokenProperties.getSecretKey()), tokenProperties.getUserId());
filterChain.doFilter(request, response);
}
/** * write json data to the front-end **@param response
* @throws IOException
*/
private void writeJson(HttpServletResponse response, String msg) throws IOException {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
Map<String, String> params = new HashMap<>(4);
params.put("msg", msg);
response.getWriter().print(OBJECT_MAPPER.writeValueAsString(params));
}
/** * Get the real token string **@param authorizationToken
* @return* /
private String getRealAuthorizationToken(String authorizationToken) {
return StringUtils.substring(authorizationToken, tokenProperties.getTokenHeaderPrefix().length()).trim();
}
/** * Check whether the token is issued after login in the system@param authorizationHeader
* @return* /
private boolean checkIsTokenAuthorizationHeader(String authorizationHeader) {
if (StringUtils.isBlank(authorizationHeader)) {
return false;
}
if(! StringUtils.startsWith(authorizationHeader, tokenProperties.getTokenHeaderPrefix())) {return false;
}
return true; }}Copy the code
2. JWT utility class code
package com.huan.study.security.token;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultJws;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
/** * JWT utility class **@author huan
* @dateThe 2020-05-20-17:09 * /
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JwtUtils {
/** * parse JWT token **@paramToken needs to parse json *@paramSecretKey key *@return* /
public static Jws<Claims> parserAuthenticateToken(String token, String secretKey) {
try {
final Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
return claimsJws;
} catch (ExpiredJwtException e) {
return new DefaultJws<>(null, e.getClaims(), "");
} catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException | IncorrectClaimException e) {
log.error(e.getMessage(), e);
return null; }}/** * Determine whether the JWT is expired **@param jws
* @returnTrue: expired false: not expired */
public static boolean isJwtExpired(Jws<Claims> jws) {
return jws.getBody().getExpiration().before(new Date());
}
/** * Build a certified authentication object */
public static Authentication buildAuthentication(Jws<Claims> jws, String userIdFieldName) {
Object userId = jws.getBody().get(userIdFieldName);
TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(userId, null.new ArrayList<>(0));
SecurityContextHolder.getContext().setAuthentication(testingAuthenticationToken);
return SecurityContextHolder.getContext().getAuthentication();
}
/** * Generates a JWT token */
public static String generatorJwtToken(Object loginUserId, String userIdFieldName, Long expireSecond, String secretKey) {
Date expireTime = Date.from(LocalDateTime.now().plusSeconds(expireSecond).atZone(ZoneId.systemDefault()).toInstant());
return Jwts.builder()
.setHeaderParam("typ"."JWT")
.setIssuedAt(newDate()) .setExpiration(expireTime) .claim(userIdFieldName, loginUserId) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }}Copy the code
The complete code
Code gitee.com/huan1993/Sp…