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

  1. Handle the token if it is valid

  2. 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…