Springboot中如何使用JWT令牌认证技术?

  • 主题:JWT令牌认证技术
  • 作者:格智学院
  • 联系:565599455@qq.com

#JWT令牌认证技术 #

什么是JWT?

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间作为JSON对象安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。JWT可以使用秘密(使用HMAC算法)或公钥/私钥对使用RSA或ECDSA来签名。

虽然JWT可以加密,但也提供保密各方之间,我们将重点放在签名令牌。签名的令牌可以验证包含在其中的声明的完整性,而加密的令牌隐藏这些声明以防止其他各方查阅变更。当令牌使用公钥/私钥对签名时,签名也证明只有持有私钥的方才是签名方。

JWT的结构

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了JWT字符串。
就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT包含了三部分:
Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型) ;Payload 负载 (类似于飞机上承载的物品);Signature 签名/签证

在这里插入图片描述

###JWT头
JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。

{

"alg": "HS256",

"typ": "JWT"

}

在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。

最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
###有效载荷
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。

iss:发行人

exp:到期时间

sub:主题

aud:用户

nbf:在此之前不可用

iat:发布时间

jti:JWT ID用于标识该JWT

除以上默认字段外,我们还可以自定义私有字段,如下例:

{

"loginName": "zs",

"userName": "张三",

"admin": true

}

请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。

JSON对象也使用Base64 URL算法转换为字符串保存。

###签名哈希

签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。

首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload) , secret)

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

为什么使用JWT?

随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

JWT架构图:

在这里插入图片描述

##JWT工作机制?

在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web Token(即:JWT)。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,不应将令牌保留的时间超过要求。理论上超时时间越短越好。

每当用户想要访问受保护的路由或资源时,用户代理应该使用Bearer模式发送JWT,通常在Authorization header中。标题内容应如下所示:

Authorization: Bearer <token>	

在某些情况下,这可以作为无状态授权机制。服务器的受保护路由将检查Authorization header中的有效JWT ,如果有效,则允许用户访问受保护资源。如果JWT包含必要的数据,则可以减少查询数据库或缓存信息。
如果在Authorization header中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie。

注意:使用签名令牌,虽然他们无法更改,但是令牌中包含的所有信息都会向用户或其他方公开。这意味着不应该在令牌中放置敏感信息。

##如何使用JWT?
###创建springboot工程

在IDEA中构建一个springboot的web工程,在对应的pom.xml文件中引入以下内容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gezhi</groupId>
    <artifactId>spring-boot-jwt</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-jwt</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <!--导入redis启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--导入jwt工具包-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--导入web启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--导入热部署启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--导入lombox包-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--导入google提供的Java库-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>18.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

###编写application.properties配置文件

server.port=80
server.servlet.context-path=/boots
#jwt设置
#私钥
com.gezhi.springbootjwt.secret=puxubo
#发布者
com.gezhi.springbootjwt.issuer=www.gezhi100.com
#主题
com.gezhi.springbootjwt.subject=userLoginToken
#签发给谁?
com.gezhi.springbootjwt.audience=APP
#令牌过期时间
com.gezhi.springbootjwt.hour=1
#令牌刷新时间
com.gezhi.springbootjwt.minute=30

# Redis数据库索引(默认为0)
spring.redis.database=0  
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379  
# Redis服务器连接密码(默认为空)
spring.redis.password=123456
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=200
# 连接超时时间(毫秒)
spring.redis.timeout=5000

###构建项目包结构

在这里插入图片描述

包结构说明: annotation 注解包,bean 实体包,configure 配置包,exception 异常包,interceptor 拦截器包,
jwt JWT工具包,message 返回消息包,sysmag 系统管理包,usermag 用户管理包,util 工具包

代码说明

####annotation包

FieldMarker 该注解的作用:主要在于说明UserBean中那些字段是JWT的负载字段

package com.gezhi.springbootjwt.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:19
 * 功能描述 标记JWT 字段 用在JavaBean身上
 * 用来描述 对象与Map 进行转换时 属性是否需要忽略
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface FieldMarker {

    /**
    * 功能描述 属性的名称
    * 开发时间 2019/11/26 0026 上午 11:02
    */
    String value();
}

####bean包

UserBean 是一个JavaBean,代表用户资源,此类中某些属性可以作为JWT负载中的一部分

package com.gezhi.springbootjwt.bean;

import com.gezhi.springbootjwt.annotation.FieldMarker;
import lombok.Data;

import java.io.Serializable;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述
 */
