springboot+jwt做api的token认证

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介:

本篇和大家分享jwt(json web token)的使用,她主要用来生成接口访问的token和验证,其单独结合springboot来开发api接口token验证很是方便,由于jwt的token中存储有用户的信息并且有加密,所以适用于分布式,这样直接吧信息存储在用户本地减速了服务端存储sessiion或token的压力;如下快速使用:

<!--jwt-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>
<!--阿里 FastJson依赖-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.44</version>
</dependency>

一般使用jwt来达到3种结果:

  • 生成token
  • 验证token是否有效
  • 获取token中jwt信息(主要用户信息)

生成token

引入了jjwt依赖后,要生成token很方便;对于一个token来说,代表的是唯一并且不可逆的,因此我们在生成时需要增加一些唯一数据进去,比如下面的id:

long currentTime = System.currentTimeMillis();
return Jwts.builder()
        .setId(UUID.randomUUID().toString())
        .setIssuedAt(new Date(currentTime))  //签发时间
        .setSubject("system")  //说明
        .setIssuer("shenniu003") //签发者信息
        .setAudience("custom")  //接收用户
        .compressWith(CompressionCodecs.GZIP)  //数据压缩方式

        .signWith(SignatureAlgorithm.HS256, encryKey) //加密方式
        .setExpiration(new Date(currentTime + secondTimeOut * 1000))  //过期时间戳
        .addClaims(claimMaps) //cla信息
        .compact();

通过uuid来标记唯一id信息;当然在对token加密时需要用到秘钥,jwt很是方便她支持了很多中加密方式如:HS256,HS265,Md5等复杂及常用的加密方式;
jwt生成的token中内容分为3个部分:head信息,payload信息,sign信息,通常我们要做的是往payload增加一些用户信息(比如:账号,昵称,权限等,但不包含密码);在对jwt的token有一定了解后,我们来看下真实生成的token值:

eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAFWMTQ7CIBSE7_LWkPDzaEsP4QnYINCIptX4INE0vbtg4sLlfPPN7HAtGWbwg1BKL4GrcbEcIwpujZF8iiEpjXFapAAG2ReYpUEcR2VxYED13Nb0ppLW3hP1eEnblqsQuiFfY0OhUrl3I70evweU_aFSejZhd7DlcDv5NTmYHUilHTD3rf_hAccHRTv--7YAAAA.i4xwoQtaWI0-dwHWN8uZ4DBm-vfli5bavYU9lRYxU5E

验证token是否有效

token生成的时都会伴随者有一个失效的时间,在这我们可以通过setExpiration函数设置过期时间,记住jwt的有效时间不是滑动的,也就是说不做任何处理时,当到达第一次设置的失效时间时,就基本没用了,要获取token是否过期可以使用如下方式:

public static boolean isExpiration(String token, String encryKey) {
    try {
        return getClaimsBody(token, encryKey)
                .getExpiration()
                .before(new Date());
    } catch (ExpiredJwtException ex) {
        return true;
    }
}

这里使用了date的before来用获取的过期时间和当前时间对比,判断是否继续有效,需要注意的是如果在token失效后再通过getClaimsBody(token, encryKey)获取信息,此时会报ExpiredJwtException错误,我们即可认为过期。

获取token中jwt信息(主要用户信息)

通常我们要把登录用户信息存储在jwt生成的token中,这里可以通过

addClaims(claimMaps) //cla信息

传递map来设置信息,反过来要获取token中的用户信息,我们需要这样做:

return Jwts.parser()
        .setSigningKey(encryKey)
        .parseClaimsJws(token)
        .getBody();

此时body获取出来是Claims类型,我们需要从中获取到用户信息,需要注意的是在addClaims存储信息的时候如果存储的map值没做过出来,那完整的实体对象存储进去后会映射成一个LinkHasMap类型,如下:
image
因此通常会在存储的时候json化,如下代码:

claimMaps.forEach((key, val) -> {
    claimMaps.put(key, JSON.toJSONString(val));
});

再来就是通过get方法获取我们存储进去的信息,并json反序列化:

/**
* 获取body某个值
*
* @param token
* @param encryKey
* @param key
* @return
*/
public static Object getVal(String token, String encryKey, String key) {
    return getJws(token, encryKey).getBody().get(key);
}

/**
 * 获取body某个值,json字符转实体
 *
 * @param token
 * @param encryKey
 * @param key
 * @param tClass
 * @param <T>
 * @return
 */
public static <T> T getValByT(String token, String encryKey, String key, Class<T> tClass) {
    try {
        String strJson = getVal(token, encryKey, key).toString();
        return JSON.parseObject(strJson, tClass);
    } catch (Exception ex) {
        return null;
    }
}

