SpringBoot 使用JWT

当下微服务架构在互联网企业中得到了广泛的应用,其便捷的开发方式也得到了大多数Java工程师的喜爱。但是当一个大系统拆分成各个小系统后其系统间的会话管理便成了一个令人头疼的问题,今天要说的JWT(Json Web Token)就是一种解决这种问题的方式,而且该技术也在业界得到了广泛的应用。

JWT是什么?

我的理解就是一个base64编码后的字符串。
例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

可以看到,该字符串被"."符号分割成了三个部分,每个部分所代表的意义不一样。
第一部分我们称为头部,也就是“eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9”,这一串base64解码后得到一个json

{
  "alg": "HS256",
  "typ": "JWT"
}

声明一种加密方式,一般来说我们在使用的时候头部是固定的,无需修改任何内容。
第二部分称作负载,也就是“eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ”。这串字符串解析出来也是一个json

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

这里面包含了各种信息,一般来说将业务相关的信息放在这一部分,但是敏感数据还是不要放在这里了。
第三部分为签名,是为了保证jwt的数据不被篡改。具体算法是一部分的字符串和第二部分的字符串用"."连接组成一个字符串,然后通过第一部分中声明的加密方式进行加盐secret组合加密。

如何在springboot中使用

首先了解下JWT工作原理


JWT工作原理.png

这是JWT官网的一张图,理解起来就是客户端向服务端发起登录登录请求,服务器处理登录请求,成功则生成JWT并返回给客户端,客户端再次向服务端发起请求,此时将服务端返回的JWT带在请求header中一并返回,服务端接收到请求后先认证JWT是否合法,合法则处理后续业务逻辑,否则认定为请求非法。
原理就这些了,现在上实践:
1.搭建一个springboot web项目。
2.maven中添加依赖

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
</dependency>

3.编写jwt工具类该类主要用于生成jwt和解码jwt

/**
     *生成jwt字符串,参数列表可以自定义
     * @param name 用户名(自定义字段)
     * @param userId 用户id(自定义字段)
     * @param role 用户角色(自定义字段)
     * @param TTLSeconds 有效时长
     * @return jwt字符串
     */
    public static String createJWT(String name, String userId, String role,long TTLSeconds) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        byte[] keyBytes = DatatypeConverter.parseBase64Binary(Constants.JWT_SECRET);
        Key signKey = new SecretKeySpec(keyBytes, signatureAlgorithm.getJcaName());
//此处JwtBuilder是jwt包提供的用于生成jwt字符串的构造类,“setHeaderParam”表示设置jwt预定义字段的值,“claim”表示设置自定义字段及值
        JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
                .claim("name", name)
                .claim("id", userId)
                .claim("role", role)
                .signWith(signatureAlgorithm, signKey);
        if (TTLSeconds > 0) {
            Date expTime = new Date(nowMillis + TTLSeconds * 1000);
            builder.setExpiration(expTime).setNotBefore(now);
        }
        return builder.compact();
    }

解析jwt字符串

/**
     * 解析jwt字符串
     * @param token
     * @return
     */
    public static Claims parseJWT(String token) {
        try {
            return Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(Constants.JWT_SECRET)).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            log.error("jwt 过期!", e);
            return null;
        } catch (SignatureException e) {
            log.error("jwt 解码失败!", e);
            return null;
        } catch (Exception e) {
            log.error("jwt 未知异常!",e);
            return null;
        }
    }

返回的Claims本质是一个map,通过它何以获取JWT中包含的字段。
4.编写JWT过滤器。回顾其工作原理,在客户端请求到达服务端时,需要先验证JWT是否合法,因此将这个过程放在过滤器中是个不错的办法。

public class JwtFilter extends GenericFilterBean {




    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest req = (HttpServletRequest) servletRequest;
        final HttpServletResponse resp = (HttpServletResponse) servletResponse;
        final String authHeader = req.getHeader("authorization");
        if (authHeader != null && authHeader.startsWith("bearer;")) {
            final String token = authHeader.substring(7);
              //解析jwt字符串
            final Claims claims = JwtHelper.parseJWT(token);
            if (claims != null) {
              //获取jwt中的参数并放入request中以便后面的业务逻辑使用
                req.setAttribute(Constants.KEY_USER_ID,claims.get("id"));
                filterChain.doFilter(req,resp);
                return;
            }
        }
                //验证未通过直接返回
        resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json;charset=utf-8");
        ObjectMapper mapper = new ObjectMapper();
        ResponseVO vo = ResponseVO.buildResponse(ReturnCode.AUTH_FAIL);
        resp.getWriter().write(mapper.writeValueAsString(vo));
    }
}

5.springboot中注册过滤器和过滤规则

@SpringBootApplication
public class CmsApplication {

    public static void main(String[] args) {
        SpringApplication.run(CmsApplication.class, args);
    }


    /**
     * 注册jwt过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean jwtFilter() {
        final FilterRegistrationBean<JwtFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new JwtFilter());
        List<String> urlPatterns = new ArrayList<>();
        urlPatterns.add("/index/hello");
        registrationBean.addUrlPatterns(urlPatterns.toArray(new String[urlPatterns.size()]));
        return registrationBean;

    }


}

6.编写Controller。

@RestController
@RequestMapping("/index")
public class IndexController {
    @Autowired
    private CommonConfig config;
//用于验证jwt
    @RequestMapping("/hello")
    public ResponseVO hello(HttpServletRequest request) {
        String userId = (String) request.getAttribute(Constants.KEY_USER_ID);
        Map<String, Object> map = new HashMap<>();
        map.put("index", "hello");
        map.put("user_id",userId);
        return ResponseVO.buildResponse(ReturnCode.SUCCESS,map);
    }
//用于登录生成jwt
    @RequestMapping("/login")
    public ResponseVO login(String userName,String pass) {
        //省略了登录
        Map<String, String> map = new HashMap<>();
        String token = JwtHelper.createJWT(userName,"1","admin",config.getJwtExpTime());
        map.put("token", token);
        map.put("userName", userName);
        return ResponseVO.buildResponse(ReturnCode.SUCCESS, map);
    }
}

7.测试,测试工具我是用的是postman

生成JWT

像登录接口发送请求,接口返回生成的jwt字符串
验证JWT

将登录返回的JWT放入head中向服务器发起请求,服务器验证并返回成功。
如果在生成JWT时添加了过期时间,也就是在JWT工具类中的createJWT()方法中builder.setExpiration()方法设置了时间,则如果客户端发送的jwt超时后服务端将会抛出ExpiredJwtException,如果图省事则直接捕获该异常后返回客户端重新登录,如果想要更好的用户体验则需要自行进行过期时间验证并生成新的JWT。
代码:https://download.csdn.net/download/luo_dream/11002422

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容