@Data
public class UserBean implements Serializable {

    /**
    * 开发时间 2019/11/26 0026 上午 9:50
    */
    private Long id;
    /**
    * 功能描述 用户名
     * 将userName作为token令牌中的一部分
     * 一定不要把过于敏感的数据,作为令牌中的一部分,切记
    * 开发时间 2019/11/26 0026 上午 9:03
    */
    @FieldMarker(value="userName")
    private String userName;
    /**
    * 功能描述 登录名
    * 开发时间 2019/11/26 0026 上午 9:04
    */
    private String loginName;
    /**
    * 功能描述 密码
    * 开发时间 2019/11/26 0026 上午 9:04
    */
    private String password;
    /**
    * 功能描述 年龄
    * 开发时间 2019/11/26 0026 上午 9:04
    */
    private Integer age;

}

####configure包

RedisConfig 类,通过@Configuration标记为一个spring配置类, 使用@Bean向容器中装配一个RedisTemplate的模板实例

package com.gezhi.springbootjwt.configure;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 11:31
 * 功能描述
 */
@Configuration
public class RedisConfig {

    /**
     * 功能描述: 向容器中添加redis模板类
     * @param factory 链接工厂
     * @return
     * 开发时间 2019/11/29 0029 上午 11:05
     */
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        /*制定key 与value 的 序列化规则*/

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

WebMvcConfig 类,通过@Configuration标记为一个spring配置类, 使用@Bean向容器中装配一个LoginInterceptor的模板实例,拦截器主要用来做JWT令牌认证

package com.gezhi.springbootjwt.configure;

import com.gezhi.springbootjwt.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述 springmvc补充配置
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{

    /**
     * 功能描述: 向容器中添加拦截器
     * @return
     * 开发时间 2019/11/29 0029 上午 11:04
     */
	@Bean
    public LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }
    /**
     * 功能描述: 添加拦截规则
     * @param registry
     * @return
     * 开发时间 2019/11/29 0029 上午 11:04
     */
	 @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/sys/login")
                .excludePathPatterns("/sys/logout")
                .excludePathPatterns("/static/**");
    }
}

####exception异常处理包

异常,代表后端系统可能存在的问题。可能是持久层的,也可能是业务层的,当然也可能是表现的,还有可能是其他组件的。但不管是哪个层的,都要注意,不要抛给前端(特别是:不要让用户看到,体验感很重要!!!)

ExceptionHandle 全局异常处理类,通过@RestControllerAdvice使用AOP编程原理将通知定义在表现层,只要表现层有异常都可以被该处理类监听到,从而可以从容的在此处对"异常"进行加工处理!!!

package com.gezhi.springbootjwt.exception;

import javax.servlet.http.HttpServletResponse;

import com.gezhi.springbootjwt.exception.customer.CustomerException;
import com.gezhi.springbootjwt.message.ReturnMessage;
import com.gezhi.springbootjwt.message.ReturnMessageUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述 全局异常处理类
 */
@RestControllerAdvice
public class ExceptionHandle {

	private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);

	@ExceptionHandler(value = Exception.class)
	public ReturnMessage<Object> handle(HttpServletResponse response, Exception exception) {
		 response.setCharacterEncoding("utf-8");

		if(exception instanceof CustomerException) {
			CustomerException customerException = (CustomerException)exception;
			return ReturnMessageUtil.error(customerException.getCode(), customerException.getMessage());
		}else {
			logger.error("系统异常 {}",exception);
			return ReturnMessageUtil.error(-1, "未知异常: "+exception.getMessage());
		}
	}
}

#####customer自定义异常包

CustomerException 自定义异常

package com.gezhi.springbootjwt.exception.customer;

import com.gezhi.springbootjwt.message.CodeEnum;
import lombok.Data;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述 自定义异常
 */
@Data
public class CustomerException extends RuntimeException{

	/**
	* 功能描述 状态码
	* 开发时间 2019/11/25 0025 下午 5:19
	*/
	private Integer code;

	public CustomerException(CodeEnum codeEnum) {
		super(codeEnum.getMsg());
		this.code = codeEnum.getCode();
	}
}

TokenIllegalException 令牌非法异常

package com.gezhi.springbootjwt.exception.customer;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 13:21
 * 功能描述 令牌不合法异常
 */
public class TokenIllegalException extends RuntimeException {

    /**
     * Constructs a new runtime exception with {@code null} as its
     * detail message.  The cause is not initialized, and may subsequently be
     * initialized by a call to {@link #initCause}.
     */
    public TokenIllegalException() {
    }

