当前位置:网站首页>JWT login authentication + token automatic renewal scheme, well written!

JWT login authentication + token automatic renewal scheme, well written!

2022-07-28 10:04:00 Java technology stack

author : Is he Tiantian here
link :https://juejin.cn/post/6932702419344162823

In the past, I was mainly responsible for the user management module in the project , The user management module will involve encryption and authentication processes , Encryption has been introduced in the previous article , You can read the user management module : How to ensure user data security .

Today, let's talk about the technical selection and implementation of authentication function . There is no technical difficulty, and of course there is no challenge , But it is also a kind of exercise for a vegetable chicken sweet that has not written the certification function before

Technology selection

To realize the authentication function , It's easy to think of JWT perhaps session, But what's the difference between the two ? Advantages and disadvantages of each ? should Pick who ? Deadly third company

difference

be based on session And based on JWT The main difference is where the user's state is saved ,session It's stored in the server Of , and JWT It's saved on the client side Of

The certification process

be based on session The authentication process of
  • The user enters the user name and password in the browser , The server generates a password after passing the password verification session And save to database
  • The server generates a for the user sessionId, And will have sesssionId Of cookie Place in user browser , This will be included in subsequent requests cookie Access information
  • Server acquisition cookie, By acquiring cookie Medium sessionId Search the database to determine whether the current request is valid
be based on JWT The authentication process of
  • The user enters the user name and password in the browser , The server generates a password after passing the password verification token And save to database
  • The front end gets token, Store in cookie perhaps local storage in , This will be included in subsequent requests token Access information
  • Server acquisition token value , By looking up the database to determine the current token Whether it works

Advantages and disadvantages

  • JWT Save on client , In a distributed environment, no extra work is required . and session Because it is saved on the server side , It is necessary to realize multi machine data sharing in distributed environment
  • session It usually needs to be combined with Cookie Achieve Authentication , So you need browser support cookie, Therefore, the mobile terminal cannot be used session Certification scheme
Security
  • JWT Of payload It uses base64 Coded , So in JWT You can't store sensitive data in . and session The information of is stored in the server , Relatively safer

If in JWT Sensitive information is stored in , It can be decoded, which is very unsafe

performance
  • After coding JWT Will be very long ,cookie The size of the limit is generally 4k,cookie It's very likely that I can't put , therefore JWT Generally placed on local storage Inside . And every time the user is in the system http Requests will bring JWT Carry in Header Inside ,HTTP Requested Header Maybe it's better than Body Even bigger . and sessionId It's just a very short string , Therefore use JWT Of HTTP Request is better than use session It's a lot more expensive
Disposable

Stateless is JWT Characteristics , But it also leads to this problem ,JWT It's disposable . I want to modify the contents , You have to issue a new JWT

  • It can't be abandoned
    Once a JWT, It will remain in effect until it expires , You can't abandon it . If you want to abandon , A common treatment is to combine redis
  • Renewal
    If you use JWT Do conversation management , Conventional cookie Renewal programs are usually built into the framework ,session The period of validity 30 minute ,30 If there is an interview within minutes , The validity period is refreshed to 30 minute . The same thing , To change the JWT Effective time of , It's about to issue a new JWT. The simplest way is to refresh every time you request JWT, each HTTP All requests return a new JWT. This method is not only violent but also not elegant , And every request has to be made JWT Encryption and decryption of , Performance issues . Another way is to redis For each JWT Set expiration time , Refresh on every visit JWT The expiration time of

choice JWT or session

I vote for JWT One vote ,JWT There are many disadvantages , But in a distributed environment, you don't need to be like session As an additional implementation of multi machine data sharing , although seesion Multi machine data sharing can be achieved through viscosity sessionsession share session Copy Persistence sessionterracoa Realization seesion Copy And other mature solutions to solve this problem . however JWT No extra work required , Use JWT Is it not fragrant ? And JWT The disadvantages of one-off can be combined with redis Make up for . Improve the strength and make up the weakness , Therefore, in the actual project, we choose to use JWT To authenticate

Function realization

JWT The required depend on

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

JWT Tool class

public class JWTUtil {
    private static final Logger logger = LoggerFactory.getLogger(JWTUtil.class);

    // Private key 
    private static final String TOKEN_SECRET = "123456";

    /**
     *  Generate token, Custom expiration time   millisecond 
     *
     * @param userTokenDTO
     * @return
     */
    public static String generateToken(UserTokenDTO userTokenDTO) {
        try {
            //  Private key and encryption algorithm 
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            //  Set header information 
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HS256");

            return JWT.create()
                    .withHeader(header)
                    .withClaim("token", JSONObject.toJSONString(userTokenDTO))
                    //.withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            logger.error("generate token occur error, error is:{}", e);
            return null;
        }
    }

    /**
     *  test token Whether it is right 
     *
     * @param token
     * @return
     */
    public static UserTokenDTO parseToken(String token) {
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        JWTVerifier verifier = JWT.require(algorithm).build();
        DecodedJWT jwt = verifier.verify(token);
        String tokenInfo = jwt.getClaim("token").asString();
        return JSON.parseObject(tokenInfo, UserTokenDTO.class);
    }
}

explain :

  • Generated token There is no expiration time in the ,token The expiration time of is determined by redis Conduct management
  • UserTokenDTO There is no sensitive information in the , Such as password The field will not appear in token in

Redis Tool class

public final class RedisServiceImpl implements RedisService {
    /**
     *  Expiration time 
     */
    private final Long DURATION = 1 * 24 * 60 * 60 * 1000L;

    @Resource
    private RedisTemplate redisTemplate;

    private ValueOperations<String, String> valueOperations;

