用户登录
校验token,并解析token获取用户信息,并把用户信息存入redis中,分别存入两种形式;
一种是以key-value字符串形式存入redis,一种是以有序集合zset形式存入redis;
前一种便于查看当前用户是否已登录,后一种便于管理人员查看当前在线用户并执行相关操作
@Override
public Map login(JwtAuthenticationRequest authenticationRequest) throws Exception {
//校验用户信息
UserInfo info = permissionService.validate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
if (!StringUtils.isEmpty(info.getId())) {
JWTInfo jwtInfo = new JWTInfo(info.getUsername(), info.getId() + "", info.getName());
//生成token
String token = jwtTokenUtil.generateToken(jwtInfo);
Map<String, String> result = new HashMap<>();
result.put("accessToken", token);
result.put("id", info.id);
//创建会话
writeOnlineLog(jwtInfo);
return result;
}
throw new UserInvalidException("用户不存在或账户密码错误!");
}
/**
* 创建当前会话
* @param jwtInfo
*/
@Async
public void writeOnlineLog(JWTInfo jwtInfo) {
final UserAgent userAgent = UserAgent.parseUserAgentString(WebUtils.getRequest().getHeader("User-Agent"));
final String ip = IpUtils.getRemoteIP(WebUtils.getRequest());
String address = AddressUtils.getRealAddressByIP(ip);
OnlineLog onlineLog = new OnlineLog();
String os = userAgent.getOperatingSystem().getName();// 获取客户端操作系统
String browser = userAgent.getBrowser().getName();// 获取客户端浏览器
onlineLog.setBrowser(browser);
onlineLog.setIpaddr(ip);
onlineLog.setTokenId(jwtInfo.getTokenId());
onlineLog.setLoginTime(System.currentTimeMillis());
onlineLog.setUserId(jwtInfo.getId());
onlineLog.setUserName(jwtInfo.getName());
onlineLog.setLoginLocation(address);
onlineLog.setOs(os);
//当前用户token存入redis
stringRedisTemplate.opsForValue().set(RedisKeyConstant.REDIS_KEY_TOKEN + ":" + jwtInfo.getTokenId(), JSON.toJSONString(onlineLog, false), expire, TimeUnit.MINUTES);
//当前用户token存入zset有序集合,关联分数
stringRedisTemplate.opsForZSet().add((RedisKeyConstant.REDIS_KEY_TOKEN), jwtInfo.getTokenId(), 0);
}
zset 不允许重复的成员。zset 的每个元素都会关联一个分数(分数可以重复),redis 通过分数来为集合中的成员进行从小到大的排序
请求过滤器校验
public class AccessGatewayFilter implements GlobalFilter {
private IJWTInfo getJWTUser(ServerHttpRequest request, ServerHttpRequest.Builder ctx) throws Exception {
//获取请求中的token信息
List<String> strings = request.getHeaders().get(userAuthConfig.getTokenHeader());
String authToken = null;
if (strings != null) {
authToken = strings.get(0);
}
if (StringUtils.isBlank(authToken)) {
strings = request.getQueryParams().get("token"); //如果 Authorization信息为空,则获取携带参数 token
if (strings != null) {
authToken = strings.get(0);
}
}
IJWTInfo infoFromToken = userAuthUtil.getInfoFromToken(authToken);
//根据token获取用户会话信息
String s = stringRedisTemplate.opsForValue().get(RedisKeyConstant.REDIS_KEY_TOKEN + ":" + infoFromToken.getTokenId());
if (StringUtils.isBlank(s)) {
//无会话表示当前登录已过期 / 已经下线(包括被强制下线)
throw new UserTokenException("User token expired!");
}
ctx.header(userAuthConfig.getTokenHeader(), authToken);
BaseContextHandler.setToken(authToken);
return infoFromToken;
}
查询用户登录状态/强制下线
@RestController
@RequestMapping("online")
public class OnlineController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 查询在线用户列表
* @param limit 每页条数
* @param offset 页码
* @return
*/
@RequestMapping("/page")
public TableResultResponse<OnlineLog> getOnlineInfo(@RequestParam(defaultValue = "10") int limit, @RequestParam(defaultValue = "1") int offset) {
stringRedisTemplate.opsForValue();
//从redis的zset有序集合中根据分数分页查询用户id列表
Set<String> ids = stringRedisTemplate.opsForZSet().reverseRange(RedisKeyConstant.REDIS_KEY_TOKEN, (offset - 1) * limit, (offset - 1) * limit + limit - 1);
List<OnlineLog> logs = new ArrayList<>(ids.size());
for (String id : ids) {
String s = stringRedisTemplate.opsForValue().get(RedisKeyConstant.REDIS_KEY_TOKEN + ":" + id);
if (s == null) {
stringRedisTemplate.opsForZSet().remove(RedisKeyConstant.REDIS_KEY_TOKEN, id);
} else {
logs.add(JSON.parseObject(s, OnlineLog.class));
}
}
return new TableResultResponse<>(stringRedisTemplate.opsForZSet().size(RedisKeyConstant.REDIS_KEY_TOKEN), logs);
}
/**
* 强退用户
* @param tokenId
* @return
*/
@RequestMapping("/{id}")
public ObjectRestResponse forceLogout(@PathVariable("id") String tokenId) {
//redis中删除用户会话信息,是用户无法通过请求过滤器校验
stringRedisTemplate.delete(RedisKeyConstant.REDIS_KEY_TOKEN + ":" + tokenId);
stringRedisTemplate.opsForZSet().remove(RedisKeyConstant.REDIS_KEY_TOKEN, tokenId);
return new ObjectRestResponse<>();
}
}