    /**
     * Constructs a new runtime exception with the specified detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     *
     * @param message the detail message. The detail message is saved for
     *                later retrieval by the {@link #getMessage()} method.
     */
    public TokenIllegalException(String message) {
        super(message);
    }
}

TokenMisMatchException 令牌数据不匹配异常

package com.gezhi.springbootjwt.exception.customer;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 12:02
 * 功能描述 token与redis中数据不匹配异常
 */
public class TokenMisMatchException extends RuntimeException {
    /**
     * Constructs a new runtime exception with {@code null} as its
     * detail message.  The cause is not initialized, and may subsequently be
     * initialized by a call to {@link #initCause}.
     */
    public TokenMisMatchException() {
    }

    /**
     * Constructs a new runtime exception with the specified detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     *
     * @param message the detail message. The detail message is saved for
     *                later retrieval by the {@link #getMessage()} method.
     */
    public TokenMisMatchException(String message) {
        super(message);
    }
}

####interceptor拦截器包

LoginInterceptor 拦截器类,这是一个主要的类,主要作用:判断用户的登录状态,判断用户的令牌是否合法,判断用户令牌是否已达刷新标准 (此处:刷新令牌的手段,采用的是后端根据时间自动完成刷新"令牌交换",当然也可以前端调用接口来完成刷新)

package com.gezhi.springbootjwt.interceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.gezhi.springbootjwt.exception.customer.CustomerException;
import com.gezhi.springbootjwt.exception.customer.TokenIllegalException;
import com.gezhi.springbootjwt.exception.customer.TokenMisMatchException;
import com.gezhi.springbootjwt.message.CodeEnum;
import com.gezhi.springbootjwt.jwt.JwtProvide;
import com.google.common.base.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述  状态拦截器
 * 专门用来判断用户是否登录的拦截器
 * 类似于WEB开发中的登录过滤器的功能
 */
public class LoginInterceptor implements HandlerInterceptor{
	
	Logger log = LoggerFactory.getLogger(LoginInterceptor.class);

	@Resource
	private JwtProvide jwtProvide;
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
		
		log.info("Token Checkout processing");
		response.setCharacterEncoding("UTF-8");
		response.setHeader("Content-Type", "text/html;charset=UTF-8");

		/*从HTTP请求头中,获得令牌信息*/
		String token = request.getHeader("Authorization");
		
		if (StringUtils.isEmpty(token)) {
			throw new CustomerException(CodeEnum.TOKENISEMPTY);
		}
		/*验证令牌*/
		try {
			 jwtProvide.verifyToken(token);

			/*判断令牌是否需要刷新*/
			String newToken = jwtProvide.refreshToken(token);
			if(!Objects.equal(token,newToken)){
				response.setHeader("Authorization",newToken);
			}

		} catch (AlgorithmMismatchException  e) {
			log.error("Token Checkout processing AlgorithmMismatchException 异常!"+e.getLocalizedMessage());
			throw new CustomerException(CodeEnum.ILLEGALTOKEN);
		}catch (TokenExpiredException  e) {
			log.info("token已经过期");
			throw new CustomerException(CodeEnum.EXPIRETOKEN);
		}catch (SignatureVerificationException  e) {
			log.error("Token Checkout processing SignatureVerificationException 异常!"+e.getLocalizedMessage());
			throw new CustomerException(CodeEnum.ILLEGALTOKEN);
		}catch (TokenIllegalException e) {
			log.error("Token Checkout processing TokenIllegalException 异常!"+e.getLocalizedMessage());
			throw new CustomerException(CodeEnum.ILLEGALTOKEN);
		}catch(TokenMisMatchException e){
			log.error("Token Checkout processing TokenMisMatchException 异常!"+e.getLocalizedMessage());
			throw new CustomerException(CodeEnum.ILLEGALTOKEN);
		}catch (Exception e) {
			log.error("Token Checkout processing 未知异常!"+e.getLocalizedMessage());
			throw e;
		}
		
		return true;
	}

	@Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

####jwt包

此包主要用于:产生JWT令牌,以及校验JWT令牌,可以理解为JWT的工具包

JwtProvide类,使用@ConfigurationProperties完成application.properties中定义的配置信息,自动绑定到类中的属性身上

package com.gezhi.springbootjwt.jwt;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;