来到这里一个Jwt的Util代码基本就完成了,下面给出完整的代码例子,仅供参考:

public class JwtUtil {

    /**
     * 获取token - json化 map信息
     *
     * @param claimMaps
     * @param encryKey
     * @param secondTimeOut
     * @return
     */
    public static String getTokenByJson(Map<String, Object> claimMaps, String encryKey, int secondTimeOut) {
        return getToken(claimMaps, true, encryKey, secondTimeOut);
    }

    /**
     * 获取token
     *
     * @param claimMaps
     * @param isJsonMpas
     * @param encryKey
     * @param secondTimeOut
     * @return
     */
    public static String getToken(Map<String, Object> claimMaps, boolean isJsonMpas, String encryKey, int secondTimeOut) {

        if (isJsonMpas) {
            claimMaps.forEach((key, val) -> {
                claimMaps.put(key, JSON.toJSONString(val));
            });
        }
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //签发时间
                .setSubject("system")  //说明
                .setIssuer("shenniu003") //签发者信息
                .setAudience("custom")  //接收用户
                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式

                .signWith(SignatureAlgorithm.HS256, encryKey) //加密方式
                .setExpiration(new Date(currentTime + secondTimeOut * 1000))  //过期时间戳
                .addClaims(claimMaps) //cla信息
                .compact();
    }

    /**
     * 获取token中的claims信息
     *
     * @param token
     * @param encryKey
     * @return
     */
    private static Jws<Claims> getJws(String token, String encryKey) {
        return Jwts.parser()
                .setSigningKey(encryKey)
                .parseClaimsJws(token);
    }

    public static String getSignature(String token, String encryKey) {
        try {
            return getJws(token, encryKey).getSignature();
        } catch (Exception ex) {
            return "";
        }
    }

    /**
     * 获取token中head信息
     *
     * @param token
     * @param encryKey
     * @return
     */
    public static JwsHeader getHeader(String token, String encryKey) {
        try {
            return getJws(token, encryKey).getHeader();
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 获取payload body信息
     *
     * @param token
     * @param encryKey
     * @return
     */
    public static Claims getClaimsBody(String token, String encryKey) {
        return getJws(token, encryKey).getBody();
    }

    /**
     * 获取body某个值
     *
     * @param token
     * @param encryKey
     * @param key
     * @return
     */
    public static Object getVal(String token, String encryKey, String key) {
        return getJws(token, encryKey).getBody().get(key);
    }

    /**
     * 获取body某个值,json字符转实体
     *
     * @param token
     * @param encryKey
     * @param key
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> T getValByT(String token, String encryKey, String key, Class<T> tClass) {
        try {
            String strJson = getVal(token, encryKey, key).toString();
            return JSON.parseObject(strJson, tClass);
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 是否过期
     *
     * @param token
     * @param encryKey
     * @return
     */
    public static boolean isExpiration(String token, String encryKey) {
        try {
            return getClaimsBody(token, encryKey)
                    .getExpiration()
                    .before(new Date());
        } catch (ExpiredJwtException ex) {
            return true;
        }
    }

    public static String getSubject(String token, String encryKey) {
        try {
            return getClaimsBody(token, encryKey).getSubject();
        } catch (Exception ex) {
            return "";
        }
    }
}

过滤器验证token

有了基本的JwtUtil工具,我们需要用到springboot项目中,一般来说对于登录授权token验证可以通过过滤器来操作,这里创建一个AuthenFilter,用于对post请求过来的token做验证:

public class AuthenFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest rq = (HttpServletRequest) servletRequest;
        HttpServletResponse rp = (HttpServletResponse) servletResponse;
        RpBase rpBase = new RpBase();
        try {
            //只接受post
            if (!rq.getMethod().equalsIgnoreCase("post")) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }

            String token = rq.getHeader("token");
            if (StringUtils.isEmpty(token)) {
                rpBase.setMsg("无token");
                return;
            }

            //jwt验证
            MoUser moUser = JwtUtil.getValByT(token, WebConfig.Token_EncryKey, WebConfig.Login_User, MoUser.class);
            if (moUser == null) {
                rpBase.setMsg("token已失效");
                return;
            }

            System.out.println("token用户:" + moUser.getNickName());

            filterChain.doFilter(servletRequest, servletResponse);
        } catch (Exception ex) {
        } finally {
            if (!StringUtils.isEmpty(rpBase.getMsg())) {
                rp.setCharacterEncoding("utf-8");
                rpBase.setCode(HttpStatus.BAD_REQUEST.value());
                rp.getWriter().write(JSON.toJSONString(rpBase));
            }
        }
    }
}

