文档章节

JWT 在 Spring 上的实践

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

精选30+云产品,助力企业轻松上云!>>>

简介

手头的新项目采用 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
博文 31
码字总数 17009
作品 0
长宁
项目经理
私信 提问
加载中
此博客有 7 条评论,请先登录后再查看。
Spring Boot Security JWT 整合实现前后端分离认证示例

前面两章节我们介绍了 Spring Boot Security 快速入门 和 Spring Boot JWT 快速入门,本章节使用 JWT 和 Spring Boot Security 构件一个前后端分离的认证系统。本章代码实例来自于 Spring Boo...

osc_m9b5s62g
2019/08/02
112
0
最近

SpringBoot + Shiro + JWT集成Redis缓存(Jedis) 将 Apache Shiro 改造成 JWT 认证方式 Smith-Cruise/Spring-Boot-Shiro Shiro集成redis和JWT碰到的问题 Shiro实现jwt验证流程梳理 Shiro禁用S...

miaojiangmin
2019/01/04
23
0
Spring Boot Security OAuth2 实现支持JWT令牌的授权服务器

文章首发于公众号《程序员果果》 地址:https://mp.weixin.qq.com/s/UvRfEF_WW1TGQqsuMxgoLg 概要 之前的两篇文章,讲述了Spring Security 结合 OAuth2 、JWT 的使用,这一节要求对 OAuth2、...

程序员果果
2019/05/28
20
0
手把手教你实现JWT Token

前言 Json Web Token () 近几年是前后端分离常用的 Token 技术,是目前最流行的跨域身份验证解决方案。你可以通过文章 。今天我们来手写一个通用的 服务。DEMO 获取方式在文末,实现在 相关...

osc_uz17zfbp
2019/10/26
110
0
Spring Security 实战干货: 登录成功后返回 JWT Token

文章目录 1. 前言 2. 流程 3. 实现登录成功/失败返回逻辑 3.1 AuthenticationSuccessHandler 返回 JWT Token 3.2 AuthenticationFailureHandler 返回认证失败信息 4. 配置 5. 验证 5.1 登录成...

osc_uz17zfbp
2019/10/29
63
0

没有更多内容

加载失败,请刷新页面

加载更多

深入解析Mysql 主从同步延迟原理及解决方案

https://www.cnblogs.com/fengff/p/11011702.html

漫步行者
12分钟前
22
0
webstorm常用快捷键

1.根据文件名查找文件 commond+shift+O 2.整个工程全局搜索 command + shift + r 3.折叠和展开代码 command + shift + -/+  4.文件内替换 command + R...

osc_bvincwvq
12分钟前
9
0
Linux网络数据转发平面的变迁-从内核协议栈到DPDK/XDP

昨晚读了一篇Paper: https://penberg.org/parakernel-hotos19.pdf 大意是说,随着IO设备的进化,它们的存取/传输速率已经超过了CPU到内存的存储/传输速率,再也不再是慢速 外设 了,所以,对...

osc_odp8kgup
13分钟前
10
0
服务器的保护有哪几类?

基础防护 基础防护说来只有简单的四个字,但是操作起来却有很多需要注意的地方,比如,服务器上的磁盘分区格式转化(例如NTFS格式),反病毒软件(包括服务器和工作站)的第一时间更新设置、...

osc_kz2s8mnr
14分钟前
10
0
redis 分布式缓存

分布式缓存的主要有点: 高性能与高并发 高性能:内存读取速度远高于数据库 高并发:数据库瞬间不能支持高并发,通过缓存,可以支持每秒十万级的请求。普通数据库每秒响应千级的。 使用缓存存...

ZH-JSON
14分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部