import com.auth0.jwt.interfaces.Claim;
import com.gezhi.springbootjwt.bean.UserBean;
import com.gezhi.springbootjwt.exception.customer.CustomerException;
import com.gezhi.springbootjwt.exception.customer.TokenIllegalException;
import com.gezhi.springbootjwt.exception.customer.TokenMisMatchException;
import com.gezhi.springbootjwt.message.CodeEnum;
import com.gezhi.springbootjwt.util.Object2MapUtil;
import com.gezhi.springbootjwt.util.RedisProvide;
import com.google.common.base.Objects;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.JWTCreator.Builder;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;

import javax.annotation.Resource;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述 JWT 服务类
 */
@Data
@Component
@ConfigurationProperties(prefix = "com.gezhi.springbootjwt")
public class JwtProvide {

    @Resource
    private RedisProvide redisProvide;

    /**
     * 功能描述 私钥
     * 开发时间 2019/11/25 0025 下午 5:10
     */
    private String secret;
    /**
     * 功能描述 发布者
     * 开发时间 2019/11/25 0025 下午 5:10
     */
    private String issuer;
    /**
     * 功能描述 主题
     * 开发时间 2019/11/25 0025 下午 5:11
     */
    private String subject;
    /**
     * 功能描述 签名的观众 也可以理解谁接受签名的
     * 开发时间 2019/11/25 0025 下午 5:11
     */
    private String audience;
    /**
     * 功能描述 自定义签名
     * 开发时间 2019/11/25 0025 下午 5:11
     */
    private Map<String, String> claims;
    /**
     * 功能描述 过期时间
     * 开发时间 2019/11/26 0026 下午 2:20
     */
    private Integer hour;
    /**
     * 功能描述 刷新时间
     * 开发时间 2019/11/26 0026 下午 2:19
     */
    private Integer minute;

    /**
     * 创建 默认小时后过期的Token
     *
     * @param claims
     * @return
     */
    public String createToken(Map<String, Object> claims) {
        return createToken(claims, hour,minute);
    }

    /**
     * 创建 hour小时后过期的Token
     *
     * @param claims
     * @param hour   有效时间
     * @param minute 刷新时间
     * @return
     */
    public String createToken(Map<String, Object> claims, int hour, int minute) {
        Payload createPayload = this.createPayload(hour, minute);
        createPayload.setClaims(claims);
        Algorithm hmac256 = Algorithm.HMAC256(this.getSecret());
        return createToken(createPayload, hmac256);
    }

    /**
     * 根据负载和算法创建Token
     *
     * @param payload
     * @param algorithm
     * @return
     */
    public String createToken(Payload payload, Algorithm algorithm) {
        Builder headBuilder = createHeaderBuilder(algorithm);
        Builder publicClaimbuilder = addPublicClaimBuilder(headBuilder, payload);
        Builder privateClaimbuilder = addPrivateClaimbuilder(publicClaimbuilder, payload);
        String token = privateClaimbuilder.sign(algorithm);
        return token;
    }

    /**
     * 创建自定小时后过期的负载
     *
     * @param hour   有效时间
     * @param minute 刷新时间
     * @return
     */
    public Payload createPayload(int hour, int minute) {
        Payload payload = new Payload();
        payload.setIssuer(this.getIssuer());
        payload.setSubject(this.getSubject());
        payload.setAudiences(this.getAudience());
        this.setIssuedAtAndExpiresAt(new Date(), hour, minute, payload);
        return payload;
    }


    /**
     * 添加私有声明
     *
     * @param builder
     * @param payload
     * @return
     */
    private Builder addPrivateClaimbuilder(Builder builder, Payload payload) {
        Map<String, Object> claims = payload.getClaims();
        if (!CollectionUtils.isEmpty(claims)) {
            claims.forEach((k, v) -> {
                builder.withClaim(k, (String) v);
            });
        }
        builder.withClaim("refreshAt", payload.getRefreshAt());
        return builder;
    }

    /**
     * 添加公共声明
     *
     * @param builder
     * @param payload
     * @return
     */
    private Builder addPublicClaimBuilder(Builder builder, Payload payload) {

        //生成签名者
        if (!StringUtils.isEmpty(payload.getIssuer())) {
            builder.withIssuer(payload.getIssuer());
        }

        //生成签名主题
        if (!StringUtils.isEmpty(payload.getSubject())) {
            builder.withSubject(payload.getSubject());
        }

        //生成签名的时间
        if (payload.getIssuedAt() != null) {
            builder.withIssuedAt(payload.getIssuedAt());
        }
        //签名过期的时间
        if (payload.getExpiresAt() != null) {
            builder.withExpiresAt(payload.getExpiresAt());
        }
        // 签名领取签名的观众 也可以理解谁接受签名的
        if (CollectionUtils.isEmpty(payload.getAudience())) {
            payload.getAudience().forEach((s) -> {
                builder.withAudience(s);
            });
        }

        return builder;
    }

