SpringBoot中使用JWT

JWT是什么我就不说了,这里只说名SpringBoot中怎么用。

首先在pom中天际依赖

1 <dependency>
2         <groupId>org.bitbucket.b_c</groupId>
3         <artifactId>jose4j</artifactId>
4         <version>0.6.5</version>
5 </dependency>

这里我用的jose4j,他与其他几个库的对比可以参考各类JWT库的对比

之后新建一个工具类,方便token生成和校验

 1 import com.example.demo.domain.User;
 2 import org.jose4j.jwk.RsaJsonWebKey;
 3 import org.jose4j.jwk.RsaJwkGenerator;
 4 import org.jose4j.jws.AlgorithmIdentifiers;
 5 import org.jose4j.jws.JsonWebSignature;
 6 import org.jose4j.jwt.JwtClaims;
 7 import org.jose4j.jwt.consumer.InvalidJwtException;
 8 import org.jose4j.jwt.consumer.JwtConsumer;
 9 import org.jose4j.jwt.consumer.JwtConsumerBuilder;
10 import org.jose4j.lang.JoseException;
11 
12 import java.util.Random;
13 
14 public class JWTManager {
15     /**
16      * RsaJsonWebKeyBuilder 采用单例模式获取rsaJsonWebKey, 这样任何时候都可以得到同样的公钥/私钥对
17      */
18     private static class RsaJsonWebKeyBuilder {
19         private static volatile RsaJsonWebKey rsaJsonWebKey;
20         private RsaJsonWebKeyBuilder(){}
21         public static RsaJsonWebKey getRasJsonWebKeyInstance() {
22             if(rsaJsonWebKey == null) {
23                 synchronized (RsaJsonWebKey.class) {
24                     if(rsaJsonWebKey == null){
25                         try {
26                             rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
27                             rsaJsonWebKey.setKeyId(String.valueOf(new Random().nextLong()));
28                         } catch(Exception e){
29                             return null;
30                         }
31                     }
32                 }
33             }
34             return rsaJsonWebKey;
35         }
36     }
37 
38     public static String generateToken(User user, int expiration) throws Exception{
39         JwtClaims jwtClaims = new JwtClaims();
40         jwtClaims.setIssuer(user.getEmail());
41         jwtClaims.setAudience(System.getProperty("os.name"));
42         jwtClaims.setExpirationTimeMinutesInTheFuture(expiration);
43         jwtClaims.setGeneratedJwtId();
44         jwtClaims.setIssuedAtToNow();
45         jwtClaims.setNotBeforeMinutesInThePast(2);
46         jwtClaims.setSubject("Bearer");
47 
48         JsonWebSignature jsonWebSignature = new JsonWebSignature();
49         jsonWebSignature.setPayload(jwtClaims.toJson());
50         jsonWebSignature.setKey(RsaJsonWebKeyBuilder.getRasJsonWebKeyInstance().getPrivateKey());
51         jsonWebSignature.setKeyIdHeaderValue(RsaJsonWebKeyBuilder.getRasJsonWebKeyInstance().getKeyId());
52         jsonWebSignature.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_PSS_USING_SHA256);
53 
54         String jwt = jsonWebSignature.getCompactSerialization();
55 
56         return "Bearer " + jwt;
57     }
58     public static boolean verifyToken(String token, String email) {  // 由于生成token时使用了用户的email作为issuer,故这里需要传入email来做校验,这样做可以防止对不同用户的修改操作
59         String tokenContent = token.substring(7);
60         JwtConsumer consumer = new JwtConsumerBuilder()
61                 .setRequireExpirationTime()
62                 .setMaxFutureValidityInMinutes(5256000)
63                 .setAllowedClockSkewInSeconds(30)
64                 .setRequireSubject()
65                 .setExpectedIssuer(email)
66                 .setExpectedAudience(System.getProperty("os.name"))
67                 .setVerificationKey(RsaJsonWebKeyBuilder.getRasJsonWebKeyInstance().getPublicKey())
68                 .build();
69         try {
70             JwtClaims claims = consumer.processToClaims(tokenContent);
71             return true;
72         } catch (InvalidJwtException e) {
73             return false;
74         }
75     }
76 }

然后为了做统一校验,创建拦截器

 AuthenticationInterceptor

 注意24行, 他的目的使检验方法是否被LoginRequired装饰。对于没有被装饰和LoginRequired的value是false的情况全部放行, 否则则校验token, 对于没有token, 或者校验不同过的情况,抛出ResponseException异常。

再来看LoginRequired装饰器,他的定义很简单

 1 import java.lang.annotation.ElementType;
 2 import java.lang.annotation.Retention;
 3 import java.lang.annotation.RetentionPolicy;
 4 import java.lang.annotation.Target;
 5 
 6 @Target({ElementType.METHOD, ElementType.TYPE})
 7 @Retention(RetentionPolicy.RUNTIME)
 8 public @interface LoginRequired {
 9     boolean required() default true;
10 }

使用时,支取要在需要登录验证的方法上添加@LoginRequired修饰即可

ResponseException继承自RuntimeException, 只有RuntimeException的子类才能被spingboot处理

 1 public class ResponseException extends RuntimeException {
 2     public static ResponseException UNAUTHORIZED = new ResponseException(401, "请先登录");
 3 
 4     private int code;
 5     private String message;
 6 
 7     public ResponseException(int code, String message) {
 8         super(message);
 9         this.code = code;
10         this.message = message;
11     }
12 
13     @Override
14     public String getMessage() {
15         return message;
16     }
17 
18     public void setMessage(String message) {
19         this.message = message;
20     }
21 
22     public int getCode() {
23         return code;
24     }
25 
26     public void setCode(int code) {
27         this.code = code;
28     }
29 }

另外我们需要添加一个异常捕获,来捕获校验失败抛出的异常。这里才用@ConrollerAdvice + @ExceptionHandler来捕获异常, 这种方式同时可以捕获程序运行时的各种错误,来做统一格式返回。

 View Code

其中beforeBodyWrite就是用来修改响应内容,可以做到统一格式响应,需要注意的是,如果他的参数Object o是字符串,需要ObjectMapper做转换,否则在后续的序列化会失败返回500或者404错误。

至此,springboot使用jwt校验的方法说完了。另外需要说明的是,拦截器里抛出异常的话,虽然我们能捕获并修改他的响应,但是他会导致跨域处理失效,响应头中没有Control-Allowed-Oringin等响应头,目前我还没找到解决办法,只能在前端做代理来避免跨域

SpringBoot中使用JWT - 不想取名字所以就随便写了 - 博客园



Java RsaJsonWebKey类代码示例 - 纯净天空

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值