#JWT令牌认证技术 #
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间作为JSON对象安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。JWT可以使用秘密(使用HMAC算法)或公钥/私钥对使用RSA或ECDSA来签名。
虽然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
随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。
JWT架构图:
##JWT工作机制?
在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web Token(即:JWT)。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,不应将令牌保留的时间超过要求。理论上超时时间越短越好。
每当用户想要访问受保护的路由或资源时,用户代理应该使用Bearer模式发送JWT,通常在Authorization header中。标题内容应如下所示:
Authorization: Bearer
在某些情况下,这可以作为无状态授权机制。服务器的受保护路由将检查Authorization header中的有效JWT ,如果有效,则允许用户访问受保护资源。如果JWT包含必要的数据,则可以减少查询数据库或缓存信息。
如果在Authorization header中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie。
注意:使用签名令牌,虽然他们无法更改,但是令牌中包含的所有信息都会向用户或其他方公开。这意味着不应该在令牌中放置敏感信息。
##如何使用JWT?
###创建springboot工程
在IDEA中构建一个springboot的web工程,在对应的pom.xml文件中引入以下内容:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
com.gezhi
spring-boot-jwt
0.0.1-SNAPSHOT
spring-boot-jwt
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-data-redis
com.auth0
java-jwt
3.4.1
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
com.google.guava
guava
18.0
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-maven-plugin
###编写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 redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate();
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
#####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 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 claims) {
return createToken(claims, hour,minute);
}
/**
* 创建 hour小时后过期的Token
*
* @param claims
* @param hour 有效时间
* @param minute 刷新时间
* @return
*/
public String createToken(Map 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 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 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 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 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 claims = new HashMap();
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 buildJWTHeader(Algorithm algorithm) {
Map map = new HashMap();
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 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 claims;
public void setAudiences(String... audienceStr) {
List audiences = new ArrayList();
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 {
/**
* 功能描述 错误码
* 开发时间 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
####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 login(String loginName, String password, HttpSession session) {
/*从数据库中根据登录名和密码查询是否存在用户数据*/
UserBean userBean = valid(loginName,password);
if(userBean == null) {
return ReturnMessageUtil.error(CodeEnum.LOGINNAMEANDPWDERROR);
}
/*将javabean实体转换为map数据,方便向jwt中追加数据*/
Map 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 addUserBean(){
return ReturnMessageUtil.sucess();
}
@PutMapping(value = "/{id}")
public ReturnMessage 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 bean类型
* @return 转换结果
*/
public static Map bean2Map(T source) throws IllegalAccessException {
Map 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 map2Bean(Map source, Class 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 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 hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map 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 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 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 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 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 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);
}
}
##项目演示