要是自定义过滤器AuthenFilter生效,还需要把她注册到容器中,这里通过编码方式,当然还可以通过@WebFilter注解来加入到容器中:

@Configuration
public class WebFilterConfig {

    @Bean
    public FilterRegistrationBean setFilter() {

        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new AuthenFilter());
        registrationBean.addUrlPatterns("/api/*");
        registrationBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);

        return registrationBean;
    }
}

注意addUrlPatterns匹配的是过滤器作用的url连接,根据需求而定;为了验证效果,这里我创建了两个接口getToken和t0,分别是获取token和post查询接口,代码如是:

@RestController
public class TestController {

    @PostMapping("/api/t0")
    public String t0() throws MyException {

        return UUID.randomUUID().toString();
    }

    @GetMapping("/token/{userName}")
    public String getToken(@PathVariable String userName) {

        MoUser moUser = new MoUser();
        moUser.setUserName(userName);
        moUser.setNickName(userName);

        Map<String, Object> map = new HashMap<>();
        map.put(WebConfig.Login_User, moUser);

        return JwtUtil.getTokenByJson(map,
                WebConfig.Token_EncryKey,
                WebConfig.Token_SecondTimeOut);
    }
}

最终要获通过head传递token值来访问t01接口,得到如下结果:
image
token在有效时间后访问直接失败,从新获取token并访问t01接口,得到成功的信息:
image

目录
相关文章
|
7天前
|
JSON 安全 算法
|
15天前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
31 4
|
17天前
|
Java API 数据库
如何使用Spring Boot构建RESTful API,以在线图书管理系统为例
【10月更文挑战第9天】本文介绍了如何使用Spring Boot构建RESTful API,以在线图书管理系统为例,从项目搭建、实体类定义、数据访问层创建、业务逻辑处理到RESTful API的实现,详细展示了每个步骤。通过Spring Boot的简洁配置和强大功能,开发者可以高效地开发出功能完备、易于维护的Web应用。
46 3
|
16天前
|
IDE Java API
基于Spring Boot REST API设计指南
【10月更文挑战第4天】 在现代的软件开发中,RESTful API已经成为了构建网络应用的标准之一。它通过HTTP协议提供了与资源交互的方式,使得不同的应用程序能够进行数据交互。Spring Boot作为一个功能强大的框架,它简化了配置和开发流程,成为了构建RESTful API的理想选择。本文将详细介绍如何在Spring Boot中设计和实现高质量的RESTful API,并提供一些最佳实践。
35 1
|
14天前
|
缓存 Java API
基于Spring Boot REST API设计指南
【10月更文挑战第11天】 在构建现代Web应用程序时,RESTful API已成为一种标准,使得不同的应用程序能够通过HTTP协议进行通信,实现资源的创建、读取、更新和删除等操作。Spring Boot作为一个功能强大的框架,能够轻松创建RESTful API。本文将详细介绍如何在Spring Boot中设计和实现高质量的RESTful API。
110 61
|
7天前
|
存储 安全 Java
|
3天前
|
JSON 算法 安全
JWT Bearer 认证在 .NET Core 中的应用
【10月更文挑战第30天】JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。它由头部、载荷和签名三部分组成,用于在用户和服务器之间传递声明。JWT Bearer 认证是一种基于令牌的认证方式,客户端在请求头中包含 JWT 令牌,服务器验证令牌的有效性后授权用户访问资源。在 .NET Core 中,通过安装 `Microsoft.AspNetCore.Authentication.JwtBearer` 包并配置认证服务,可以实现 JWT Bearer 认证。具体步骤包括安装 NuGet 包、配置认证服务、启用认证中间件、生成 JWT 令牌以及在控制器中使用认证信息
|
16天前
|
安全 Java API
基于Spring Boot REST API设计指南
【10月更文挑战第10天】 在现代Web应用开发中,RESTful API扮演着至关重要的角色。Spring Boot作为一个高效、便捷的Java开发框架,为构建RESTful API提供了强大的支持。本文将分享基于Spring Boot的REST API设计指南,涵盖从项目初始化到API文档配置的全过程。
30 0
|
消息中间件 存储 安全
干货|SpringBoot JMS(ActiveMQ)API实践应用详解
Active是一种开源的,实现了JMS1.1规范的,面向消息(MOM)的中间件,为应用程序提供高效的、可扩展的、稳定的和安全的企业级消息通信。AC-tiveMQ使用Apache提供的...
296 0
|
17天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
99 1