隐藏

springboot+jwt做api的token认证

发布:2022/3/15 15:56:19作者:管理员 来源:本站 浏览次数:940



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

复制代码


1 <!--jwt-->

2 <dependency>

3     <groupId>io.jsonwebtoken</groupId>

4     <artifactId>jjwt</artifactId>

5     <version>0.9.0</version>

6 </dependency>

7 <!--阿里 FastJson依赖-->

8 <dependency>

9     <groupId>com.alibaba</groupId>

10     <artifactId>fastjson</artifactId>

11     <version>1.2.44</version>

12 </dependency>


复制代码


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


   生成token

   验证token是否有效

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


生成token


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

复制代码


1 long currentTime = System.currentTimeMillis();

2 return Jwts.builder()

3         .setId(UUID.randomUUID().toString())

4         .setIssuedAt(new Date(currentTime))  //签发时间

5         .setSubject("system")  //说明

6         .setIssuer("shenniu003") //签发者信息

7         .setAudience("custom")  //接收用户

8         .compressWith(CompressionCodecs.GZIP)  //数据压缩方式

9

10         .signWith(SignatureAlgorithm.HS256, encryKey) //加密方式

11         .setExpiration(new Date(currentTime + secondTimeOut * 1000))  //过期时间戳

12         .addClaims(claimMaps) //cla信息

13         .compact();


复制代码


通过uuid来标记唯一id信息;当然在对token加密时需要用到秘钥,jwt很是方便她支持了很多中加密方式如:HS256,HS265,Md5等复杂及常用的加密方式;


jwt生成的token中内容分为3个部分:head信息,payload信息,sign信息,通常我们要做的是往payload增加一些用户信息(比如:账号,昵称,权限等,但不包含密码);在对jwt的token有一定了解后,我们来看下真实生成的token值:


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


验证token是否有效


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

复制代码


1 public static boolean isExpiration(String token, String encryKey) {

2     try {

3         return getClaimsBody(token, encryKey)

4                 .getExpiration()

5                 .before(new Date());

6     } catch (ExpiredJwtException ex) {

7         return true;

8     }

9 }


复制代码


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

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


通常我们要把登录用户信息存储在jwt生成的token中,这里可以通过 addClaims(claimMaps) 传递map来设置信息,反过来要获取token中的用户信息,我们需要这样做:


1 return Jwts.parser()

2         .setSigningKey(encryKey)

3         .parseClaimsJws(token)

4         .getBody();


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


因此通常会在存储的时候json化,如下代码:


1 claimMaps.forEach((key, val) -> {

2     claimMaps.put(key, JSON.toJSONString(val));

3 });


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

复制代码


1 /**

2 * 获取body某个值

3 *

4 * @param token

5 * @param encryKey

6 * @param key

7 * @return

8 */

9 public static Object getVal(String token, String encryKey, String key) {

10     return getJws(token, encryKey).getBody().get(key);

11 }

12

13 /**

14  * 获取body某个值,json字符转实体

15  *

16  * @param token

17  * @param encryKey

18  * @param key

19  * @param tClass

20  * @param <T>

21  * @return

22  */

23 public static <T> T getValByT(String token, String encryKey, String key, Class<T> tClass) {

24     try {

25         String strJson = getVal(token, encryKey, key).toString();

26         return JSON.parseObject(strJson, tClass);

27     } catch (Exception ex) {

28         return null;

29     }

30 }


复制代码


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