    /**
     * 创建JWT 头部信息
     *
     * @param algorithm
     * @return
     */
    private Builder createHeaderBuilder(Algorithm algorithm) {
        Builder builder = JWT.create().withHeader(buildJWTHeader(algorithm));
        return builder;
    }

    /**
     * 校验Token
     *
     * @param token
     * @return
     */
    public Payload verifyToken(String token) {
        DecodedJWT jwt = null;
        Payload payload = null;
        try {
            jwt = getDecodedJWT(token);
            payload = getPublicClaim(jwt);
            payload = getPrivateClaim(jwt, payload);
            Map<String, Object> claims = payload.getClaims();
            UserBean userBean = Object2MapUtil.map2Bean(claims, UserBean.class);
            /*判断解析出来的对象,是否与redis中的对象在属性上是否一致,如果不一致则需要抛出异常*/
            UserBean user = (UserBean) redisProvide.get(token);
            if (user == null) {
                throw new TokenIllegalException(token);
            }
            if (!Objects.equal(user.getUserName(), userBean.getUserName())) {
                throw new TokenMisMatchException(token);
            }

        } catch (AlgorithmMismatchException e) {
            //算法不一致,将抛出异常
            throw e;
        } catch (TokenExpiredException e) {
            //令牌失效,将抛出异常
            throw e;
        } catch (Exception e) {
            //其他异常
            throw e;
        }
        return payload;
    }

    /**
     * 功能描述: 刷新新的Token
     *
     * @param token 老的令牌信息
     * @return 开发时间 2019/11/26 0026 下午 1:02
     */
    public String refreshToken(String token) {
        DecodedJWT jwt = null;
        Payload payload = null;
        try {
            jwt = getDecodedJWT(token);
            payload = getPublicClaim(jwt);
            payload = getPrivateClaim(jwt, payload);
            Map<String, Object> claims = payload.getClaims();

            Claim claim = (Claim) claims.get("refreshAt");
            // 刷新时间
            Date refreshAt = claim.asDate();
            // 过期时间
            Date expiresAt = payload.getExpiresAt();
            Date currentAt = new Date();
            //  当前时间未超过过期时间,但是又超过了刷新时间,那么就刷新
            if (currentAt.before(expiresAt) && refreshAt.before(currentAt)) {

                UserBean userBean = (UserBean) redisProvide.get(token);
                Map<String, Object> userInfo = null;
                try {
                    userInfo = Object2MapUtil.bean2Map(userBean);
                } catch (IllegalAccessException e) {
                    throw new CustomerException(CodeEnum.DATAPARSEERROR);
                }

                redisProvide.del(token);
                token = createToken(userInfo, hour, minute);
                redisProvide.set(token, userBean);
            }

        } catch (AlgorithmMismatchException e) {
            //算法不一致,将抛出异常
            throw e;
        } catch (TokenExpiredException e) {
            //令牌失效,将抛出异常
            throw e;
        } catch (Exception e) {
            //其他异常
            throw e;
        }
        return token;
    }


    /**
     * 获取JWT 私有声明
     *
     * @param jwt
     * @param payload
     * @return
     */
    private Payload getPrivateClaim(DecodedJWT jwt, Payload payload) {
        Map<String, Object> claims = new HashMap<String, Object>();
        jwt.getClaims().forEach((k, v) -> {
            claims.put(k, v);
        });
        payload.setClaims(claims);
        return payload;
    }

    /**
     * 获取JWT 公共声明
     *
     * @param jwt
     * @return
     */
    private Payload getPublicClaim(DecodedJWT jwt) {
        Payload payload = new Payload();
        payload.setIssuer(jwt.getIssuer());
        payload.setSubject(jwt.getSubject());
        payload.setAudience(jwt.getAudience());
        payload.setIssuedAt(jwt.getIssuedAt());
        payload.setExpiresAt(jwt.getExpiresAt());
        return payload;
    }

