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 session、session share 、session Copy 、 Persistence session、terracoa 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 !







![[OpenHarmony] [RK2206] 构建OpenHarmony编译器 (二)](/img/0c/2e8290403d64ec43d192969f776724.png)
