文档章节

JWT 在 Spring 上的实践

郁也风
 郁也风
发布于 2017/10/26 06:30
字数 823
阅读 955
收藏 58

简介

手头的新项目采用 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 配置,也类似,就不提了。

以上!

© 著作权归作者所有

共有 人打赏支持
郁也风
粉丝 16
博文 26
码字总数 13568
作品 0
长宁
项目经理
私信 提问
加载中

评论(7)

郁也风
郁也风

引用来自“公孙二狗”的评论

文中:用这种方式还有个好处就是直接对方法做了用户验证了,所以连 spring-security 都省了。

但是用户角色怎么处理呢?Spring Security 有个角色继承等非常有用,大一点的项目是逃不了这种要求的,如果只是玩具型的就意义不大了。
你说的角色是指的 Spring Security 中的 Auth 吧?SS 本身其实是没有角色的概念的,所以一般都是自己处理这块,Role 搞成个树状结构,而 Auth 则是扁平的,RA 之间搞成个多对多关系,这么一来 SS 能做的其实只剩下认证功能了,授权这块都是结合业务自己处理了,所以相比之下 SS 逐渐在我这块就没啥太大作用了
公孙二狗
公孙二狗
文中:用这种方式还有个好处就是直接对方法做了用户验证了,所以连 spring-security 都省了。

但是用户角色怎么处理呢?Spring Security 有个角色继承等非常有用,大一点的项目是逃不了这种要求的,如果只是玩具型的就意义不大了。
郁也风
郁也风

引用来自“跟猪谈理想”的评论

跨域需求对于app影响我觉的不是很大, 又不是发ajax浏览器限制,如果不牵扯身份认证,那就完全没影响。
没发现JWT比cookie,好在什么地方。 LZ讲一下优点😄
其实不用 cookie 还有一个考量,iOS 的 WebView 不支持 cookie(现在版本不知道是否支持了),所以综合来看还是 header 更好些
郁也风
郁也风

引用来自“跟猪谈理想”的评论

跨域需求对于app影响我觉的不是很大, 又不是发ajax浏览器限制,如果不牵扯身份认证,那就完全没影响。
没发现JWT比cookie,好在什么地方。 LZ讲一下优点😄

引用来自“leixu2”的评论

jwt 的载体也有多种,一种 header,一种 cookie,也可以是query,但多数是用 header,或者cookie;
jwt 提供多种签名算法,同时可以将一些用户公开属性放入jwt 中。

文章再深入一些会更好一些。
额,这种深入介绍jwt 的文章网上很多啊,我这个主要是看到有人在头疼怎么让客户端 jwt 作废所以写了这么一篇
郁也风
郁也风

引用来自“跟猪谈理想”的评论

跨域需求对于app影响我觉的不是很大, 又不是发ajax浏览器限制,如果不牵扯身份认证,那就完全没影响。
没发现JWT比cookie,好在什么地方。 LZ讲一下优点😄
其实用 header 而不是 cookie 了,还基于多客户端共用一套 API 的需要,重点就是这个 stateless 了。
leixu2
leixu2

引用来自“跟猪谈理想”的评论

跨域需求对于app影响我觉的不是很大, 又不是发ajax浏览器限制,如果不牵扯身份认证,那就完全没影响。
没发现JWT比cookie,好在什么地方。 LZ讲一下优点😄
jwt 的载体也有多种,一种 header,一种 cookie,也可以是query,但多数是用 header,或者cookie;
jwt 提供多种签名算法,同时可以将一些用户公开属性放入jwt 中。

文章再深入一些会更好一些。
跟猪谈理想
跟猪谈理想
跨域需求对于app影响我觉的不是很大, 又不是发ajax浏览器限制,如果不牵扯身份认证,那就完全没影响。
没发现JWT比cookie,好在什么地方。 LZ讲一下优点😄
springboot集成JWT,必须要使用spring security组件吗?

看到网上很多springboot集成JWT的demo,都使用了spring security组件, 想问下大牛们,spring security组件是JWT集成所必须的吗? 我只想单纯的springboot集成JWT,各位有例子参考不? 谢谢了...

jelly_oy
2018/10/11
115
0
Spring rest 配置jwt 安全验证

在用spring + mybatis 做项目。之前安全验证用basic 方式现在想换成jwt。 jwt : JWS模式对这个内容进行了数字化签名。这个内容被用来存放JWT的声明.服务端签名出JWT并且发送到客户端,并在用...

Fly_f
2017/10/28
0
2
SpringBoot--JWT集成配置

一, JWT简介 JWT 全名 JSON WEB Token 主要作用为用户身份验证, 广泛应用与前后端分离项目当中. JWT 的优缺点 : https://www.jianshu.com/p/af8360b83a9f 二, JWT 在 spring boot 项目当中的...

ge洋
2018/05/19
0
2
【spring cloud】自定义jwt实现spring cloud nosession

JWT实现在网关模块,网关的路由是默认配置。jwt 生成、验证依赖 最核心的配置是在spring security中加入我们token校验机制的fiter:JwtAuthenticationTokenFilter 在看我们的spring security...

冷冷gg
2017/08/22
0
9
深入理解Spring Cloud Security OAuth2及JWT

因项目需要,需要和三方的oauth2服务器进行集成。网上关于spring cloud security oauth2的相关资料,一般都是讲如何配置,而能把这块原理讲透彻的比较少,这边自己做一下总结和整理,顺带介绍...

暴走的初号机
2018/12/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

搜索引擎(Solr-索引详解)

时间字段类型特别说明 Solr中提供的时间字段类型( DatePointField, DateRangeField,废除的TrieDateField )是以时间毫秒数来存储时间的。 要求字段值以ISO-8601标准格式来表示时间:YYYY-MM...

这很耳东先生
24分钟前
0
0
Java成神之路

1、基础篇 01、面向对象 → 什么是面向对象 面向对象、面向过程 面向对象的三大基本特征和五大基本原则 → 平台无关性 Java 如何实现的平台无关 JVM 还支持哪些语言(Kotlin、Groovy、JRuby...

asdf08442a
54分钟前
2
0
dubbo源码分析-服务导出

简介 dubbo框架spring Schema扩展机制与Spring集成,在spring初始化时候加载dubbo的配置类。 dubbo服务导出的入口类是ServiceBean的onApplicationEvent方法 ServiceBean的继承关系如下 publ...

王桥修道院副院长
59分钟前
0
0
QQ音乐的动效歌词是如何实践的?

本文由云+社区发表 作者:QQ音乐技术团队 一、 背景 1. 现状 歌词浏览已经成为音乐app的标配,展示和动画效果也基本上大同小异,主要是单行的逐字染色的卡拉OK效果和多行的滚动效果。当然,我...

腾讯云加社区
今天
4
0
idea里配置springboot项目打热部署

首先添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional></dependency> 然后添......

shatian
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部