什么是jwt

首先jwt其实是三个英语单词JSON Web Token的缩写。通过全名你可能就有一个基本的认知了。token一般都是用来认证的,比如我们系统中常用的用户登录token可以用来认证该用户是否登录。jwt也是经常作为一种安全的token使用。

JWT的定义:
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
JWT特点:
简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

简单来说:

  • JWT 全称JSON Web Token ,简单的说就是用户登录成功之后,将用户的信息进行加密,然后生成一个token 返回给客户端

JWT可以做什么

授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够在不同的域中轻松使用。
信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。因为可以对 JWT 进行签名(例如,使用公钥/私钥对),所以可以确定发件人就是他们所说的那个人。此外,由于使用标头和有效负载计算签名,还可以验证内容没有被篡改。

为什么是jwt

基于传统的session

认证方式

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了
用户名和密码来进行用户认证,那么下一次请求时,用户还要进行一次用户认证才行,因为
根据http协议,我们并不知道是那个用户发出的请求。所以为了让我们的应用能识别是那个
用户发出的请求,我们只能在服务器存储一份用户的登陆信息,这份登陆信息会在响应时传
递给浏览器,告诉其保存为cookie,以便下次请求发送给我们的应用,这样我们的应用就能
识别请求来自那个用户了,这就是传统的基于session认证

认证流程

交互流程

暴露问题

1.每个用户经过我们的应用认证之后,我们的应用都是要在服务器端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中的,而随着认证用户的增多,服务器的压力开销就会明显增大2,用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应限制了负载均衡的能力,遮这也意味着限制了应用的扩展。3,因为是基于cookie来进行用户识别,cokkie如果被拦截,用就很容易受到跨站请求伪造的攻击4,前后盾分离系统中更痛苦,
- 前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次,如果用session每次携带sessionid 到服务器,服务器还要查询用户信息。
- 同时如果用户很多,这些信息存储在服务器内存中,给服务器增加负担。
- 容易受到CSRF(跨站伪造请求攻击)攻击,session是基于cookie进行用户识别的,cookie如果被拦截,用户就会很容易受到跨站请求伪造的攻击。
- sessionid就是一个特征值,表达的信息不够准确,不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制,不方便集群

认证:

用户认证是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信 息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手 机短信登录,指纹认证等方式。

会话:

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前 用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。

session认证方式‘

它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的
sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数
据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。

授权:

授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有 权限则拒绝访问。

JWT的结构是什么

令牌的组成

 1. 标头(Header)2,有效荷载(Payload)3,签名(Stringture)因此,JWT通常为 Header.Payload,Signature

Header

- 标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256 或RSA 。他会使用Base64 编码组成JWT结构的第一部分- 注意:Base64 是一种编码,也就是说,它是可以被翻译会原来的样子。并不是一种加密过程
{"alg":"HS256","typ":"JWT"
}

Payload(不要把敏感信息放在这里,会被解密)

- 令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的
声明。同样的,他会使用Base64编码组成JWT结构的第二部分
{"sub":"12345","name":"John Doe","admin":true
}

Signature

- 前两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息。Signature需要使
用编码后的Header和payload以及我们提供的一个秘钥,然后使用haeder中指定的算法(HS256)
进行签名,签名的作用是保证JWT没有被篡改过

签名的目的

-保证内容没有被篡改过
最后一步签名的过程实际上对头部以及负载内容进行签名,防止内容被更改。第三部分的内容是
第一部分加第二部分和加密盐生成的,如果更改了前面的内容生成的签名就会发生改变。

信息安全

- 在这里我们一定会问一个问题:Base64是一种编码,是可逆的,那我们的数据不就被暴露了吗- 是的,因为我们一般不会将敏感信息放在负载里面。因此JWT适用于向web应用传递非敏感信息。
JWT还用于设计用户的认证和授权系统,甚至实现web应用单点登陆

使用jwt

1.引入依赖

<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.19.1</version>
</dependency>