复制代码


 1 public class JwtUtil {

 2

 3     /**

 4      * 获取token - json化 map信息

 5      *

 6      * @param claimMaps

 7      * @param encryKey

 8      * @param secondTimeOut

 9      * @return

10      */

11     public static String getTokenByJson(Map<String, Object> claimMaps, String encryKey, int secondTimeOut) {

12         return getToken(claimMaps, true, encryKey, secondTimeOut);

13     }

14

15     /**

16      * 获取token

17      *

18      * @param claimMaps

19      * @param isJsonMpas

20      * @param encryKey

21      * @param secondTimeOut

22      * @return

23      */

24     public static String getToken(Map<String, Object> claimMaps, boolean isJsonMpas, String encryKey, int secondTimeOut) {

25

26         if (isJsonMpas) {

27             claimMaps.forEach((key, val) -> {

28                 claimMaps.put(key, JSON.toJSONString(val));

29             });

30         }

31         long currentTime = System.currentTimeMillis();

32         return Jwts.builder()

33                 .setId(UUID.randomUUID().toString())

34                 .setIssuedAt(new Date(currentTime))  //签发时间

35                 .setSubject("system")  //说明

36                 .setIssuer("shenniu003") //签发者信息

37                 .setAudience("custom")  //接收用户

38                 .compressWith(CompressionCodecs.GZIP)  //数据压缩方式

39

40                 .signWith(SignatureAlgorithm.HS256, encryKey) //加密方式

41                 .setExpiration(new Date(currentTime + secondTimeOut * 1000))  //过期时间戳

42                 .addClaims(claimMaps) //cla信息

43                 .compact();

44     }

45

46     /**

47      * 获取token中的claims信息

48      *

49      * @param token

50      * @param encryKey

51      * @return

52      */

53     private static Jws<Claims> getJws(String token, String encryKey) {

54         return Jwts.parser()

55                 .setSigningKey(encryKey)

56                 .parseClaimsJws(token);

57     }

58

59     public static String getSignature(String token, String encryKey) {

60         try {

61             return getJws(token, encryKey).getSignature();

62         } catch (Exception ex) {

63             return "";

64         }

65     }

66

67     /**

68      * 获取token中head信息

69      *

70      * @param token

71      * @param encryKey

72      * @return

73      */

74     public static JwsHeader getHeader(String token, String encryKey) {

75         try {

76             return getJws(token, encryKey).getHeader();

77         } catch (Exception ex) {

78             return null;

79         }

80     }

81

82     /**

83      * 获取payload body信息

84      *

85      * @param token

86      * @param encryKey

87      * @return

88      */

89     public static Claims getClaimsBody(String token, String encryKey) {

90         return getJws(token, encryKey).getBody();

91     }

92

93     /**

94      * 获取body某个值

95      *

96      * @param token

97      * @param encryKey

98      * @param key

99      * @return

100      */

101     public static Object getVal(String token, String encryKey, String key) {

102         return getJws(token, encryKey).getBody().get(key);

103     }

104

105     /**

106      * 获取body某个值,json字符转实体

107      *

108      * @param token

109      * @param encryKey

110      * @param key

111      * @param tClass

112      * @param <T>

113      * @return

114      */

115     public static <T> T getValByT(String token, String encryKey, String key, Class<T> tClass) {

116         try {

117             String strJson = getVal(token, encryKey, key).toString();

118             return JSON.parseObject(strJson, tClass);

119         } catch (Exception ex) {

120             return null;

121         }

122     }

123

124     /**

125      * 是否过期

126      *

127      * @param token

128      * @param encryKey

129      * @return

130      */

131     public static boolean isExpiration(String token, String encryKey) {

132         try {

133             return getClaimsBody(token, encryKey)

134                     .getExpiration()

135                     .before(new Date());

136         } catch (ExpiredJwtException ex) {

137             return true;

138         }

139     }

140

141     public static String getSubject(String token, String encryKey) {

142         try {

143             return getClaimsBody(token, encryKey).getSubject();

144         } catch (Exception ex) {

145             return "";

146         }

147     }

148 }


复制代码

过滤器验证token


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

复制代码


1 public class AuthenFilter implements Filter {

2     @Override

3     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

4

5         HttpServletRequest rq = (HttpServletRequest) servletRequest;

6         HttpServletResponse rp = (HttpServletResponse) servletResponse;

7         RpBase rpBase = new RpBase();

8         try {

9             //只接受post

10             if (!rq.getMethod().equalsIgnoreCase("post")) {

11                 filterChain.doFilter(servletRequest, servletResponse);

12                 return;

13             }

14

15             String token = rq.getHeader("token");

16             if (StringUtils.isEmpty(token)) {

17                 rpBase.setMsg("无token");

18                 return;

19             }

20

21             //jwt验证

22             MoUser moUser = JwtUtil.getValByT(token, WebConfig.Token_EncryKey, WebConfig.Login_User, MoUser.class);

23             if (moUser == null) {

24                 rpBase.setMsg("token已失效");

25                 return;

26             }

27

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

29

30             filterChain.doFilter(servletRequest, servletResponse);

31         } catch (Exception ex) {

32         } finally {

33             if (!StringUtils.isEmpty(rpBase.getMsg())) {

34                 rp.setCharacterEncoding("utf-8");

35                 rpBase.setCode(HttpStatus.BAD_REQUEST.value());

36                 rp.getWriter().write(JSON.toJSONString(rpBase));

37             }

38         }

39     }

40 }


复制代码


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

复制代码


1 @Configuration

2 public class WebFilterConfig {

3

4     @Bean

5     public FilterRegistrationBean setFilter() {

6

7         FilterRegistrationBean registrationBean = new FilterRegistrationBean();

8         registrationBean.setFilter(new AuthenFilter());

9         registrationBean.addUrlPatterns("/api/*");

10         registrationBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);

11

12         return registrationBean;

13     }

14 }


复制代码


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

复制代码


1 @RestController

2 public class TestController {

3

4     @PostMapping("/api/t0")

5     public String t0() throws MyException {

6

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

8     }

9

10     @GetMapping("/token/{userName}")

11     public String getToken(@PathVariable String userName) {

12

13         MoUser moUser = new MoUser();

14         moUser.setUserName(userName);

15         moUser.setNickName(userName);

16

17         Map<String, Object> map = new HashMap<>();

18         map.put(WebConfig.Login_User, moUser);

19

20         return JwtUtil.getTokenByJson(map,

21                 WebConfig.Token_EncryKey,

22                 WebConfig.Token_SecondTimeOut);

23     }

24 }


复制代码


最终要获通过head传递token值来访问t01接口,得到如下结果:


token在有效时间后访问直接失败,从新获取token并访问t01接口,得到成功的信息:



git地址: https://github.com/shenniubuxing3    云栖社区博客:https://yq.aliyun.com/users/xjexlr3no5xj4