JWT 在 Spring 上的实践


声明:本文转载自https://my.oschina.net/someok/blog/1556414,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

简介

手头的新项目采用 jwt 做客户端验证,而不再使用 cookie,确实方便很多,起码跨域这事不用考虑了。

jwt 是什么之类的就不多说了,这玩意的介绍满大街都是,这儿只是简单介绍下我在使用过程中的一些处理方式。

目的

这个 API 接口项目中使用 jwt 达成如下效果:

  1. 每个用户的签名都不一样,而不是共用签名,这样即使某人的 jwt 信息泄露,也不会影响其他人
  2. 服务器有专门的表存储用户签名,这样也可以在服务端控制某用户 jwt 的无效化
  3. 定义一个 spring 的 annotation,在 controller 方法的参数里面使用,用于得到用户的 jwt 存储的信息。

实现

采用的 jwt 处理库是 io.jsonwebtoken:jjwt:0.8.0,下面用伪码的方式介绍上述要求的实现过程。

签名方式

jjwt 组件支持自定义签名实现,只需要继承 SigningKeyResolverAdapter 即可:

public class SigningKeyResolverImpl extends SigningKeyResolverAdapter {      private byte[] decode(String secret) {         return TextCodec.BASE64URL.decode(secret);     }      /**      * 从数据库中返回相应的 hashId 用于加密或解密。      *      */     public Optional<String> getHashId(UUID clientId) {         // 数据库读取过程略          return Optional.empty();     }      /**      * 根据不同的 clientId 对应的 {@link JwtHash} 的 id 生成不同的加密密钥。      *      * @param clientId 用户 id      * @return      */     public byte[] resolveSigningKeyBytes(UUID clientId) {         Optional<String> hashIdOptional = getHashId(clientId);         if (hashIdOptional.isPresent()) {             String hashId = hashIdOptional.get();             return decode(hashId);         } else {             throw new IllegalArgumentException("不支持的参数格式");         }     }      /**      * 根据 claims 中 clientId 读取对应的 {@link JwtHash} 表中的 id 作为密钥来解密。      *      * @param header      * @param claims      * @return      */     @Override     public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {         String id = claims.getSubject();         UUID clientId = UUID.fromString(id);          Optional<String> hashIdOptional = getHashId(clientId);         if (hashIdOptional.isPresent()) {             String hashId = hashIdOptional.get();             return decode(hashId);         }          return super.resolveSigningKeyBytes(header, claims);     } }  

然后在生成和解密 jwt 的方法中调用即可:

加密:

Jwts.builder().signWith(SignatureAlgorithm.HS512,                 new SigningKeyResolverImpl                     .resolveSigningKeyBytes(clientId)) 

解密:

Jwts.parser()                 .setSigningKeyResolver(new SigningKeyResolverImpl) 

定义 spring 的 annotation

其实在 spring 中获得请求头的 Authorization 信息的方法有多种,常用的有拦截器和自定义 annotation,我个人采用的是后者,因为更加清晰,达到的效果为:

@GetMapping("/auth") public RestResponse authDemo(@JwtAuthHeader JwtAuth jwtAuth) {     return new RestResponse("auth success"); } 

只要是方法中存在 @JwtAuthHeader 定义的参数,就解析 Authorization 头信息,用这种方式还有个好处就是直接对方法做了用户验证了,所以连 spring-security 都省了。

当然,有些时候某些方法虽然需要验证,但是方法体里面其实没有用到 JwtAuth 信息,这个也无所谓,定义此参数,不用就是了。

annotation 定义

@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface JwtAuthHeader { } 

HandlerMethodArgumentResolver 实现

public class JwtAuthHeaderHandlerMethodArgumentResolver implements                                                         HandlerMethodArgumentResolver {     @Override     public boolean supportsParameter(MethodParameter parameter) {         return parameter.hasParameterAnnotation(JwtAuthHeader.class);     }      @Override     public Object resolveArgument(MethodParameter parameter,         ModelAndViewContainer mavContainer, NativeWebRequest webRequest,         WebDataBinderFactory binderFactory) throws Exception {          if (parameter.isOptional()) {             throw new IllegalArgumentException("@JwtAuthHeader 参数不支持 Optional");         }          if (!parameter.getParameterType().isAssignableFrom(JwtAuth.class)) {             throw new IllegalArgumentException("@JwtAuthHeader 参数必须是 JwtAuth");         }          String authorization = webRequest.getHeader('Authorization');          // Authorization 头不存在         if (StringUtils.isBlank(authorization)) {             throw new JwtAuthHeaderUnauthorizedException();         }          Optional<JwtAuth> jwtAuthOptional = JwtAuthUtil             .getJwtAuth(authorization);          // jwt 信息解析不匹配,表示没有权限         if (!jwtAuthOptional.isPresent()) {             throw new JwtAuthHeaderUnauthorizedException();         }          JwtAuth jwtAuth = jwtAuthOptional.get();          return jwtAuth;     } } 

上述代码抛出的异常,在 @ExceptionHandler 中捕获就可以了。

为使上述代码生效,如果是用的 spring java config,则增加如下代码:

@Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter {      @Override     public void addArgumentResolvers(         List<HandlerMethodArgumentResolver> argumentResolvers) {         argumentResolvers.add(new JwtAuthHeaderHandlerMethodArgumentResolver());     } } 

如果是 xml 配置,也类似,就不提了。

以上!

本文发表于2017年10月26日 07:25
(c)注:本文转载自https://my.oschina.net/someok/blog/1556414,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2210 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1