手頭的新項目採用 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 配置,也相似,就不提了。
以上!