1.模块结构
2.引入依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
3.创建JWT配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "com.security.config.jwt")
public class JWTConfig {
private String sign; //保存签名信息
private String issuer; //保存签发者
private String secret; //加密的密钥
private long expire; //失效时间
}
4.创建application.yml配置文件
com:
security:
config:
jwt:
sign: xl
issuer: xiaoli
secret: xiaolixiaoliaidabuli
expire: 10000 #单位:秒
spring:
application:
name: JWT-TEST
5.创建TokenService
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import javax.crypto.SecretKey;
import java.util.Map;
/**
* Token 操作接口
*/
public interface ITokenService {
public SecretKey generalKey(); //获取当前JWT数据的加密KEY
public String createToken(String id, Map<String, Object> subject); //创建Token的数据内容,同时要求保存用户的id以及所需要的附加数据
public Jws<Claims> parseToken(String token); //解析Token数据
public boolean verifyToken(String token); //验证Token有效性
public String refreshToken(String token); //刷新Token
}
6.实现TokenService
import com.cloud.jwt.config.JWTConfig;
import com.cloud.jwt.service.ITokenService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//此时的组件中的代码需要其他的模块去引用,所有未必会与扫描包相同
public class TokenServiceImpl implements ITokenService {
@Autowired //SpringBoot容器启动时会自动提供Jacks示实例
private ObjectMapper objectMapper;
@Autowired
private JWTConfig jwtConfig; //获取JWT的相关配置属性
@Value("${spring.application.name}") //通过SpEL进行配置注入
private String applicationName; //应用名称
private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //签名算法
@Override
public SecretKey generalKey() {
byte[] encodeKey = Base64.encodeBase64(Base64.encodeBase64(this.jwtConfig.getSecret().getBytes()));
SecretKey key = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
return key;
}
@Override
public String createToken(String id, Map<String, Object> subject) {
//使用JWT数据结构进行开发,目的之一就是不需要进行JWT数据的分布式存储,所以所谓的缓存主键、数据库都用不到
//所有Token都存在有保存失效的问题,所以需要通过当前时间来进行计算
Date nowDate = new Date(); //获取当前时间
Date expireDate = new Date(nowDate.getTime() + this.jwtConfig.getExpire() * 1000); //证书过期时间
Map<String, Object> cliams = new HashMap<>(); //保存所有附加数据
cliams.put("client", "admin");
Map<String, Object> headers = new HashMap<>(); // 保存头信息
headers.put("author", "小李小李爱搭不理"); //作者,也可以通过配置处理
//后续会有很多模块引用此组件,为了后续的安全,最佳的做法就是设置一个模块名称的信息
headers.put("module", this.applicationName);
JwtBuilder builder = null;
try {
builder = Jwts.builder() //进行JWTBuilder对象实例化
.setClaims(cliams) //保存附加的数据内容
.setHeader(headers) //保存头信息
.setId(id) //保存ID信息
.setIssuedAt(nowDate) //签发时间
.setIssuer(this.jwtConfig.getIssuer()) //设置签发者
.setSubject(this.objectMapper.writeValueAsString(subject)) //索要传递的数据转为JSON
.signWith(this.signatureAlgorithm, this.generalKey()) //获取签名算法
.setExpiration(expireDate); //配置失效时间
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return builder.compact(); //构建Token
}
@Override
public Jws<Claims> parseToken(String token) {
if (this.verifyToken(token)) { //只有Token有效才进行解析
Jws<Claims> claims = Jwts.parser().setSigningKey(this.generalKey()).parseClaimsJws(token);
return claims;
}
return null; //解析失败返回null
}
@Override
public boolean verifyToken(String token) {
try {
Jwts.parser().setSigningKey(this.generalKey()).parseClaimsJws(token).getBody();
return true;
} catch (Exception e) {
return false;
}
}
@Override
public String refreshToken(String token) {
if (this.verifyToken(token)) {
Jws<Claims> jws = this.parseToken(token); //解析Token数据
try {
return this.createToken(jws.getBody().getId(), this.objectMapper.readValue(jws.getBody().getSubject(), Map.class));
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
return null;
}
}
7.配置注入属性
import com.cloud.jwt.config.JWTConfig;
import com.cloud.jwt.service.ITokenService;
import com.cloud.jwt.service.impl.TokenServiceImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties({JWTConfig.class}) //配置注入属性
public class JWTAutoConfiguration {
@Bean("tokenService")
public ITokenService getTokenServiceBean() {
return new TokenServiceImpl();
}
}
8.添加spring.factories
org.springframework.boot.context.properties.EnableConfigurationProperties=com.cloud.jwt.autoconfig.JWTAutoConfiguration
9.编写测试类测试
注意:测试的话需要修改TokenServiceImpl实现类里的ObjectMapper注入,会提示找不到;
修改:去掉@Autowired,直接new ObjectMapper组件即可,测试完记得改回来!
import com.cloud.jwt.StartJWTConfiguration;
import com.cloud.jwt.service.ITokenService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@SpringBootTest(classes = StartJWTConfiguration.class)
public class testTokenService {
@Autowired
private ITokenService tokenService;
//testCreateToken()方法生成的Token
private String jwt = "eyJhdXRob3IiOiJjZGlpcC5jbiIsIm1vZHVsZSI6IkpXVC1DRElJUCIsImFsZyI6IkhTMjU2In0.eyJzdWIiOiJ7XCJ1c2VyX2lkXCI6XCIxXCIsXCJ1c2VyX25hbWVcIjpcIuWwj-eqnVwifSIsImlzcyI6ImNkaWlwLmNuIiwiZXhwIjoxNjQ3NTE0MTQyLCJpYXQiOjE2NDc1MDQxNDIsImNsaWVudF9pZCI6ImFkbWluIiwianRpIjoiY2RpaXAtZDI4ZjhkZmYtYzI5ZC00NDBkLWFkMjAtODNhYzU1YjE4OTBjIn0.fupPvV7Uzhl_VClRgqv3uJTdLJuJ6CWLb7wqhciYk3M";
@Test
public void testCreateToken() {
Map<String, Object> map = new HashMap<>();
map.put("user_id", "1");
map.put("user_name", "小窝");
String id = "xiaoli-" + UUID.randomUUID(); //JWT-ID数据
System.out.println(this.tokenService.createToken(id, map));
}
@Test
public void testParseToken() {
Jws<Claims> jws = this.tokenService.parseToken(jwt);
System.out.println("JWT签名数据:" + jws.getSignature());
JwsHeader header = jws.getHeader(); //获取头信息
header.forEach((headerName, headerValue) -> {
System.out.println("【JWT头信息】" + headerName + "=" + headerValue);
});
Claims claims = jws.getBody();
claims.forEach((bodyName, bodyValue) -> {
System.out.println("【JWT数据】" + bodyName + "=" + bodyValue);
});
}
@Test
public void testVerifyJWT(){
System.out.println("【JWT数据验证】"+this.tokenService.verifyToken(jwt));
}
@Test
public void testRefreshJWT(){
System.out.println("【JWT刷新数据】"+this.tokenService.refreshToken(jwt));
}
}