功能:生成token 发送token 、token验证、短信验证码登录

1.创建新工程

2.创建子模块 名为 gateway 用来配置网关,创建名为usercenter的子模块用来做用户认证服务

spring gatewat routes配置 spring gateway jwt_spring

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 认证服务实现完啦