JWT详解
传统的session认证
安全性
CSRF攻击因为基于cookie来进行用户识别, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
扩展性
对于分布式应用,需要实现 session 数据共享
性能
每一个用户经过后端应用认证之后,后端应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大,与REST风格不匹配。因为它在一个无状态协议里注入了状态
JWT
官网
JWT
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。特别适用于分布式站点的单点登录(SSO)场景
优点:
无状态
适合移动端应用
单点登录友好
原理
服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2030年7月1日0点0分"
}
注意
用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候会加上签名,服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展
JWT的结构
头部(header)/载荷(payload)/签证(signature)
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行
JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面
JWT项目搭建
创建maven项目,名字为cloud-jkw
pom.xml
<dependencies> <!-- JWT --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.79</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.7.0</version> </dependency> <!-- redis --> <!-- <dependency>--> <!-- <groupId>org.springframework.boot</groupId>--> <!-- <artifactId>spring-boot-starter-data-redis</artifactId>--> <!-- </dependency>--> <!-- eureka client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- Actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- SpringMVC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> </dependencies>
application.yml
server: port: 6500 spring: application: name: cloud-jwt eureka: client: service-url: defaultZone: http://localhost:7001/eureka/
启动类
@SpringBootApplication @EnableEurekaClient public class Jwt6500 { public static void main(String[] args) { SpringApplication.run(Jwt6500.class,args); } }
Jwt工具类
import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import java.util.Date; /** * JWT工具类 */ public class JWTUtil { //签发人 private static final String ISS_USER="jkw"; //令牌过期时间(五分钟) private static final long TOKEN_EXPIRE_TIME=5*60*1000; //签名秘钥(最好写的复杂一点) private static final String KEY="jkw-123456"; /** * 生成令牌(签名) * @return */ public static String token(){ Date now=new Date();//时间 Algorithm algorithm_key=Algorithm.HMAC256(KEY);//加密秘钥 //创建JWT String token = JWT.create() //签发人 .withIssuer(ISS_USER) //签发时间(当前时间) .withIssuedAt(now) //过期时间(当前时间+过期时间) .withExpiresAt(new Date(now.getTime() + TOKEN_EXPIRE_TIME)) //加密过的签名秘钥 .sign(algorithm_key); return token; } /** * 校验令牌(签名) * @param token 传来的令牌 * @return */ public static boolean verify(String token){ try { Algorithm algorithm_key=Algorithm.HMAC256(KEY);//加密秘钥 //校验令牌(校验 签发人/签名秘钥) JWTVerifier verifier = JWT.require(algorithm_key) //签发人 .withIssuer(ISS_USER) .build(); verifier.verify(token); return true; //如果校验有问题,就会抛出异常 }catch (IllegalArgumentException e){ e.printStackTrace(); }catch (JWTDecodeException e){ e.printStackTrace(); } return false; } //测试 public static void main(String[] args) { //1.生成令牌 //String token = token(); //System.out.println(token); //eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5dWx1IiwiZXhwIjoxNjc4MjAwMDM1LCJpYXQiOjE2NzgxOTk3MzV9.al7qL_RHDvLHLx38J-5WGcsrK9UnDEpSWM1L0I3iWT4 boolean verify = verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5dWx1IiwiZXhwIjoxNjc4MjAwMDM1LCJpYXQiOjE2NzgxOTk3MzV9.al7qL_RHDvLHLx38J-5WGcsrK9UnDEpSWM1L0I3iWT4"); System.out.println(verify); } }
统一返回结果集
/** * 统一返回结果实体类 */ @Data @AllArgsConstructor public class BaseResult<T> implements Serializable { //状态码(成功:200,失败:其他) private Integer code; //提示信息 private String message; //返回数据 private T data; //构建成功结果 public static <T> BaseResult<T> ok(){ return new BaseResult(CodeEnum.SUCCESS.getCode(),CodeEnum.SUCCESS.getMessage(),null); } //构建带有数据的成功结果 public static <T> BaseResult<T> ok(T data){ return new BaseResult(CodeEnum.SUCCESS.getCode(),CodeEnum.SUCCESS.getMessage(),data); } }
@Getter @AllArgsConstructor public enum CodeEnum { // 正常 SUCCESS(200, "OK"), // 系统异常 SYSTEM_ERROR(500, "系统异常"), // 业务异常 PARAMETER_ERROR(601, "参数异常"), INSERT_PRODUCT_TYPE_ERROR(602, "该商品类型不能添加子类型"), DELETE_PRODUCT_TYPE_ERROR(603, "该商品类型有子类型,无法删除"), UPLOAD_FILE_ERROR(604, "文件上传失败"), /** * user */ REGISTER_CODE_ERROR(605,"验证码不正确"), REGISTER_REPEAT_PHONE_ERROR(606,"手机号已存在"), REGISTER_REPEAT_NAME_ERROR(607,"用户名已存在"), LOGIN_NAME_PASSWORD_ERROR(608,"用户名或密码错误"), LOGIN_CODE_ERROR(609,"验证码不正确"), VERIFY_TOKEN_ERROR(610,"签名解析失败"), QR_CODE_ERROR(611,"二维码错误"), CHECK_SIGN_ERROR(612,"验签失败"), NO_STOCK_ERROR(613,"库存不足"), ORDER_EXPIRED_ERROR(614,"订单过期") ; private final Integer code; private final String message; }
控制器接口
@RestController @RequestMapping("/user") public class UserController { /** * 登陆 * @param username 用户名 * @param password 密码 */ @PostMapping("/login") public BaseResult login(String username, String password){ //1.验证用户名和密码 if("user".equals(username)&&"user".equals(password)){ //2.生成令牌 String token = JWTUtil.token(); return BaseResult.ok(token); }else { return new BaseResult(CodeEnum.Login_ERROR.getCode(), CodeEnum.Login_ERROR.getMessage(), null); } } }
访问post【postman测试】
localhost:6500/user/login?username=jkw&password=jkw
配置路由
#cloud-jwt - id: cloud-jwt uri: lb://cloud-jwt predicates: - Path=/user/*
访问post
localhost:9527/user/login?username=jkw&password=jkw