    /**
     * 获取 DecodedJWT
     *
     * @param token
     * @return
     */
    private DecodedJWT getDecodedJWT(String token) {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(this.getSecret())).build();
        DecodedJWT jwt = verifier.verify(token);
        return jwt;
    }

    /**
     * 构建JWT头部Map信息
     *
     * @param algorithm
     * @return
     */
    private Map<String, Object> buildJWTHeader(Algorithm algorithm) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("alg", algorithm.getName());
        map.put("typ", "JWT");
        return map;
    }

    /**
     * 根据发布时间设置过期时间
     * 根据发布时间设置刷新时间
     *
     * @param issuedAt
     * @param hour
     * @param payload
     */
    public void setIssuedAtAndExpiresAt(Date issuedAt, Integer hour, Integer minute, Payload payload) {
        payload.setIssuedAt(issuedAt);
        payload.setExpiresAt(getAfterDateByHour(issuedAt, hour));
        payload.setRefreshAt(getAfterDateByMinute(issuedAt, minute));
    }

    /**
     * 返回一定时间后的日期
     *
     * @param date 开始计时的时间
     * @param hour 增加的小时
     * @return
     */
    public Date getAfterDateByHour(Date date, int hour) {
        if (date == null) {
            date = new Date();
        }
        Date afterDate = getAfterDate(date, 0, 0, 0, hour, 0, 0);
        return afterDate;
    }

    /**
     * 返回一定时间后的日期
     *
     * @param date   开始计时的时间
     * @param minute 增加的分钟
     * @return
     */
    public Date getAfterDateByMinute(Date date, int minute) {
        if (date == null) {
            date = new Date();
        }
        Date afterDate = getAfterDate(date, 0, 0, 0, 0, minute, 0);
        return afterDate;
    }

    /**
     * 返回一定时间后的日期
     *
     * @param date   开始计时的时间
     * @param year   增加的年
     * @param month  增加的月
     * @param day    增加的日
     * @param hour   增加的小时
     * @param minute 增加的分钟
     * @param second 增加的秒
     * @return
     */
    public Date getAfterDate(Date date, int year, int month, int day, int hour, int minute, int second) {
        if (date == null) {
            date = new Date();
        }

        Calendar cal = new GregorianCalendar();

        cal.setTime(date);
        if (year != 0) {
            cal.add(Calendar.YEAR, year);
        }
        if (month != 0) {
            cal.add(Calendar.MONTH, month);
        }
        if (day != 0) {
            cal.add(Calendar.DATE, day);
        }
        if (hour != 0) {
            cal.add(Calendar.HOUR_OF_DAY, hour);
        }
        if (minute != 0) {
            cal.add(Calendar.MINUTE, minute);
        }
        if (second != 0) {
            cal.add(Calendar.SECOND, second);
        }
        return cal.getTime();
    }
}

Payload 负载类,此类中主要描述JWT中 负载 部分的相关信息

package com.gezhi.springbootjwt.jwt;

import lombok.Data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述 JWT 负载
 */
@Data
public class Payload implements Serializable {
	/**
	* 功能描述 发布者
	* 开发时间 2019/11/25 0025 下午 5:06
	*/
	private String issuer;
	/**
	 * 功能描述 主题
	 * 开发时间 2019/11/25 0025 下午 5:06
	 */
	private String subject;
	/**
	 * 功能描述 签名的观众 也可以理解谁接受签名的
	 * 开发时间 2019/11/25 0025 下午 5:06
	 */
	private List<String> audience;
	/**
	 * 功能描述 发布时间
	 * 开发时间 2019/11/25 0025 下午 5:06
	 */
	private Date issuedAt;
	/**
	 * 功能描述 过期时间
	 * 开发时间 2019/11/25 0025 下午 5:06
	 */
	private Date expiresAt;
	/**
	 * 功能描述 开始使用时间
	 * 开发时间 2019/11/25 0025 下午 5:06
	 */
	private Date notBefore;
	/**
	* 功能描述 刷新时间
	* 开发时间 2019/11/26 0026 下午 1:43
	*/
	private Date refreshAt;

	/**
	 * 功能描述 自定义签名
	 * 开发时间 2019/11/25 0025 下午 5:06
	 */
	private Map<String,Object> claims;

	public void setAudiences(String... audienceStr) {
		List<String> audiences = new ArrayList<String>();
		for (String string : audienceStr) {
			audiences.add(string);
		}
		this.audience = audiences;
	}

}

####message包

CodeEnum 枚举类,主要作用:定义向前端输出的提示信息(替代:向前端抛出异常)

