冷饭新炒:理解JWT的实现原理和基本使用 (5)

JWT一般用于认证场景,搭配API网关使用效果甚佳。多数情况下,API网关会存在一些通用不需要认证的接口,其他则是需要认证JWT合法性并且提取JWT中的消息载荷内容进行调用,针对这个场景:

对于控制器入口可以提供一个自定义注解标识特定接口需要进行JWT认证,这个场景在Spring Cloud Gateway中需要自定义实现一个JWT认证的WebFilter

对于单纯的路由和转发可以提供一个URI白名单集合,命中白名单则不需要进行JWT认证,这个场景在Spring Cloud Gateway中需要自定义实现一个JWT认证的GlobalFilter

下面就Spring Cloud Gateway和jjwt,贴一些骨干代码,限于篇幅不进行细节展开。引入依赖:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR10</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.2</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.18</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>

然后编写JwtSpi和对应的实现HMAC256JwtSpiImpl:

@Data public class CreateJwtDto { private Long customerId; private String customerName; private String customerPhone; } @Data public class JwtCacheContent { private Long customerId; private String customerName; private String customerPhone; } @Data public class VerifyJwtResultDto { private Boolean valid; private Throwable throwable; private long jwtId; private JwtCacheContent content; } public interface JwtSpi { /** * 生成JWT * * @param dto dto * @return String */ String generate(CreateJwtDto dto); /** * 校验JWT * * @param jwt jwt * @return VerifyJwtResultDto */ VerifyJwtResultDto verify(String jwt); /** * 把JWT添加到封禁名单中 * * @param jwtId jwtId */ void blockJwt(long jwtId); /** * 判断JWT是否在封禁名单中 * * @param jwtId jwtId * @return boolean */ boolean isInBlockList(long jwtId); } @Component public class HMAC256JwtSpiImpl implements JwtSpi, InitializingBean, EnvironmentAware { private SecretKey secretKey; private Environment environment; private int minSeed; private String issuer; private int seed; private Random random; @Override public void afterPropertiesSet() throws Exception { String secretKey = Objects.requireNonNull(environment.getProperty("jwt.hmac.secretKey")); this.minSeed = Objects.requireNonNull(environment.getProperty("jwt.exp.seed.min", Integer.class)); int maxSeed = Objects.requireNonNull(environment.getProperty("jwt.exp.seed.max", Integer.class)); this.issuer = Objects.requireNonNull(environment.getProperty("jwt.issuer")); this.random = new Random(); this.seed = (maxSeed - minSeed); this.secretKey = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"); } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public String generate(CreateJwtDto dto) { long duration = this.random.nextInt(this.seed) + minSeed; Map<String, Object> claims = new HashMap<>(8); claims.put("iss", issuer); // 这里的jti最好用类似雪花算法之类的序列算法生成,确保唯一性 claims.put("jti", dto.getCustomerId()); claims.put("uid", dto.getCustomerId()); claims.put("exp", TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) + duration); String jwt = Jwts.builder() .setHeaderParam("typ", "JWT") .signWith(this.secretKey, SignatureAlgorithm.HS256) .addClaims(claims) .compact(); // 这里需要缓存uid->JwtCacheContent的信息 JwtCacheContent content = new JwtCacheContent(); // redis.set(KEY[uid],toJson(content),expSeconds); return jwt; } @Override public VerifyJwtResultDto verify(String jwt) { JwtParser parser = Jwts.parserBuilder() .requireIssuer(this.issuer) .setSigningKey(this.secretKey) .build(); VerifyJwtResultDto resultDto = new VerifyJwtResultDto(); try { Jws<Claims> parseResult = parser.parseClaimsJws(jwt); Claims claims = parseResult.getBody(); long jti = Long.parseLong(claims.getId()); if (isInBlockList(jti)) { throw new IllegalArgumentException(String.format("jti is in block list,[i:%d]", jti)); } long uid = claims.get("uid", Long.class); // JwtCacheContent content = JSON.parse(redis.get(KEY[uid]),JwtCacheContent.class); // resultDto.setContent(content); resultDto.setValid(Boolean.TRUE); } catch (Exception e) { resultDto.setValid(Boolean.FALSE); resultDto.setThrowable(e); } return resultDto; } @Override public void blockJwt(long jwtId) { } @Override public boolean isInBlockList(long jwtId) { return false; } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjzjp.html