2.生成token

     public static void main(String[] args) {HashMap<String, Object> map = new HashMap<>();Calendar instance = Calendar.getInstance();instance.add(Calendar.SECOND,98);//生成令牌String token=JWT.create().withHeader(map)//header.withClaim("userId",21)//设置自定义用户id.withClaim("username","张三")//设置自定义用户名.withExpiresAt(instance.getTime())//设置过期时间.sign(Algorithm.HMAC256("token!Q2M#E$RW"));//设置签名 保留 复杂//输出打印System.out.println(token);}

根据令牌和签名解析数据

 //验证对象public static void main(String[] args) {//创建验证对象JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2M#E$RW")).build();DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NjY1MDY2NDgsInVzZXJJZCI6MjEsInVzZXJuYW1lIjoi5byg5LiJIn0.erWPVY6un_1a9-l1IYEibQW_XCYYmTIjQE4Iqkw9Iz0");System.out.println(verify.getClaim("userid").asInt());System.out.println(verify.getClaim("username").asString());System.out.println("过期时间"+verify.getExpiresAt());
//        System.out.println(verify.getClaim("username").asString());
//        System.out.println(verify.getClaims().get("userid").asString());
//        System.out.println(verify.getClaims().get("username").asString());}

生成令牌/解析令牌(签名算法和密钥要相同,否则会抛异常)案例

public static void main(String[] args) {// 生成令牌Map<String, Object> map = new HashMap<>();map.put("id", 100);map.put("name", "李四");String token = JWT.create().withClaim("userId", 1) // 设置payload.withClaim("userName", "张三").withClaim("user", map) // 自定义Map.withIssuedAt(new Date()) // 令牌生成时间.withExpiresAt(new Date(System.currentTimeMillis() + 8 * 60 * 60 * 1000)) // 令牌过期时间(8小时有效).sign(Algorithm.HMAC256("Test666"));// 设置签名算法和密钥System.out.println("token = " + token);// 解析令牌JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("Test666")).build();DecodedJWT verify = jwtVerifier.verify(token);System.out.println("解析结果:" + verify.getClaim("userId").asInt());System.out.println("解析结果:" + verify.getClaim("userName").asString());System.out.println("解析结果:" + verify.getClaim("user").asMap());System.out.println("解析结果:" + verify.getClaim("user").asMap().get("id"));System.out.println("解析结果:" + verify.getClaim("user").asMap().get("name"));}

JWT工具类封装

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Calendar;
import java.util.Map;public class JWTUtils {
private static String TOKEN="token!QM3#e4r";
/*
* 生成token
* */
public static String getToken(Map<String,String>map){JWTCreator.Builder builder = JWT.create();map.forEach((k,v)->{builder.withClaim(k,v);});Calendar instance = Calendar.getInstance();instance.add(Calendar.DATE,7);//默认7天过期instance.add(Calendar.MINUTE,30);// 有效时间:30分钟builder.withExpiresAt(instance.getTime());return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
}/** 生成token写法2* */public static String getToken1(Map<String,String>map){Calendar instance = Calendar.getInstance();instance.add(Calendar.DATE,7);//默认7天过期//创建jwt builderJWTCreator.Builder builder = JWT.create();//payloadmap.forEach((k,v)->{builder.withClaim(k,v);});String token= builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(TOKEN));return token;}/*
* 验证token
* 原本写法
*  public static void verity(String token){JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);}
* */
//优化public static DecodedJWT verity(String token){return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);}/**获取token中payload* */
/* public static DecodedJWT getToken(String token){return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);}*/}

Springboot整合jwt

 <!--引入jwt--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.19.2</version></dependency><!--使用mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><!--使用lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--引入druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.9</version></dependency><!--使用sql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>

配置文件

server.port=9000spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/cs?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
spring.datasource.username=root
spring.datasource.password=rootmybatis.type-aliases-package=com.xcuin.jwt
mybatis.mapper-locations=classpath:com/xcuin/jwt/mapper/*.xmllogging.level.com.xcuin.jwt.dao=debug

实体类

import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class User {private String id;private String name;private String password;
}

Dao+Dao.xml层

UserDao

import com.xcuin.jwt.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserDao {//直接根据用户名和密码登录User login(User user);}

UserDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.xcuin.jwt.Dao.UserDao"><select id="login" parameterType="User" resultType="User">select * from user where name=#{name} andpassword =#{password}</select>
</mapper>

UserService+UserServiceImpl层

userService

public interface UserService {User login(User user);//登录接口
}

userServiceImpl


import com.xcuin.jwt.Dao.UserDao;
import com.xcuin.jwt.entity.User;
import com.xcuin.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
@Transactional
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Override@Transactional(propagation = Propagation.SUPPORTS)public User login(User user){//根据接收用户名密码查询数据库User login = userDao.login(user);if (login!=null){return login;}throw new RuntimeException("登陆失败");}}

Controller层


import com.xcuin.jwt.Dao.UserDao;
import com.xcuin.jwt.entity.User;
import com.xcuin.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
@Transactional
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Override@Transactional(propagation = Propagation.SUPPORTS)public User login(User user){//根据接收用户名密码查询数据库User login = userDao.login(user);if (login!=null){return login;}throw new RuntimeException("登陆失败");}}

工具类


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Calendar;
import java.util.Map;public class JWTUtils {
private static String TOKEN="token!QM3#e4r";
/*
* 生成token
* */
public static String getToken(Map<String,String>map){JWTCreator.Builder builder = JWT.create();map.forEach((k,v)->{builder.withClaim(k,v);});Calendar instance = Calendar.getInstance();instance.add(Calendar.DATE,7);//默认7天过期builder.withExpiresAt(instance.getTime());return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
}/** 生成token写法2* *//*  public static String getToken1(Map<String,String>map){Calendar instance = Calendar.getInstance();instance.add(Calendar.DATE,7);//默认7天过期//创建jwt builderJWTCreator.Builder builder = JWT.create();//payloadmap.forEach((k,v)->{builder.withClaim(k,v);});String token= builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(TOKEN));return token;}*//*
* 验证token
* 原本写法
*  public static void verity(String token){JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);}
* */
//优化验证token+获取token中payloadpublic static DecodedJWT verity(String token){return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);}/**获取token中payload* */
/* public static DecodedJWT getToken(String token){return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);}*/}

增加回响token


import com.xcuin.jwt.entity.User;
import com.xcuin.jwt.service.UserService;
import com.xcuin.jwt.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;@RestController
@Slf4j
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/user/login")public Map<String,Object>login(User user){log.info("用户名:{{}}",user.getName());log.info("用户名:{{}}",user.getPassword());HashMap<String, Object> map = new HashMap<>();try {User login = userService.login(user);HashMap<String, String> payload = new HashMap<>();payload.put("id",login.getId());payload.put("name",login.getName());//生成jwt的令牌String token = JWTUtils.getToken(payload);map.put("state",true);map.put("msg","认证成功");map.put("token",token);} catch (Exception e) {map.put("state",false);map.put("msg",e.getMessage());}return map;}
}