    @PostConstruct
    public void init() {
        RedisSerializer redisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setValueSerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        redisTemplate.setHashValueSerializer(redisSerializer);
        valueOperations = redisTemplate.opsForValue();
    }

    @Override
    public void set(String key, String value) {
        valueOperations.set(key, value, DURATION, TimeUnit.MILLISECONDS);
        log.info("key={}, value is: {} into redis cache", key, value);
    }

    @Override
    public String get(String key) {
        String redisValue = valueOperations.get(key);
        log.info("get from redis, value is: {}", redisValue);
        return redisValue;
    }

    @Override
    public boolean delete(String key) {
        boolean result = redisTemplate.delete(key);
        log.info("delete from redis, key is: {}", key);
        return result;
    }

    @Override
    public Long getExpireTime(String key) {
        return valueOperations.getOperations().getExpire(key);
    }
}

RedisTemplate Simple packaging

Business implementation

Landing function
public String login(LoginUserVO loginUserVO) {
    //1. Determine whether the user name and password are correct 
    UserPO userPO = userMapper.getByUsername(loginUserVO.getUsername());
    if (userPO == null) {
        throw new UserException(ErrorCodeEnum.TNP1001001);
    }
    if (!loginUserVO.getPassword().equals(userPO.getPassword())) {
        throw new UserException(ErrorCodeEnum.TNP1001002);
    }

    //2. The user name and password are generated correctly token
    UserTokenDTO userTokenDTO = new UserTokenDTO();
    PropertiesUtil.copyProperties(userTokenDTO, loginUserVO);
    userTokenDTO.setId(userPO.getId());
    userTokenDTO.setGmtCreate(System.currentTimeMillis());
    String token = JWTUtil.generateToken(userTokenDTO);

    //3. Deposit in token to redis
    redisService.set(userPO.getId(), token);
    return token;
}

explain :

  • Determine whether the user name and password are correct
  • If the user name and password are correct, generate token
  • The generated token Save to redis
Logout function
public boolean loginOut(String id) {
     boolean result = redisService.delete(id);
     if (!redisService.delete(id)) {
        throw new UserException(ErrorCodeEnum.TNP1001003);
     }

     return result;
}

The corresponding key Delete it

Update password function
public String updatePassword(UpdatePasswordUserVO updatePasswordUserVO) {
    //1. Change Password 
    UserPO userPO = UserPO.builder().password(updatePasswordUserVO.getPassword())
            .id(updatePasswordUserVO.getId())
            .build();
    UserPO user = userMapper.getById(updatePasswordUserVO.getId());
    if (user == null) {
        throw new UserException(ErrorCodeEnum.TNP1001001);
    }

    if (userMapper.updatePassword(userPO) != 1) {
        throw new UserException(ErrorCodeEnum.TNP1001005);
    }
    //2. Generate a new token
    UserTokenDTO userTokenDTO = UserTokenDTO.builder()
            .id(updatePasswordUserVO.getId())
            .username(user.getUsername())
            .gmtCreate(System.currentTimeMillis()).build();
    String token = JWTUtil.generateToken(userTokenDTO);
    //3. to update token
    redisService.set(user.getId(), token);
    return token;
}

explain :
A new password needs to be regenerated when updating the user password token, And new token Back to the front end , The front-end update is saved in local storage Medium token, At the same time, the update is stored in redis Medium token, This implementation can prevent users from logging in again , The user experience is not too bad

Other instructions
  • In the actual project , Users are divided into ordinary users and administrator users , Only the administrator user has the right to delete users , This function also involves token Operation of the , But I'm too lazy ,demo The project will not be written
  • In the actual project , Password transmission is encrypted

Interceptor class

public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
    String authToken = request.getHeader("Authorization");
    String token = authToken.substring("Bearer".length() + 1).trim();
    UserTokenDTO userTokenDTO = JWTUtil.parseToken(token);
    //1. Determine if the request is valid 
    if (redisService.get(userTokenDTO.getId()) == null 
            || !redisService.get(userTokenDTO.getId()).equals(token)) {
        return false;
    }

    //2. Decide whether to renew 
    if (redisService.getExpireTime(userTokenDTO.getId()) < 1 * 60 * 30) {
        redisService.set(userTokenDTO.getId(), token);
        log.error("update token info, id is:{}, user info is:{}", userTokenDTO.getId(), token);
    }
    return true;
}

explain :
The interceptor mainly does two things , One is right token check , Second, judgment token Whether renewal is required
token check :

  • Judge id Corresponding token Does it not exist , Nonexistence token Be overdue
  • if token Existence is more token Is it consistent , Ensure that only one user operates at the same time

token Automatic renewal : In order to operate infrequently redis, Only when the expiration time is only 30 The expiration time is updated in minutes

Interceptor configuration class

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticateInterceptor())
                .excludePathPatterns("/logout/**")
                .excludePathPatterns("/login/**")
                .addPathPatterns("/**");
    }

    @Bean
    public AuthenticateInterceptor authenticateInterceptor() {
        return new AuthenticateInterceptor();
    }
}

At the end

If there are mistakes and deficiencies , Welcome to point out

Recent hot article recommends :

1.1,000+ Avenue Java Arrangement of interview questions and answers (2022 The latest version )

2. Explode !Java Xie Cheng is coming ...

3.Spring Boot 2.x course , It's too complete !

4. Don't write about the explosion on the screen , Try decorator mode , This is the elegant way !!

5.《Java Development Manual ( Song Mountain version )》 The latest release , Download it quickly !

I think it's good , Don't forget to like it + Forward !

原网站

版权声明
本文为[Java technology stack]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/209/202207280938012951.html