功能:生成token 发送token 、token验证、短信验证码登录
1.创建新工程
2.创建子模块 名为 gateway 用来配置网关,创建名为usercenter的子模块用来做用户认证服务
3.加入spring cloud gateway 、redis、jwt 的相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
4.在gateway模块新建filter包新建AuthFilter类并实现GlobalFilter类和Ordered类用作网关过滤器代码如下,当请求与路由匹配时,Web 处理程序会将所有的GlobalFilter和特定的GatewayFilter添加到过滤器链中。这个组合过滤器链是按org.springframework.core.Ordered接口排序的,也通过实现getOrder()方法来设置。
package com.lzbx.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lzbx.base.constant.JwtConstant;
import com.lzbx.base.to.ResultJson;
import com.lzbx.gateway.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.Builder;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "auth.skip")
@Data
public class AuthFilter implements GlobalFilter, Ordered {
private List<String> uris;
private List<String> checktoken;
@Value("${jwt.blacklist.format}")
private String jwtBlacklist;
@Value("${jwt.token.format}")
private String jwtToken;
@Autowired
JwtUtils jwtUtils;
@Autowired
StringRedisTemplate redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type","application/json; charset=utf-8");
String path = request.getURI().getPath();
//如果访问路径在定义过滤路径之中,直接放行
//boolean containUri=this.uris.contains(path);
if (path.contains("/openapi/")){
return chain.filter(exchange);
}
//log.error("放行路径{},当前路径 {},是否放行 {}",Arrays.asList(uris),path,containUri);
String token = "";
//得到请求头中Authorization的token值
List<String> tokenHead = request.getHeaders().get(JwtConstant.tokenHeader);
if (tokenHead!=null){
token=tokenHead.get(0);
}
//验证token
//没有token,没有权限
if (StringUtils.isEmpty(token)){
//50000: no token
DataBuffer dataBuffer = createResponseBody(50000,"无访问权限",response);
return response.writeWith(Flux.just(dataBuffer));
}
//有token,token不合法
Claims claim = jwtUtils.getClaimsFromToken(token);
if(claim==null){
//50008: Illegal token
DataBuffer dataBuffer = createResponseBody(50008,"非法token",response);
return response.writeWith(Flux.just(dataBuffer));
}
String username = jwtUtils.getUserNameFromToken(token);
String id = jwtUtils.getUserIdFromToken(token);
String group = jwtUtils.getGroupFromToken(token);
//没有有效载荷,token定义为非法
if (ObjectUtils.isEmpty(username)
||ObjectUtils.isEmpty(id)
||ObjectUtils.isEmpty(group)){
DataBuffer dataBuffer = createResponseBody(50008,"非法token",response);
return response.writeWith(Flux.just(dataBuffer));
}
//token可用性判断后 才可以刷新和重新登录
boolean checkUri = this.checktoken.contains(path);
if (checkUri){
return chain.filter(exchange);
}
//log.error("验证token后放行路径{},当前路径 {},是否放行 {}",Arrays.asList(checktoken),path,checkUri);
//有token,但已被加入黑名单,只能选择再登录
String key = String.format(jwtBlacklist,group);
String blackToken=redisTemplate.opsForValue().get(key);
if (!ObjectUtils.isEmpty(blackToken)){
//50010: Token out;
DataBuffer dataBuffer = createResponseBody(50010,username+" 已登出",response);
return response.writeWith(Flux.just(dataBuffer));
}
// redis中id对应的token不存在
// 或者请求中的token和redis中活跃的token不匹配,只能选择再登录
String redisToken = (String)redisTemplate.opsForHash().get(jwtToken,id);
//为空说明 被 注销/重新登录 操作删除
if (ObjectUtils.isEmpty(redisToken)||!redisToken.equals(token)){
//50010: Token out;
DataBuffer dataBuffer = createResponseBody(50010,username+" 信息不匹配,无法继续操作",response);
return response.writeWith(Flux.just(dataBuffer));
}
//有身份,过免登录时间
if(!jwtUtils.isHoldTime(token)){
//50014: Token expired;
DataBuffer dataBuffer = createResponseBody(50014,"token过期",response);
return response.writeWith(Flux.just(dataBuffer));
}
//token有效期内,可以进行登出
boolean expiredTimeUri = path.equals("/jwt-client/logout");
if (expiredTimeUri){
return chain.filter(exchange);
}
//log.error("当前路径 {},是否放行 {}",path,expiredTimeUri);
//token 失效
if(jwtUtils.canRefresh(token)){
String refreshToken = jwtUtils.refreshToken(token);
//更新请求头
ServerHttpRequest httpRequest = request.mutate().header(JwtConstant.tokenHeader, refreshToken).build();
ServerWebExchange webExchange = exchange.mutate().request(httpRequest).build();
return chain.filter(webExchange);
}
return chain.filter(exchange);
}
private DataBuffer createResponseBody(int code,String message,ServerHttpResponse response){
ResultJson result = ResultJson.returnData(message);
ObjectMapper objectMapper = new ObjectMapper();
String str="";
try {
str=objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
//log.error("json转换错误 {}",e.getLocalizedMessage());
}
DataBuffer dataBuffer = response.bufferFactory().wrap(str.getBytes());
return dataBuffer;
}
@Override
public int getOrder() {
return 0;
}
}
5.在gateway模块下创建utils包并创建JwtUtils类用来做token生成的一些操作代码如下
package com.lzbx.gateway.utils;
import com.lzbx.base.constant.JwtConstant;
import com.lzbx.base.entity.Admin;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* JwtToken生成的工具类
*
* JWT token的格式:header.payload.signature
*
* header的格式(算法、token的类型):
* {"alg": "HS512","typ": "JWT"}
* payload的格式(用户名、创建时间、生成时间):
* {"id":,"sub":"","created":,"exp":}
*/
@Component
public class JwtUtils {
@Value("${jwt.subject.name}")
private String SUBJECT;
//秘钥
@Value("${jwt.secret.key}")
private String APPSECRET;
//过期时间,毫秒,30分钟
@Value("${jwt.expire.time}")
private long EXPIRE;
@Value("${jwt.hold.time}")
private int holdTime;
@Value("${jwt.hold.type}")
private int holdType;
/**
* 根据用户信息生成token
*/
public String generateToken(Admin admin) {
Map<String, Object> claims = new HashMap<String, Object>();
claims.put(JwtConstant.CLAIM_KEY_USERID, admin.getId());
claims.put(JwtConstant.CLAIM_KEY_CREATED, new Date());
claims.put(JwtConstant.CLAIM_KEY_HOLDTIME,generateLoginDate());
claims.put(JwtConstant.CLAIM_KEY_GROUP,generateGroup());
return generateToken(claims);
}
/**
* 根据负责生成JWT的token
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setSubject(SUBJECT)
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, APPSECRET)
.compact();
}
/**
* 从token中获取JWT中的负载
*/
public Claims getClaimsFromToken(String token) {
Claims claims = null;
token = token.replace("Bearer ","");
try {
claims = Jwts.parser()
.setSigningKey(APPSECRET)
.parseClaimsJws(token)
.getBody();
}catch (ExpiredJwtException e) {
String id = (String) e.getClaims().get(JwtConstant.CLAIM_KEY_USERID).toString();
String username = (String) e.getClaims().get(JwtConstant.CLAIM_KEY_USERID).toString();
//log.error("JWT载荷中 用户ID:{} 用户名:{}", id, username);
claims=e.getClaims();
} catch (MalformedJwtException e){
//log.error("Json格式错误 {}",e.getLocalizedMessage());
} catch (SignatureException e){
//log.error("Json格式错误 {}",e.getLocalizedMessage());
} catch(IllegalArgumentException e){
//log.error("错误 {}",e.getLocalizedMessage());
}
return claims;
}
/**
* 生成token的过期时间
*/
public Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + EXPIRE);
}
/**
* 生成token的免登录时间
*/
public Date generateLoginDate() {
//有效期内可刷新token
Calendar calendar = new GregorianCalendar();
//当天+2
calendar.add(holdType,holdTime);
return calendar.getTime();
}
/**
* 生成token的group
*/
public String generateGroup() {
String group = UUID.randomUUID().toString();
group = group.replace(".","");
return group;
}
/**
* 从token中获取登录用户名
*/
public String getUserNameFromToken(String token) {
Claims claims = getClaimsFromToken(token);
String username = (String) claims.get(JwtConstant.CLAIM_KEY_USERID).toString();
return username;
}
/**
* 从token中获取过期时间
*/
public Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
Date expiredDate = claims.getExpiration();
//log.error("token中过期时间 {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(expiredDate));
return expiredDate;
}
/**
* 从token中获取group
*/
public String getGroupFromToken(String token) {
Claims claims = getClaimsFromToken(token);
String group = (String)claims.get(JwtConstant.CLAIM_KEY_GROUP);
//log.error("token中的用户组 {}", group);
return group;
}
/**
* 从token中获取登录用户名id
*/
public String getUserIdFromToken(String token) {
Claims claims = getClaimsFromToken(token);
String id = claims.get(JwtConstant.CLAIM_KEY_USERID).toString();
return id;
}
/**
* 从token中获取登录截止时间
*/
public Date getHoldTime(String token){
Claims claims = getClaimsFromToken(token);
long dateTime = (long)claims.get(JwtConstant.CLAIM_KEY_HOLDTIME);
Date date = new Date(dateTime);
//log.info("原数据值:{} 该token免登录时间截止至 {}",dateTime,
// new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
return date;
}
/**
* 验证token是否还有效
*
* @param token 客户端传入的token
* @param admin 从数据库中查询出来的用户信息
*/
public boolean validateToken(String token, Admin admin) {
String username = getUserNameFromToken(token);
return username.equals(admin.getUsername()) && !isTokenExpired(token);
}
/**
* 判断token是否已经失效
*/
public boolean isTokenExpired(Date expiredDate) {
boolean before = new Date().before(expiredDate);
return before;
}
/**
* 判断token是否已经失效
*/
public boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
boolean before = new Date().before(expiredDate);
return before;
}
/**
* 免登录截止时间判断
*/
public boolean isHoldTime(String token){
Date date = getHoldTime(token);
return new Date().before(date);
}
/**
* 判断token是否可以被刷新
*/
public boolean canRefresh(String token) {
return !isTokenExpired(token);
}
/**
* 刷新token
*/
public String refreshToken(String token) {
Claims claims = getClaimsFromToken(token);
claims.put(JwtConstant.CLAIM_KEY_CREATED, new Date());
claims.put(JwtConstant.CLAIM_KEY_GROUP,generateGroup());
//网关仅更新token有效期,不更新免登录时间
//claims.put(JwtConstant.CLAIM_KEY_HOLDTIME,generateLoginDate());
return generateToken(claims);
}
}
JwtConstant 代码如下
public class JwtConstant {
public static final String tokenHeader = "Authorization";
public static final String CLAIM_KEY_USERID = "userid";
public static final String CLAIM_KEY_CREATED = "created";
public static final String CLAIM_KEY_HOLDTIME = "holdtime";
//用于区分token,充当存入redis中的key
public static final String CLAIM_KEY_GROUP = "group";
}
6.现在可以用来做登录操作啦
controller
/**
*
* 短信验证码注册+登录
*
* @param mobile 手机号
* @param code 短信验证码
* @return
*/
@GetMapping(value = "/openapi/smsCodeRegsterLogin")
public ResultJson smsCodeRegsterLogin(String mobile, String code) throws Exception {
return userLoginService.smsCodeRegsterLogin(mobile,code);
}
service
/**
*
* 短信验证码注册+登录
*
* @param mobile 手机号
* @param code 短信验证码
* @return
*/
@Override
public ResultJson smsCodeRegsterLogin(String mobile, String code) throws Exception {
//参数效验
if (ObjectUtils.isEmpty(mobile)) {
return ResultJson.returnError("手机号码不能为空");
}
if (ObjectUtils.isEmpty(code)) {
return ResultJson.returnError("验证码不能为空");
}
try {
String code1 = redisTemplate.opsForValue().get(RedisSmsConstants.SMS_USER_REGISTER_PREFIX + mobile);
if (code1.isEmpty()) {
return ResultJson.returnError("验证码已经过期.");
}
if (Integer.parseInt(code) != Integer.parseInt(code1)) {
return ResultJson.returnError("验证码不正确.");
}
} catch (Exception ex) {
return ResultJson.returnError("验证码已经过期.");
}
UserBaseInfo userBaseInfo = iUserBaseInfoService.getOne(new LambdaQueryWrapper<UserBaseInfo>()
.eq(UserBaseInfo::getMobile, AES.encrypt(mobile)));
if (userBaseInfo != null) {
return jwtUtils.getLoginToken(userBaseInfo);
} else {
userBaseInfo = new UserBaseInfo();
userBaseInfo.setMobile(mobile);
ResultJson rs = iUserBaseInfoService.userRegister(userBaseInfo);
if (rs.getStatus() == 1) {
return jwtUtils.getLoginToken(userBaseInfo);
}
}
return ResultJson.returnError("登录失败");
}
相关方法以及代码 这两个方法在上面JwtUtils类里
/**
*
* 获取登录token redis 缓存处理
*
* @param userBaseInfo
* @return
*/
public ResultJson getLoginToken(UserBaseInfo userBaseInfo)
{
String key = String.format(jwtUsername, userBaseInfo.getUserId());
log.info("redis key: {}", key);
//判断redis中是否存在该用户名
String name = (String) redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(name)) {
redisTemplate.delete(key);
//return ResultJson.returnError(name + " 已经登录!");
}
//成功生成token
String token = generateToken(userBaseInfo);
//用户名有效时间 - 用户免登录时间
//得到jwt中的截止时间
long time = generateLoginDate().getTime();
long expired = time - new Date().getTime();
log.info("原始数据: {} redis {} 截止时间: {}", time, key,
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(time)));
//信息放入redis - set key value EX 10
redisTemplate.opsForValue().set(key,userBaseInfo.getUserId().toString(), expired, TimeUnit.MILLISECONDS);
//存当前id对应正在使用的token
//hset key field value
redisTemplate.opsForHash().put(jwtToken, userBaseInfo.getUserId().toString(), token);
log.info("redis hashKey: {} field: {} token:{}", jwtToken, userBaseInfo.getUserId(), token);
return ResultJson.returnData("token", token);
}
/**
* 根据用户信息生成token
*/
public String generateToken(UserBaseInfo userBaseInfo) {
Map<String, Object> claims = new HashMap<String, Object>();
claims.put(JwtConstant.CLAIM_KEY_USERID, userBaseInfo.getUserId());
// claims.put(JwtConstant.CLAIM_KEY_CREATED, new Date());
// claims.put(JwtConstant.CLAIM_KEY_HOLDTIME,generateLoginDate());
claims.put(JwtConstant.CLAIM_KEY_GROUP,generateGroup());
return generateToken(claims);
}
至此spring cloud gateway +jwt 认证服务实现完啦