博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot中使用JWT
阅读量:7118 次
发布时间:2019-06-28

本文共 9469 字,大约阅读时间需要 31 分钟。

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

首先在pom中天际依赖

1 
2
org.bitbucket.b_c
3
jose4j
4
0.6.5
5

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

之后新建一个工具类,方便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 }

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

1 import com.example.demo.exceptions.ResponseException; 2 import com.example.demo.service.UserService; 3 import com.example.demo.utils.JWTManager; 4 import com.example.demo.utils.annotaion.LoginRequired; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.web.method.HandlerMethod; 7 import org.springframework.web.servlet.HandlerInterceptor; 8 import org.springframework.web.servlet.ModelAndView; 9 10 import javax.servlet.http.HttpServletRequest;11 import javax.servlet.http.HttpServletResponse;12 import java.lang.reflect.Method;13 14 public class AuthenticationInterceptor implements HandlerInterceptor {15     @Autowired16     UserService userService;17 18     @Override19     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {20         String token = request.getHeader("Authorization");21         if (!(handler instanceof HandlerMethod))22             return true;23         Method method = ((HandlerMethod) handler).getMethod();24         if(method.isAnnotationPresent(LoginRequired.class)) {25             LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);26             if(loginRequired.required()) {27                 if(token == null) {28                     throw ResponseException.UNAUTHORIZED;29                 }30                 // 校验token31                 if (JWTManager.verifyToken(token, request.getParameter("email"))){32                     return true;33                 }34                 else35                     throw ResponseException.UNAUTHORIZED;36             }37         }38         return true;39     }40 41     @Override42     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {43     }44 45     @Override46     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {47 48     }49 }
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     @Override14     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来捕获异常, 这种方式同时可以捕获程序运行时的各种错误,来做统一格式返回。

1 @RestControllerAdvice 2 public class ResponseAdvice implements ResponseBodyAdvice { 3     private Logger logger = LoggerFactory.getLogger(ResponseAdvice.class); 4     private ThreadLocal
threadLocal = ThreadLocal.withInitial(ObjectMapper::new); 5 6 @Override 7 public boolean supports(MethodParameter methodParameter, Class aClass) { 8 return true; 9 }10 11 @Override12 public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {13 ApiResult body;14 ObjectMapper mapper = threadLocal.get();15 16 if (o instanceof ResultMessage) {17 body = new ApiResult(((ResultMessage) o).getCode(), ((ResultMessage) o).getMessage(), null);18 } else if (o instanceof ApiResult) {19 body = (ApiResult) o;20 } else if (o instanceof String) {21 body = new ApiResult(ResultMessage.SUCEESS, o);22 try {23 return mapper.writeValueAsString(body);24 } catch (JsonProcessingException e) {25 body = new ApiResult(ResultMessage.JSON_PARSE_ERROR, null);26 }27 } else {28 body = new ApiResult(ResultMessage.SUCEESS, o);29 }30 31 return body;32 }33 34 /**35 * 401 - Unauthorized Exception36 */37 @ExceptionHandler(value = ResponseException.class)38 @ResponseBody39 public ApiResult unAuthorizedExceptionHandler(ResponseException e) {40 logger.trace(e.getMessage());41 return new ApiResult(e.getCode(), e.getMessage(), null);42 }43 44 /**45 * 500 - Internal Server Error46 */47 @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)48 @ExceptionHandler(value = Exception.class)49 @ResponseBody50 public ApiResult internalServerErrorHandler(Exception e) {51 logger.trace(e.getStackTrace()[0].toString());52 return new ApiResult(500, e.getStackTrace()[0].toString(), null);53 }54 55 56 }
View Code

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

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

转载于:https://www.cnblogs.com/bzaq/p/10547147.html

你可能感兴趣的文章
idou老师教你学Istio 04:Istio性能及扩展性介绍
查看>>
详解CSS的Flex布局
查看>>
域渗透——Pass The Hash & Pass The Key
查看>>
与Flutter第一次亲密接触-Android 视角
查看>>
Java设计模式---单例模式
查看>>
从源码角度看ContentProvider
查看>>
MMP,我说每年年会我怎么老是中不了奖,原来是这样
查看>>
十二、实战底部(二)
查看>>
阿里最全面试116题:阿里天猫、蚂蚁金服、阿里巴巴面试题含答案
查看>>
前端开发必备网站
查看>>
正则表达式大全(汇总)
查看>>
ELK 使用小技巧(第 5 期)
查看>>
Java并发(9)- 从同步容器到并发容器
查看>>
假如时光倒流,我会这么学习Java
查看>>
iOS--React Native浏览器插件
查看>>
一个JSON字符串和文件处理的命令行神器jq,windows和linux都可用
查看>>
Notification Swift 3 0
查看>>
Ionic Cordova实现软键盘的监听 以及操作大全
查看>>
Android小知识10则(下)
查看>>
Flask源码解析:从第一个版本开始阅读Flask源码
查看>>