手头的新项目采用 jwt 作客户端验证,而再也不使用 cookie,确实方便不少,起码跨域这事不用考虑了。java
jwt 是什么之类的就很少说了,这玩意的介绍满大街都是,这儿只是简单介绍下我在使用过程当中的一些处理方式。web
这个 API 接口项目中使用 jwt 达成以下效果:spring
采用的 jwt 处理库是 io.jsonwebtoken:jjwt:0.8.0
,下面用伪码的方式介绍上述要求的实现过程。数据库
jjwt
组件支持自定义签名实现,只须要继承 SigningKeyResolverAdapter
便可:json
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))
解密:cookie
Jwts.parser() .setSigningKeyResolver(new SigningKeyResolverImpl)
其实在 spring 中得到请求头的 Authorization
信息的方法有多种,经常使用的有拦截器和自定义 annotation,我我的采用的是后者,由于更加清晰,达到的效果为:app
@GetMapping("/auth") public RestResponse authDemo(@JwtAuthHeader JwtAuth jwtAuth) { return new RestResponse("auth success"); }
只要是方法中存在 @JwtAuthHeader
定义的参数,就解析 Authorization
头信息,用这种方式还有个好处就是直接对方法作了用户验证了,因此连 spring-security
都省了。ide
固然,有些时候某些方法虽然须要验证,可是方法体里面其实没有用到 JwtAuth
信息,这个也无所谓,定义此参数,不用就是了。
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface JwtAuthHeader { }
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 配置,也相似,就不提了。
以上!