package com.gezhi.springbootjwt.message;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述 认证码说明
 */
public enum CodeEnum {
	/**
	 * 用户名或者密码错误
	 */
	LOGINNAMEANDPWDERROR(100000,"用户名或者密码错误!"),
	/**
	 * 非法token
	 */
	ILLEGALTOKEN(110000,"非法token!"),
	/**
	 * token已经过期
	 */
	EXPIRETOKEN(110001,"token已经过期!"),
	/**
	 * Token 不能为空
	 */
	TOKENISEMPTY(110002,"Token 不能为空!"),
	/**
	 * 数据解析错误!
	 */
	DATAPARSEERROR(110003,"数据解析错误! ");
	
	
	private CodeEnum(Integer code,String msg){
		this.code = code;
		this.msg = msg;
	}
	private Integer code;
	private String msg;
	
	public Integer getCode() {
		return code;
	}
	public void setCode(Integer code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
}

ReturnMessage 返回消息类

package com.gezhi.springbootjwt.message;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述 返回消息类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ReturnMessage<T> {

	/**
	* 功能描述 错误码
	* 开发时间 2019/11/25 0025 下午 5:16
	*/
	private Integer code;
	/**
	* 功能描述 提示信息
	* 开发时间 2019/11/25 0025 下午 5:16
	*/
	private String message;
	/**
	* 功能描述 返回具体内容
	* 开发时间 2019/11/25 0025 下午 5:16
	*/
	private T date;

}

ReturnMessageUtil 返回消息工具类,主要针对ReturnMessage进行封装

package com.gezhi.springbootjwt.message;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述
 */
public class ReturnMessageUtil {
	/**
	 * 无异常 请求成功并有具体内容返回
	 * @param object
	 * @return
	 */
	public static ReturnMessage<Object> sucess(Object object) {
		ReturnMessage<Object> message = new ReturnMessage<Object>(0,"sucess",object);
		return message;
	}
	/**
	 * 无异常 请求成功并无具体内容返回
	 * @return
	 */
	public static ReturnMessage<Object> sucess() {
		ReturnMessage<Object> message = new ReturnMessage<Object>(0,"sucess",null);
		return message;
	}
	/**
	 * 有自定义错误异常信息
	 * @param code
	 * @param msg
	 * @return
	 */
	public static ReturnMessage<Object> error(Integer code,String msg) {
		ReturnMessage<Object> message = new ReturnMessage<Object>(code,msg,null);
		return message;
	}

	public static ReturnMessage<Object> error(CodeEnum codeEnum) {
		ReturnMessage<Object> message = new ReturnMessage<Object>(codeEnum.getCode(),codeEnum.getMsg(),null);
		return message;
	}
}

####sysmag包

#####controller包
该包的主要作用:编写Controller完成用户登录或者退出

package com.gezhi.springbootjwt.sysmag.controller;

import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;

import com.gezhi.springbootjwt.bean.UserBean;
import com.gezhi.springbootjwt.exception.customer.CustomerException;
import com.gezhi.springbootjwt.message.CodeEnum;
import com.gezhi.springbootjwt.message.ReturnMessage;
import com.gezhi.springbootjwt.message.ReturnMessageUtil;
import com.gezhi.springbootjwt.jwt.JwtProvide;
import com.gezhi.springbootjwt.util.Object2MapUtil;
import com.gezhi.springbootjwt.util.RedisProvide;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.base.Objects;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述
 */
@RequestMapping("/sys")
@RestController
public class LoginController {

	Logger log = LoggerFactory.getLogger(this.getClass());

	@Resource
	private RedisProvide redisProvide;

	@Resource
	private JwtProvide jwtProvide;
	
	@RequestMapping("/login")
	public ReturnMessage<Object> login(String loginName, String password, HttpSession session) {

		/*从数据库中根据登录名和密码查询是否存在用户数据*/
		UserBean userBean = valid(loginName,password);


		if(userBean == null) {
			return ReturnMessageUtil.error(CodeEnum.LOGINNAMEANDPWDERROR);
		}

		/*将javabean实体转换为map数据,方便向jwt中追加数据*/
		Map<String,Object> userInfo = null;
		try {
			userInfo = Object2MapUtil.bean2Map(userBean);
		} catch (IllegalAccessException e) {
			log.error("login()",e);
			throw new CustomerException(CodeEnum.DATAPARSEERROR);
		}
		/*转换成功之后,使用JWT令牌提供类*/
		String token = jwtProvide.createToken(userInfo);
		/*最好在这里,再将token存入到redis中做个备份,方便做校验*/
		redisProvide.set(token,userBean,1);
		log.info("Authorization:"+token);
		return ReturnMessageUtil.sucess(token);
	}
	
