Backend Development 15 min read

Token Storage and Validation in Distributed Micro‑services with Spring Boot and Vue

This article demonstrates how to generate, store, and validate login tokens for both PC and mobile clients in a distributed micro‑service architecture using Spring Boot, Redis, custom annotations, AOP verification, and a Vue front‑end that manages token lifecycle and expiration.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Token Storage and Validation in Distributed Micro‑services with Spring Boot and Vue

1. Introduction

The author explains the need for token validation in a distributed micro‑service environment, aiming to achieve single sign‑on (SSO) while avoiding the complexity of handling every request in a gateway interceptor.

2. Token Storage

Tokens are generated on login, then stored in Redis using a dedicated LoginToken entity that holds separate fields for PC and mobile tokens as well as the login IP.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginToken {
// Token stored in Redis
private String PcLoginToken;
private String MobileLoginToken;
private String LoginIP;
}

The login service creates the appropriate token based on the client type and saves it in Redis with a 7‑day expiration.

@Service
public class loginServiceImpl implements LoginService {
@Autowired UserService userService;
@Autowired RedisUtils redisUtils;
// Rate‑limit per user
@Override
public R Login(LoginEntity entity) {
String username = entity.getUsername();
String password = entity.getPassword().replaceAll(" ", "");
if (redisUtils.hasKey(RedisTransKey.getLoginKey(username))) {
return R.error(BizCodeEnum.OVER_REQUESTS.getCode(), BizCodeEnum.OVER_REQUESTS.getMsg());
}
redisUtils.set(RedisTransKey.setLoginKey(username), 1, 20);
UserEntity user = userService.getOne(new QueryWrapper
().eq("username", username));
if (user != null && SecurityUtils.matchesPassword(password, user.getPassword())) {
String token = JwtTokenUtil.generateToken(user);
String ipAddr = GetIPAddrUtils.GetIPAddr();
if (entity.getType().equals(LoginType.PcType)) {
LoginToken loginToken = new LoginToken(token, null, ipAddr);
redisUtils.set(RedisTransKey.setTokenKey(user.getUserid()+":"+LoginType.PcType), loginToken, 7, TimeUnit.DAYS);
return Objects.requireNonNull(R.ok(BizCodeEnum.SUCCESSFUL.getMsg())
.put(LoginType.PcLoginToken, token))
.put("userid", user.getUserid());
} else if (entity.getType().equals(LoginType.MobileType)) {
LoginToken loginToken = new LoginToken(null, token, ipAddr);
redisUtils.set(RedisTransKey.setTokenKey(user.getUserid()+":"+LoginType.MobileType), loginToken, 7, TimeUnit.DAYS);
return Objects.requireNonNull(R.ok(BizCodeEnum.SUCCESSFUL.getMsg())
.put(LoginType.PcLoginToken, token))
.put("userid", user.getUserid());
} else {
return R.error(BizCodeEnum.NUNKNOW_LGINTYPE.getCode(), BizCodeEnum.NUNKNOW_LGINTYPE.getMsg());
}
} else {
return R.error(BizCodeEnum.BAD_PUTDATA.getCode(), BizCodeEnum.BAD_PUTDATA.getMsg());
}
}
}

Two additional enum classes ( BizCodeEnum and custom exception types) are defined to standardize error handling.

public enum BizCodeEnum {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败"),
...
SUCCESSFUL(200,"successful");
private int code;
private String msg;
// getters ...
}

3. Front‑end Token Management

The Vue component stores the token in localStorage with an expiration wrapper, allowing automatic removal after the configured timeout (7 days).

Storage.prototype.setExpire = (key, value, expire) => {
let obj = {data: value, time: Date.now(), expire: expire};
localStorage.setItem(key, JSON.stringify(obj));
};
Storage.prototype.getExpire = key => {
let val = localStorage.getItem(key);
if (!val) return null;
val = JSON.parse(val);
if (Date.now() - val.time > val.expire) {
localStorage.removeItem(key);
return null;
}
return val.data;
};

Login form submission sends credentials to /user/user/login ; on success the returned token and user‑id are saved with the above helper and the user is redirected to the personal page.

this.axios({
url: "/user/user/login",
method: 'post',
data: {username: this.formLogin.username, password: this.formLogin.password, type: "PcType"}
}).then(res => {
res = res.data;
if (res.code === 0) {
localStorage.setExpire("LoginToken", res.PcLoginToken, this.OverTime);
localStorage.setExpire("userid", res.userid, this.OverTime);
this.$router.push({path: '/userinfo', query: {userid: res.userid}});
} else { alert(res.msg); }
});

4. Backend Token Verification via AOP

A custom annotation @NeedLogin marks controller methods that require authentication. An aspect intercepts these methods, extracts the token, user‑id and client type from request headers, and validates the token against the Redis entry.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLogin {
String value() default "";
}

The aspect ( VerificationAspect ) throws specific exceptions when validation fails.

@Component
@Aspect
public class VerificationAspect {
@Autowired RedisUtils redisUtils;
@Pointcut("@annotation(com.huterox.common.holeAnnotation.NeedLogin)")
public void verification() {}
@Around("verification()")
public Object verification(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String loginType = request.getHeader("loginType");
String userid = request.getHeader("userid");
String tokenUser = request.getHeader("loginToken");
String tokenKey = RedisTransKey.getTokenKey(userid + ":" + loginType);
if (tokenUser == null || userid == null || loginType == null) {
throw new BadLoginParamsException();
}
if (!redisUtils.hasKey(tokenKey)) {
throw new NotLoginException();
}
LoginToken loginToken = JSON.parseObject(redisUtils.get(tokenKey).toString(), LoginToken.class);
if (loginType.equals(LoginType.PcType) && !loginToken.getPcLoginToken().equals(tokenUser)) {
throw new BadLoginTokenException();
}
if (loginType.equals(LoginType.MobileType) && !loginToken.getMobileLoginToken().equals(tokenUser)) {
throw new BadLoginTokenException();
}
return pjp.proceed();
}
}

Controllers simply annotate methods with @NeedLogin ; the aspect handles all verification logic.

5. Summary

The article provides a complete end‑to‑end solution for token generation, multi‑client storage in Redis, front‑end persistence with expiration, and secure back‑end verification using custom annotations and AOP, suitable for micro‑service architectures that require fine‑grained authentication control.

MicroservicesRedisVueAuthenticationtokenaspect-orientedspring-boot
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.