JWT(JSON WEB TOKEN)是一种标准,用来在前后端或者系统间以JSON对象安全的传输信息,该信息是可以被验证和信任的,因为它是数字签名的。可以使用HMAC或RSA或ECDSA的公钥/私钥进行签名。
.
分割,三部分组成head,payload,singurater令牌的组成
格式如xxx.xxx.xxx,由.
分割,三部分组成
标头,标头通常由两部分组成,令牌的类型(JWT)和所使用的签名算法,例如HMAC、SHA256或RAS。
明文信息,json字符串
{
"alg":"HS256",
"typ":"JWT"
}
使用Base64编译为 xxx 这种格式,作为JWT的第一部分
有效负载,其中包含声明,声明是有关信息,也会使用Base64转为xxx,作为JWT的第二部分
{
"id":"sad854dsfewr1",
"name":"zhangsan"
}
需要注意的是,不要在这里放入敏感信息,例如密码,因为Base64是可逆的
签名,对头部及负载内容进行签名,保证JWT没有被篡改
引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
创建令牌
@Test
void contextLoads() {
String secretKey="asdf1213$1f.sad"; //密钥
//设置时间20s
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,20);
String token = JWT.create()
//.withHeader(map) 不设置头的话,默认就是alg:HMAC256 typ:JWT
.withClaim("id", "asd123456") //设置id
.withClaim("name", "zhangsan") //设置用户名
.withExpiresAt(instance.getTime()) //设置过期时间
.sign(Algorithm.HMAC256(secretKey));//使用密钥设置签名
System.out.println(token);
}
输出结果eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJpZCI6ImFzZDEyMzQ1NiIsImV4cCI6MTY5Nzc2OTMxNn0.MwxzzpvvCmCXB6JYWWVsMnRffo4RkpW413aDRndkbCc
验证令牌
这里密钥要和加密的密钥一致,否则签名验证不通过
@Test
void test(){
String secretKey="asdf1213$1f.sad"; //密钥
//创建验证对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secretKey)).build();
DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJleHAiOjE2OTc3NzEyMTB9.Ro9YSdTmuqdrisQhXRVDxPT-pv4FZLb5nnQkNRUrMjc");
//获取负载中name的值,存什么类型的值,就要使用对应的asInt或者asString
System.out.println(verify.getClaim("name").asString());
//获取过期时间
System.out.println(verify.getExpiresAt());
}
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 final String SECRET_KEY = "asdf1213$1f.sad";
/**
* 生成token
* @param map 负载中存储的值
* @return 生成的token值
*/
public static String getToken(Map<String, String> map) {
//设置过期时间
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7); //默认7天
//创建JWT builder
JWTCreator.Builder builder = JWT.create();
//存储payload
map.forEach((key, value) -> builder.withClaim(key, value));
//sign
String token = builder.withExpiresAt(instance.getTime()) //设置过期时间
.sign(Algorithm.HMAC256(SECRET_KEY));//使用密钥设置签名
return token;
}
/**
* 验证token
* @param token
* @return
*/
public static DecodedJWT verify(String token){
//创建验证对象
return JWT.require(Algorithm.HMAC256(SECRET_KEY)).build().verify(token);
}
}
springboot+jwt+mybatis
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.4.0version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>3.0.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.19version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.11version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
配置文件
server.port=8999
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/aaa?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
mybatis.type-aliases-package=com.jwt.entity
mybatis.mapper-locations=classpath:com/jwt/mapper/*.xml
实体
package com.jwt.entity;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class User {
private String username;
private String password;
}
Dao和Mapper
package com.jwt.dao;
import com.jwt.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao {
User login(User user);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jwt.dao.UserDao">
<select id="login" parameterType="User" resultType="User">
select * from user where username=#{username} and password=#{password}
select>
mapper>
Service
package com.jwt.service;
import com.jwt.dao.UserDao;
import com.jwt.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
@Service
public class UserServiceImpl {
@Autowired
private UsersDao usersDao;
public Users login(User user){
User user = userDao.login(user);
if(ObjectUtils.isEmpty(user)){
throw new RuntimeException("登录失败");
}
return user;
}
}
controller
package com.jwt.controller;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.jwt.entity.Users;
import com.jwt.service.UserServiceImpl;
import com.jwt.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserServiceImpl userService;
@RequestMapping("/user/login")
public Map<String,Object> login(Users user){
System.out.println(user.getUsername());
log.info("username:[{}]",user.getUsername());
log.info("password:[{}]",user.getPassword());
Map<String,Object> map=new HashMap<>();
try {
Users userDb = userService.login(user);
Map<String,String> payload = new HashMap<>();
payload.put("username",userDb.getUsername());
payload.put("password",userDb.getPassword());
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;
}
@RequestMapping("/user/verify")
public Map<String,Object> verify(String token){
Map<String,Object> map = new HashMap<String,Object>();
log.info("token:{}",token);
try {
DecodedJWT decodedJWT = JWTUtils.verify(token); //验证令牌
map.put("state",true);
map.put("msg","验证成功");
return map;
} 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);
return map;
}
}
访问/user/login
,首先从数据库查询,user对象是否存在,如果存在,就生成token,返回给前端,
然后再访问/user/verify
,验证这个token是否有效
如果对每个接口都做token认证,代码冗余太高,单体应用的话,可以使用拦截器验证,分布式应用可以使用网关验证
创建拦截器
token放在请求头header中
package com.jwt.interceptors;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jwt.utils.JWTUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
public class JWTinterceptors implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从 http 请求头中取出 token
String token = request.getHeader("token");
Map<String,Object> map = new HashMap<String,Object>();
try {
DecodedJWT decodedJWT = JWTUtils.verify(token); //验证令牌
map.put("state",true);
map.put("msg","验证成功");
return true; //验证通过,放行请求
} 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
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json; charset=utf-8");
response.getWriter().print(json);
return false;
}
}
配置拦截器
package com.jwt.config;
import com.jwt.interceptors.JWTinterceptors;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTinterceptors())
.addPathPatterns("/user/verify") //拦截测试验证接口
.excludePathPatterns("/user/login"); //放行登录接口
}
}
修改7.1中的代码,
/user/verify
,去除手动验证的方法,因为拦截器已经实现了 @RequestMapping("/user/verify")
public Map<String, Object> verify(String token) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("state", true);
map.put("msg", "请求成功");
return map;
}
验证
先访问登录接口,登录成功
再访问验证接口,现在token就不是以参数的形式传进去了,需要放在请求头header中,
先验证个没带token的,因为controller的代码已经改了,这个token无效提示不是controller的了,是拦截器中的提示
再验证个正确的token