	@GetMapping("/logout")
	public ReturnMessage<?> logout(String token) {
		/*从redis中清理令牌信息*/
		redisProvide.del(token);
		return ReturnMessageUtil.sucess();
	}

	/**
	 * 功能描述: 模拟从数据库中查询用户信息
	 * @param loginName 登录名
	 * @param password 密码
	 * @return 用户信息
	 * 开发时间 2019/11/26 0026 上午 9:09
	 */
	private UserBean valid(String loginName, String password) {

		if(Objects.equal("admin", loginName) && Objects.equal("123456", password) ) {
			UserBean userBean = new UserBean();
			userBean.setId(2L);
			userBean.setUserName("张三");
			userBean.setLoginName("zs");
			userBean.setPassword("123456");
			userBean.setAge(20);
			return userBean;
		}

		return null;
	}
	
} 

####usermag包

#####controller包
该包的主要作用:编写Controller完成用户资源的CRUD

package com.gezhi.springbootjwt.usermag.controller;

import com.gezhi.springbootjwt.message.ReturnMessage;
import com.gezhi.springbootjwt.message.ReturnMessageUtil;
import org.springframework.web.bind.annotation.*;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/25 0025 17:28
 * 功能描述
 */
@RequestMapping("/users")
@RestController
public class UserController {

    @PostMapping(value = "/{id}")
    public ReturnMessage<Object> addUserBean(){


        return ReturnMessageUtil.sucess();
    }

    @PutMapping(value = "/{id}")
    public ReturnMessage<Object> updateUserBean(){

        return ReturnMessageUtil.sucess();
    }

}

####util工具包

Object2MapUtil 工具类,完成对象与Map数据直接的相互转换

package com.gezhi.springbootjwt.util;

import com.auth0.jwt.interfaces.Claim;
import com.gezhi.springbootjwt.annotation.FieldMarker;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:17
 * 功能描述
 */
public class Object2MapUtil {

    /**
     * 转换bean为map
     *
     * @param source 要转换的bean
     * @param <T>    bean类型
     * @return 转换结果
     */
    public static <T> Map<String, Object> bean2Map(T source) throws IllegalAccessException {
        Map<String, Object> result = new HashMap<>();

        Class<?> sourceClass = source.getClass();
        //拿到所有的字段,不包括继承的字段
        Field[] sourceFiled = sourceClass.getDeclaredFields();
        for (Field field : sourceFiled) {
            //设置可访问,不然拿不到private
            field.setAccessible(true);
            //配置@FieldMarker注解的属性,作为JWT 负载数据
            FieldMarker fm = field.getAnnotation(FieldMarker.class);
            if (fm != null) {
                result.put(field.getName(), field.get(source));
            }
        }
        return result;
    }


    /**
     * map转bean
     * @param source   map属性
     * @param instance 要转换成的实例的类型
     * @return 该bean
     */
    public static <T> T map2Bean(Map<String, Object> source, Class<T> instance) {
        try {
            T object = instance.newInstance();
            Field[] fields = object.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                FieldMarker fm = field.getAnnotation(FieldMarker.class);
                if (fm != null){
                    Claim value = (Claim)source.get(field.getName());
                    field.set(object,value.asString());
                }
            }
            return object;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

RedisProvide 类,针对RedisTemplate的封装类,为了降低RedisTemplate模板操作的复杂度

package com.gezhi.springbootjwt.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 11:42
 * 功能描述
 */
@Component
public class RedisProvide {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }
    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.HOURS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

####启动类

SpringBootJwtApplication 启动类,@SpringBootApplication表示这是一个启动类,也是spring应用的加载类

package com.gezhi.springbootjwt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @author 格智学院
 * @version JDK1.8
 * 开发时间  2019/11/26 0026 09:02
 * 功能描述
 */
@SpringBootApplication
public class SpringBootJwtApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootJwtApplication.class, args);
    }

}

##项目演示

启动项目,使用Postman访问 未登录时的效果:
在这里插入图片描述

登录系统,后端使用JWT分配Token信息给前端:
在这里插入图片描述

携带Token,再次访问后端时的效果:
在这里插入图片描述在这里插入图片描述
修改Token后,再次访问后端时的效果:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值