Jwt鉴权
1. JWT概念
JWT是JSON Web Token的缩写,它是一种开源标准(RFC 7519),是一种用来定义通信双方如何安全地交换信息的格式。
重点:
-
JWT之所以叫JSON Web Token,是因为其头部和载荷在编码之前都是JSON格式的数据;
-
JWT是一种标准,它有很多的实现方案,我们的java代码中使用的就是java-jwt;(也就是我们引入的jjwt依赖包)
-
JWT规定以JSON的格式传递信息,载荷payload的数据格式是JSON的,通常使用base64编码;
-
JWT是自包含的,Token本身携带了验证信息,不需要借助其他工具就可以知道一个Token是否有效,以及载荷信息;
JWT常见误区:
- JWT是不安全的,因为使用base64编码。这种理解是错误的,头部和载荷确实使用了base64编码,
它的作用是编码而非加密
,便于传输和前端解码获取信息。所以头部和载荷不要存放保密信息,
因为头部和载荷相当于是未加密的,任何人获取以后都可以通过解码,得到编码前的内容。而签名的作用只是验证头部和载荷的信息是真实的,未被篡改的。 - JWT是自包含,不需要借助数据库和缓存。这种理解是错误的,当需要高级功能,比如token刷新、黑名单、多人共享账号等,还是需要借助缓存和数据库。
- 获取头部和载荷信息之后可以修改或者伪造token。这是不可能的,即使头部和载荷的信息完全一样,但是加密的密钥不对,签名也是不对的,后端验证也没法通过。
1.1.什么是JWT
json web token,通过数字签名的方式,以json为载体,在不同的服务之间安全的传输信息的一种技术
1.2.JWT有什么用
一般使用在授权认证的过程中,一旦用户登录,后端返回一个token给前端,相当于后端给了前端返回了一个授权码,之后前端向后端发送的每一个请求都需要包含这个token,后端在执行方法前会校验这个token(安全校验),校验通过才执行具体的业务逻辑。
1.3.JWT的组成结构
jwt由Header
(头信息),PayLoad
(载荷),signature
(签名)三个部分组成
头部
Header头信息主要声明加密算法:(具体算法对称不对称加密不作为研究内容)
通常直接使用 HMAC HS256这样的算法
{
"typ":"jwt"
"alg":"HS256" //加密算法
}
然后将头部进行base64编码,构成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷
PayLoad(载荷)
{
"username":"zhangsan",
"name":"张三",
"exp":1686117973
}
对其进行base64编码,得到Jwt的第二部分。
eyJ1c2VybmFtZSI6InpoYW5nc2FuIiwibmFtZSI6IuW8oOS4iSIsImV4cCI6MTY4NjExNzk3M30=
签名
base64编码后的header和base64编码后的payload使用.
连接组成的字符串,然后通过header中声明的加密方式进行加盐加密,然后就构成了jwt的第三部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InpoYW5nc2FuIiwibmFtZSI6IuW8oOS4iSIsImV4cCI6MTY4NjExNzk3M30=.5tmHCpcsS_VuZ2_z5Rydf2OpsviBGwB-fJE5aS7gKqE
将以上的三个部分,通过 “.” 进行连接后,就得到了我们的jwt令牌
2. 开发使用JWT
2.1. 使用说明
为了使用jwt,我们需要以下准备工作:
- 引入jjwt依赖
- 编写TokenUtils工具类,用来生成和验证token
- 在配置文件中写入密钥和token存活时间
- 编写JwtConfig文件,从配置文件中获取密钥和token存活时间等信息
在具体使用的时候,我们一般按以下步骤:
- 在登录controller层引入JwtConfig对象
- 登录业务成功后,调用TokenUtils,获取token并返回给前端
- 新建登录拦截器,在请求头中获取token信息并验证,决定是否放行
- 在SpringMVCConfig.java文件中,配置登录拦截器
2.2. 配置信息
引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
配置文件
jwt:
# 签名密钥(按自己需求填写任意值)
secureKey: 48587471A6610367927BE4168C892294
# token存活时间(毫秒)
ttlMills: 3600000
2.3. 其他文件
JwtUtils.java
jwt工具类,用来生成和校验token
/**
* token工具类
* @Author Mosfield
*/
public class JwtUtils {
/**
* 生成token
* @param params 需要加入的参数
* @param secureKey 密钥
* @param ttlMills token存活时间
* @return
*/
public static String createToken(Map<String, Object> params, String secureKey, long ttlMills) {
// 1.声明加密的算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//2.声明过期时间
long expMillis = System.currentTimeMillis() + ttlMills;
Date exp = new Date(expMillis);
//3.配置jwt中要存入的信息
//4.创建密钥
String secretKey = secureKey;
//5.生成jwt
String token = Jwts.builder()
//配置jwt中需要保存的数据
.setClaims(params)
//配置加密算法&加密的key
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
//配置过期时间
.setExpiration(exp)
//根据配置信息生成jwt
.compact();
return token;
}
/**
* 验证jwt token
* @param token 被验证的token
* @param secureKey 密钥
* @return token中的载荷
*/
public static Claims parseToken(String token,String secureKey){
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(secureKey.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的TOKEN
.parseClaimsJws(token)
.getBody();
return claims;
}
}
【注意】
解析token的方法叫做 parseClaimsJws ,不是 parseClaimsJwt 。千万不要写错!
parseClaimsJws方法,解析的是带有jws签名的token,而parseClaimsJwt只能解析没有jws签名的token
JwtConfig.java
jwt相关配置信息,主要用来获取配置文件中的信息
/**
* jwt相关配置信息
* @Author Mosfield
*/
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {
private String secureKey;
private long ttlMills;
}
2.4. 实际使用案例
登录token验证。本例中使用对称加密。所有失败的处理使用全局异常处理解决。
EmpController.java
/**
* 员工管理Controller
*/
@RestController
@Slf4j
@CrossOrigin
@Api(tags = "员工相关接口")
public class EmpController {
@Autowired
EmpService empService;
@Autowired
JwtConfig jwtConfig;
/**
* 登录校验,登录成功后发放token
*
* @param userDTO 用户登录DTO
* @return
*/
@ApiOperation(value = "登录接口")
@PostMapping("/login")
public Result login(@RequestBody UserDTO userDTO) {
// 登录校验
Emp emp = empService.login(userDTO);
// 设置需要放入token负载中的数据
HashMap<String, Object> params = new HashMap<>();
params.put("uid", emp.getId());
// 设置负载,密钥,过期时间,获取该用户对应的token
String token = JwtUtils.createToken(params, jwtConfig.getSecureKey(), jwtConfig.getTtlMills());
// 登录成功,返回token信息
return Result.success(token);
}
}
LoginInterceptor.java
登录拦截器,用于校验用户是否已登录
/**
* 登录验证拦截器
* @Author Mosfield
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
JwtConfig jwtConfig;
/**
* 检查用户是否登录
* return true => 放行逻辑 => 可以继续走三层
* return false => 拦截 =>到此为止了
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头里获取token
String token = request.getHeader("token");
try {
// ======================== 具体的验证的逻辑(jwt) ========================
Claims claims = JwtUtils.parseToken(token, jwtConfig.getSecureKey());
// 登录成功
return true;
} catch (Exception e) {
//被拦截了,我们需要给客户端提示(如:请登陆后继续操作)
ObjectMapper objectMapper = new ObjectMapper();
String jsonStr = objectMapper.writeValueAsString(Result.error("请登陆后继续操作"));
//把json格式的数据写给前端
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(jsonStr);
return false;
}
}
}
WebMVCConfig.java
在SpringMVC配置类中注册登录拦截器
/**
* 对springMVC框架进行控制
* @Author Mosfield
*/
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Autowired
LoginInterceptor loginInterceptor;
/**
* 对拦截器进行配置
* 拦截器对象在容器中
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 需要放行
ArrayList<String> acl = new ArrayList();
// 放行下列swagger相关路径,即那些页面不需要登录就能访问。本例子中放行登录和注册功能。
acl.add("/login");
acl.add("/register");
// 配置LoginInterceptor登录拦截器
registry.addInterceptor(loginInterceptor)
// 拦截所有页面
.addPathPatterns("/**")
// 放行acl中的页面
.excludePathPatterns(acl);
}
}
Comments NOTHING