新增加拦截器


import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xcuin.jwt.utils.JWTUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;/*** 定义拦截器* @Date: 2022/04/14 12:26* @Version 1.0**/
@Component
public class JWTInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {HashMap<String, Object> map = new HashMap<>();//获取请求头中的令牌String token = request.getHeader("token");try {JWTUtils.verity(token);return  true;//放行请求/*常见异常SignatureVerificationException: 签名不一致异常TokenExpiredException: 令牌过期异常AlgorithmMismatchException: 算法不匹配异常*/} catch (SignatureVerificationException e) {e.printStackTrace();map.put("msg","无效签名");} catch (TokenExpiredException e) {e.printStackTrace();map.put("msg","token过期");} catch (AlgorithmMismatchException e){e.printStackTrace();map.put("msg","token算法不一致");}catch (Exception e){e.printStackTrace();map.put("msg","token无效");}map.put("state",false);//设置状态//将map转为json jackjonString json = new ObjectMapper().writeValueAsString(map);response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);return false;}}

配置拦截器:新建InterceptorConfig类


import com.xcuin.jwt.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new JWTInterceptor())  // 注册拦截器.addPathPatterns("/**")   // 设置需要拦截的请求(也可设置成list集合).excludePathPatterns("/**");  // 设置不需要拦截的请求(也可设置成list集合)}}

