目录
1 前言
在我们编写的后端程序中,如果没有进行相关处理,那么就可能出现绕过登录,直接访问相关接口的情况。因此,我们引入了JWT令牌(一段特殊的字符串)。此外,使用JWT令牌,还要如下好处:
①减少查询数据库的次数,提高性能
②防止篡改,提高安全性
2 JWT令牌的组成
hdoj1u901jd.q0hd=kd.dhiwihdih(随便弄的,演示一下)
以.为分隔,我们可以将JWT令牌拆解成三部分
第一部分(Header/头):记录令牌类型和签名算法等
第二部分(Payload/有效载荷):携带一些自定义信息,如:id和用户名,不宜包含密码。因为JWT是依赖于Base64生成的,而Base64只是一种编码方式而非加密,携带密码就不安全
第三部分(Signature/签名):防止篡改,确保安全性,由前两部分+秘钥通过加密算法得到
3 使用步骤举例
3.1 pom.xml中引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
3.2 JWT生成
public class JwtTest {
@Test
public void testGenerate() {
Map<String, Object> claims = new HashMap<>();
claims.put("id", 5);
claims.put("username", "zy");
//生成jwt的代码
String token = JWT.create()
.withClaim("user", claims)//添加载荷
.withExpiresAt(new Date(System.currentTimeMillis() + 1000*60*60*2))//添加过期时间
.sign(Algorithm.HMAC256("test"));//指定算法,配置秘钥
System.out.println(token);
}
}
3.3 JWT验证
public class JwtTest {
/**
* JWT校验报错(失败)的两种情况:
* 1.JWT被修改
* 2.token过期
*/
@Test
public void testParse() {
//testGenerate()生成的的字符串,模拟用户传递过来的token
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
".eyJ1c2VyIjp7ImlkIjo1LCJ1c2VybmFtZSI6Inp5In0sImV4cCI6MTcwNjE3NjYxMX0" +
".bTpMAeawJ3u9-d2PKL2JIhynwGjTPZlkp1RIREwMDVc";
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("test")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);//验证token,生成一个解析后的JWT对象
Map<String, Claim> claims = decodedJWT.getClaims();//获得载荷
System.out.println(claims.get("user"));
}
}
4 实践中的使用举例
4.1 拦截非法访问
4.1.1 编写为工具类
public class JwtUtil {
//自定义秘钥
private static final String KEY = "XXXX";
//接收业务数据,生成token并返回
public static String genToken(Map<String, Object> claims) {
return JWT.create()
.withClaim("claims", claims)//添加载荷
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 ))//设置过期时间
.sign(Algorithm.HMAC256(KEY));//选择加密算法
}
//接收token,验证token,并返回业务数据
public static Map<String, Object> parseToken(String token) {
return JWT.require(Algorithm.HMAC256(KEY))
.build()
.verify(token)
.getClaim("claims")
.asMap();
}
}
4.1.2 下发给用户
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public Result<String> login(String username, String password) {
//其它代码...
User loginUser = userService.findByUserName(username);
Map<String, Object> claims = new HashMap<>();
claims.put("id", loginUser.getId());
claims.put("username", loginUser.getUsername());
String token = JwtUtil.genToken(claims);
return Result.success(token);
}
//其它代码...
}
4.1.3 编写拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
//xxx为约定好的请求头中携带的名称
Map<String, Object> claims = JwtUtil.parseToken(request.getHeader("xxx"));
//放行
return true;
} catch (Exception e) {
response.setStatus(401);//约定好的状态码,一般401表示未授权
//不放行
return false;
}
}
}
4.1.4 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//不拦截注册和登录接口
registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login", "/user/register");
}
}
大功告成,如果未登录访问接口,就会401。
4.2 获取相关数据提升效率
我们可以在请求头中获取有效载荷中的有效信息。
//token中包含id和username
public Result<User> func(@RequestHeader(name = "XXX") String token) {
Map<String, Object> map = JwtUtil.parseToken(token);
String username = (String)map.get("username");
User user = userService.findByUserName(username);
return Result.success(user);
}