优化原先的接口


import com.auth0.jwt.interfaces.DecodedJWT;
import com.xcuin.jwt.entity.User;
import com.xcuin.jwt.service.UserService;
import com.xcuin.jwt.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;@RestController
@Slf4j
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/user/login")public Map<String,Object>login(User user){log.info("用户名:{{}}",user.getName());log.info("用户名:{{}}",user.getPassword());HashMap<String, Object> map = new HashMap<>();try {User login = userService.login(user);HashMap<String, String> payload = new HashMap<>();payload.put("id",login.getId());payload.put("name",login.getName());//生成jwt的令牌String token = JWTUtils.getToken(payload);map.put("state",true);map.put("msg","认证成功");map.put("token",token);} catch (Exception e) {map.put("state",false);map.put("msg",e.getMessage());}return map;}@PostMapping("/user/test")Map<String,Object>test(HttpServletRequest request){HashMap<String, Object> map = new HashMap<>();//除理自己的业务String token = request.getHeader("token");DecodedJWT verity = JWTUtils.verity(token);log.info("用户id:{{}}",verity.getClaim("id").asString());log.info("用户name:{{}}",verity.getClaim("name").asString());map.put("state",true);map.put("msg","请求成功");return map;}
}

扩展:

扩展1:

工具类的编写

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;/*** @author Lehr* @create: 2020-02-04*/
public class JwtUtils {/**签发对象:这个用户的id签发时间:现在有效时间:30分钟载荷内容:暂时设计为:这个人的名字,这个人的昵称加密密钥:这个人的id加上一串字符串*/public static String createToken(String userId,String realName, String userName) {Calendar nowTime = Calendar.getInstance();nowTime.add(Calendar.MINUTE,30);Date expiresDate = nowTime.getTime();return JWT.create().withAudience(userId)   //签发对象.withIssuedAt(new Date())    //发行时间.withExpiresAt(expiresDate)  //有效时间.withClaim("userName", userName)    //载荷,随便写几个都可以.withClaim("realName", realName).sign(Algorithm.HMAC256(userId+"HelloLehr"));   //加密}/*** 检验合法性,其中secret参数就应该传入的是用户的id* @param token* @throws TokenUnavailable*/public static void verifyToken(String token, String secret) throws TokenUnavailable {DecodedJWT jwt = null;try {JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret+"HelloLehr")).build();jwt = verifier.verify(token);} catch (Exception e) {//效验失败//这里抛出的异常是我自定义的一个异常,你也可以写成别的throw new TokenUnavailable();}}/*** 获取签发对象*/public static String getAudience(String token) throws TokenUnavailable {String audience = null;try {audience = JWT.decode(token).getAudience().get(0);} catch (JWTDecodeException j) {//这里是token解析失败throw new TokenUnavailable();}return audience;}/*** 通过载荷名字获取载荷的值*/public static Claim getClaimByName(String token, String name){return JWT.decode(token).getClaim(name);}
}

注解类的编写

在controller层上的每个方法上,可以使用这些注解,来决定访问这个方法是否需要携带token,由于默认是全部检查,所以对于某些特殊接口需要有免验证注解

免验证注解

@PassToken:跳过验证,通常是入口方法上用这个,比如登录接口

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** @author Lehr* @create: 2020-02-03*/@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface PassToken {boolean required() default true;}

拦截器的编写

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author lehr*/@Configurationpublic class JwtInterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {//默认拦截所有路径registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");}@Beanpublic JwtAuthenticationInterceptor authenticationInterceptor() {return new JwtAuthenticationInterceptor();}} 

配置拦截器:新建InterceptorConfig类

import com.auth0.jwt.interfaces.Claim;import com.imlehr.internship.annotation.PassToken;import com.imlehr.internship.dto.AccountDTO;import com.imlehr.internship.exception.NeedToLogin;import com.imlehr.internship.exception.UserNotExist;import com.imlehr.internship.service.AccountService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;import java.util.Map;/*** @author Lehr* @create: 2020-02-03*/public class JwtAuthenticationInterceptor implements HandlerInterceptor {@AutowiredAccountService accountService;@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {// 从请求头中取出 token  这里需要和前端约定好把jwt放到请求头一个叫token的地方String token = httpServletRequest.getHeader("token");// 如果不是映射到方法直接通过if (!(object instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) object;Method method = handlerMethod.getMethod();//检查是否有passtoken注释,有则跳过认证if (method.isAnnotationPresent(PassToken.class)) {PassToken passToken = method.getAnnotation(PassToken.class);if (passToken.required()) {return true;}}//默认全部检查else {System.out.println("被jwt拦截需要验证");// 执行认证if (token == null) {//这里其实是登录失效,没token了   这个错误也是我自定义的,读者需要自己修改throw new NeedToLogin();}// 获取 token 中的 user NameString userId = JwtUtils.getAudience(token);//找找看是否有这个user   因为我们需要检查用户是否存在,读者可以自行修改逻辑AccountDTO user = accountService.getByUserName(userId);if (user == null) {//这个错误也是我自定义的throw new UserNotExist();}// 验证 tokenJwtUtils.verifyToken(token, userId)//获取载荷内容String userName = JwtUtils.getClaimByName(token, "userName").asString();String realName = JwtUtils.getClaimByName(token, "realName").asString();//放入attribute以便后面调用request.setAttribute("userName", userName);request.setAttribute("realName", realName);return true;}return true;}@Overridepublic void postHandle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o, Exception e) throws Exception {}}

这段代码的执行逻辑大概是这样的:

目标方法是否有注解?如果有PassToken的话就不用执行后面的验证直接放行,不然全部需要验证
开始验证:有没有token?没有?那么返回错误
从token的audience中获取签发对象,查看是否有这个用户(有可能客户端造假,有可能这个用户的账户被冻结了),查看用户的逻辑就是调用Service方法直接比对即可
检验Jwt的有效性,如果无效或者过期了就返回错误
Jwt有效性检验成功:把Jwt的载荷内容获取到,可以在接下来的controller层中直接使用了(具体使用方法看后面的代码)
接口的编写
这里设计了两个接口:登录和查询名字,来模拟一个迷你业务,其中后者需要登录之后才能使用,大致流程如下:

登录代码

/*** 用户登录:获取账号密码并登录,如果不对就报错,对了就返回用户的登录信息* 同时生成jwt返回给用户** @return* @throws LoginFailed  这个LoginFailed也是我自定义的*/@PassToken@GetMapping(value = "/login")public AccountVO login(String userName, String password) throws LoginFailed{try{service.login(userName,password);}catch (AuthenticationException e){throw new LoginFailed();}//如果成功了,聚合需要返回的信息AccountVO account = accountService.getAccountByUserName(userName);//给分配一个token 然后返回String jwtToken = JwtUtils.createToken(account);//我的处理方式是把token放到accountVO里去了account.setToken(jwtToken);return account;}

业务代码

这里列举一个需要登录,用来测试用户名字的接口(其中用户的名字来源于jwt的载荷部分)

@GetMapping(value = "/username")public String checkName(HttpServletRequest req) {//之前在拦截器里设置好的名字现在可以取出来直接用了String name = (String) req.getAttribute("userName");return name;}

扩展2:

引入依赖jar:

<!-- JWT依赖 -->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.1</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>

添加请求拦截器WebConfig(实现WebMvcConfigurer重写addInterceptors),对请求url进行token认证

@Configuration
public class WebConfig implements WebMvcConfigurer {@Resourceprivate JwtFilter jwtFilter ;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtFilter).addPathPatterns("/**");}
}

新增拦截器JwtFilter

@Component
public class JwtFilter extends HandlerInterceptorAdapter {public static final String LOGIN_URL = "/login";@Resourceprivate JwtTokenUtil jwtTokenUtil;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws SignatureException {String uri = request.getRequestURI();if(uri.contains(LOGIN_URL) || uri.contains("/doc.html") || uri.contains("/swagger-resources") ){return true;}//获取tokenString token = request.getHeader(jwtTokenUtil.header);if(StringUtils.isEmpty(token)){token = request.getParameter(jwtTokenUtil.header);}if(StringUtils.isEmpty(token)){throw new SignatureException(jwtTokenUtil.header+"不能为空");}//判断token是否超时Claims claims = jwtTokenUtil.getTokenClaim(token);if(null == claims || jwtTokenUtil.isTokenExpired(claims.getExpiration())){throw new SignatureException(jwtTokenUtil.header+"失效,请重新登录");}return true;}
}

异常处理类,springAop实现(异常通知)

@RestControllerAdvice
public class SecurityExceptionHandler {@ExceptionHandler(value = {SignatureException.class})public Result authorizationException(SignatureException e){return Result.failedWith(null,CodeEnum.FORBBIDEN.getCode(),"权限不足");}
}

tokenUtil工具类

@Component
public class JwtTokenUtil {@Value("${jwt.secret}")public String secret;@Value("${jwt.expire}")public int expire;@Value("${jwt.header}")public String header;/*** 生成token* @param subject* @return*/public String createToken (String subject){Date nowDate = new Date();Date expireDate = new Date(nowDate.getTime() + expire * 1000);return Jwts.builder().setHeaderParam("typ", "JWT").setSubject(subject).setIssuedAt(nowDate).setExpiration(expireDate).signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 获取token中注册信息* @param token* @return*/public Claims getTokenClaim (String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}catch (Exception e){return null;}}/*** 验证token是否过期失效* @param expirationTime* @return*/public boolean isTokenExpired (Date expirationTime) {return expirationTime.before(new Date());}/*** 获取token失效时间* @param token* @return*/public Date getExpirationDateFromToken(String token) {return getTokenClaim(token).getExpiration();}/*** 获取用户名从token中*/public String getUsernameFromToken(String token) {return getTokenClaim(token).getSubject();}/*** 获取jwt发布时间*/public Date getIssuedAtDateFromToken(String token) {return getTokenClaim(token).getIssuedAt();}
}

类中使用配置添加,application.yml中增加token的有效时间等

jwt:# 加密密钥secret: abcdefg1234567# token有效时长expire: 3600# header 名称header: token

jwt生成token使用

@Resource
JwtTokenUtil jwtTokenUtil;@Override
public String login(String userName, String password) {//验证用户名密码......//生成tokenreturn jwtTokenUtil.createToken("admin");
}

扩展3:

引入依赖jar:

<!-- JWT依赖 -->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.1</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>

类中使用配置添加,application.yml中增加token的有效时间等

jwt:# 加密密钥secret: abcdefg1234567# token有效时长expire: 3600# header 名称header: token

启动类

这里选择使用@ServletComponentScan,是因为在Filter类用@component和@configuration会导致

@WebFilter(urlPatterns = “/testToken”, filterName = “jwtFilter”) url失效变成拦截所有

package com.example.bootjwt;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication
@ServletComponentScan //这里是将filter扫描加载进spring容器
public class BootJwtApplication {public static void main(String[] args) {SpringApplication.run(BootJwtApplication.class, args);}}

实现代码

这里主要是做一个简单的demo验证,有三个类JwtController、CreatToken、JwtFilter。

JwtController:用来接收rest请求。

JwtUtil:用来生成token,解密token,验证token

JwtFilter:用来拦截请求对http请求中携带的token进行验证

JwtController

package com.example.bootjwt.controller;import com.example.bootjwt.Util.JwtUtil;
import com.example.bootjwt.domain.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@RestController
public class JwtController {@PostMapping("/get")public String creatToken2(){User user = new User();user.setId("1");user.setUsername("hand2020");user.setPassword("123456");return JwtUtil.createJWT(40000,user);}@PostMapping("/test")public String testToken2(HttpServletRequest request, HttpServletResponse response){String token= request.getHeader("Authorization");User user = new User();user.setId("1");user.setUsername("hand2020");user.setPassword("123456");if (JwtUtil.isVerify(token,user)){return "success";}return "fail";}
}

JwtUtil

这里我是在配置文件中读需要加密的明文,和过期时间。也可以在controller里处理参数设置。

package com.example.bootjwt.Util;import com.example.bootjwt.domain.User;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;public class JwtUtil {//    @Value("${jwt.secret}")
//    private static String key;/*** 用户登录成功后生成Jwt* 使用Hs256算法  私匙使用用户密码** @param ttlMillis jwt过期时间* @param user      登录成功的user对象* @return*/public static String createJWT(long ttlMillis, User user) {//指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;//生成JWT的时间long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);//创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)Map<String, Object> claims = new HashMap<String, Object>();claims.put("id", user.getId());claims.put("username", user.getUsername());claims.put("password", user.getPassword());//生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。String key = user.getPassword();//生成签发人String subject = user.getUsername();//下面就是在为payload添加各种标准声明和私有声明了//这里其实就是new一个JwtBuilder,设置jwt的bodyJwtBuilder builder = Jwts.builder()//如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的.setClaims(claims)//设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。.setId(UUID.randomUUID().toString())//iat: jwt的签发时间.setIssuedAt(now)//代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。.setSubject(subject)//设置签名使用的签名算法和签名使用的秘钥.signWith(signatureAlgorithm, key);if (ttlMillis >= 0) {long expMillis = nowMillis + ttlMillis;Date exp = new Date(expMillis);//设置过期时间builder.setExpiration(exp);}return builder.compact();}/*** Token的解密* @param token 加密后的token* @param user  用户的对象* @return*/public static Claims parseJWT(String token, User user) {//签名秘钥,和生成的签名的秘钥一模一样String key = user.getPassword();//得到DefaultJwtParserClaims claims = Jwts.parser()//设置签名的秘钥.setSigningKey(key)//设置需要解析的jwt.parseClaimsJws(token).getBody();return claims;}/*** 校验token* 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过* @param token* @param user* @return*/public static Boolean isVerify(String token, User user) {//签名秘钥,和生成的签名的秘钥一模一样String key = user.getPassword();//Jwts.parser在执行parseClaimsJws(token)时如果token时间过期会抛出ExpiredJwtException异常try {//得到DefaultJwtParserClaims claims = Jwts.parser()//设置签名的秘钥.setSigningKey(key)//设置需要解析的jwt.parseClaimsJws(token).getBody();if (claims.get("password").equals(user.getPassword())) {return true;}}catch (ExpiredJwtException e){e.printStackTrace();}return false;}}

JwtFilter

过滤器是通过实现Filter接口,注意@WebFilter相当于xml配置,但是需要在启动类上注解

@ServletComponentScan,将JwtFilter加入到spring容器中。

在JwtFilter类上注解@component或@configuration会导致@WebFilter失效从而拦截所有请求

目前这个没用到,直接在controller里做了判断,这个是后续业务需求的demo

package com.example.bootjwt;import com.example.bootjwt.Util.JwtUtil;
import com.example.bootjwt.domain.User;
import org.springframework.beans.factory.annotation.Autowired;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(urlPatterns = "/testToken", filterName = "jwtFilter")
public class JwtFilter implements Filter {@Autowiredprivate CreatToken creatToken;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");response.setHeader("Access-Control-Allow-Origin", "*");String token= request.getHeader("Authorization");User user = new User();user.setId("1");user.setUsername("hand2020");user.setPassword("123456");boolean flag = JwtUtil.isVerify(token,user);if (flag){filterChain.doFilter(servletRequest,servletResponse);}else {System.out.println("失败。。。。。。。。");response.getWriter().write("失败。。。。。。。。");}}@Overridepublic void destroy() {}
}

常用工具类之jwt的学习使用相关推荐

  1. 【JavaScript学习】JavaScript 常用工具类封装

    文章目录 1.JavaScript 常用工具类封装 (1)获得浏览器地址所有参数 (2)将json转为get参数 (3)格式校验工具类 (4)数组操作工具类 (5)表单取值工具类 (6)时间转换工具类 ...

  2. Hutool常用工具类

    Hutool常用工具类 介绍 安装 1. maven 2. gradle 常用工具类 1. 类型转换工具类-Convert 2. 日期时间工具-DateUtil 转换 字符串转日期 格式化日期输出 获 ...

  3. java file ip_java常用工具类 IP、File文件工具类

    本文实例为大家分享了java常用工具类的具体实现代码,供大家参考,具体内容如下 IP工具类 package com.jarvis.base.util; import java.io.IOExcepti ...

  4. java 常用工具类的使用一

    1. Java工具概述 很多人初学程序时,总是在想,那么多的算法该怎么写呀?那么多的数据结构都不熟悉,该怎么实现呀?总是担心英语不好程序学不精通,数学不好写程序无法达到巅峰.学的程序越多,不懂的知识越 ...

  5. java工具类与集合类_JAVA学习---集合和工具类

    1.集合概述 总称,包含多个具体的类 1.1集合和数组的比较 数组 集合 大小固定 动态变化 效率较高.只存储原生数据类型 效率稍低.可以存储引用类型 只能存放单一数据类型 可以存储多种数据类型(除泛 ...

  6. java 随机md5_java常用工具类 Random随机数、MD5加密工具类

    本文实例为大家分享了java常用工具类的具体代码,供大家参考,具体内容如下 random随机数工具类 package com.jarvis.base.util; import java.util.ra ...

  7. Java 常用工具类 Collections 源码分析

    文章出处 文章出自:安卓进阶学习指南 作者:shixinzhang 完稿日期:2017.10.25 Collections 和 Arrays 是 JDK 为我们提供的常用工具类,方便我们操作集合和数组 ...

  8. java常用工具类和Hutool常用的工具类整理

    java常用工具类和Hutool常用的工具类整理 1.java常用工具类 1.1 Scanner类 /*** Scanner 类*/@Testpublic void testScanner() {Sc ...

  9. commons-lang3-3.2.jar中的常用工具类的使用

    这个包中的很多工具类可以简化我们的操作,在这里简单的研究其中的几个工具类的使用. 1.StringUtils工具类 可以判断是否是空串,是否为null,默认值设置等操作: /*** StringUti ...

最新文章

  1. 剑指offer:把数组排成最小的数
  2. LiveBos---扩展按钮调用方法
  3. python机械编程_机器学习编程作业3——多类分类(Python版)
  4. idea调试debug技巧_被我用烂的DEBUG调试技巧,专治各种搜索不到的问题
  5. 欢迎您参加_ADT技术培训营
  6. 一名合格的电子工程师,不能逃避的“梗”
  7. c语言有趣代码_【新课预知】——C语言程序设计
  8. Arduino笔记-解决上传时出现avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00问题
  9. [转]Git使用基础篇
  10. 3-VUE -set
  11. 记一次wireshark抓取QQ好友IP和火绒抓取微信IP
  12. 微型计算机内存储容量的基本单位是,计算机中用来表示内存储器容量大小的基本单位是()。...
  13. 浅谈Hibernate的flush机制
  14. Coldfusion的基础知识
  15. Caffe Blobs
  16. 初等函数的麦克劳林级数展开+逆函数的展开求法
  17. 【21天学习挑战赛学习打卡】顺序查找
  18. 分布式配置中心 Disconf 安装配置
  19. ino查看工具android版,Tian Wang INO
  20. Oracle数据库迁移到人大金仓KingBase数据库

热门文章

  1. 日常bug记录——mybatis传值为null
  2. OpenGL ES 模拟器
  3. 前端百题斩【006】——js中三类字符串转数字的方式
  4. gitlab runner 使用案例
  5. CI24R1/SI24R1 2.4G无线传输技术--无线门铃
  6. 关于ruoyi框架集成activiti步骤
  7. 【SDU项目实训2019级】前端和后端实现手机短信验证码登录和注册功能
  8. 如何提升语音识别技术的识别能力?
  9. 通过 Teardrop 攻击程序学习自制 IP 包及了解包的结构
  10. TearDrop拒绝服务攻击