目录
1. 什么token
作为计算机术语时,是“令牌”的意思。Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
使用token机制的身份验证方法,在服务器端不需要存储用户的登录记录。
大概的流程:
1 客户端使用用户名和密码请求登录。
2 服务端收到请求,验证用户名和密码。
3 验证成功后,服务端会生成一个token,然后把这个token发送给客户端。
4 客户端收到token后把它存储起来,可以放在cookie或者Local Storage(本地存储)里。
5 客户端每次向服务端发送请求的时候都需要带上服务端发给的token。
6 服务端收到请求,然后去验证客户端请求里面带着token,如果验证成功,就向客户端返回请求的数据
2. jwt是什么
实施 Token 验证的方法挺多的,还有一些标准方法,比如 JWT,读作:jot ,表示:JSON Web Tokens 。JWT 标准的 Token 有三个部分:
1. header(头部),头部信息主要包括(参数的类型--JWT,签名的算法--HS256)
2. poyload(负荷),负荷基本就是自己想要存放的信息(因为信息会暴露,不应该在载荷里面加入任 何敏感的数据)
3. sign(签名),签名的作用就是为了防止恶意篡改数据,
例如:中间用点分隔开,并且都会使用 Base64 编码,所以真正的 Token 看起来像这样:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.//头部
eyJpZCI6IjIiLCJleHAiOjE3MDI0NDE3MDcsInVzZXJuYW1lIjoiYWRtaW4ifQ.//负载
k8F9h5GQB1-rTVi-8hs9jOWnfpJALSk2Y08xeZb7YlE//签名
3. jwt的依赖坐标
springboot引入jwt:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
4. springboot整合token
在这里只是给大家一个演示,
首先要知道如何生成一个token 那就是上面讲到的请求头+负载+签名
package com.hyh.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.hyh.domain.User;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Calendar;
import java.util.Date;
public class JwtUtil {
// 生成token
// Algorithm.HMAC256():使用HS256生成token,密钥则是用户的密码
private static final String SING = "MusicProject";
public String getToken(User user) {
//System.out.println(String.valueOf(user.getId()));
// 设置token过期时间
Calendar instance= Calendar.getInstance();
instance.add(Calendar.DATE,7);
String token="";
token= JWT.create()
.withClaim("id",String.valueOf(user.getId())) //设置载荷
.withClaim("username",user.getUsername())
.withExpiresAt(instance.getTime()) //设置令牌过期的时间
.sign(Algorithm.HMAC256(SING));
return token;
}
/**,
* 验证token 合法性
*/
public static DecodedJWT verify(String token) {
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
}
我把生成token的方法和验证方法弄成了一个工具类
在这里我定义的签名是静态变量 可以根据自己需求来定义 这里的id是从数据库里面拿到的 这里的验证token符合的话就放行,否则就抛异常。
接下来就需要配置一个拦截器 对于拦截请求资源
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token
// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(TokenRequired.class)) {
TokenRequired userLoginToken = method.getAnnotation(TokenRequired.class);
if (userLoginToken.required()) {
Map<String,Object> map = new HashMap<>();
// 获取请求头中令牌
System.out.println(token);
try {
// 验证令牌
JwtUtil.verify(token);
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","算法不一致");
}catch (Exception e){
e.printStackTrace();
map.put("msg","token无效!");
}
map.put("state",false); //设置状态
// 将map以json的形式响应到前台 map --> json (jackson)
String json = new ObjectMapper().writeValueAsString(map);
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().println(json);
return false;
}
}
return true;
}
在这里面 useservice是我自己的实现类,里面可以查到用户id
把拦截器注册:
@Configuration // 配置类
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthenticationInterceptor())
.addPathPatterns("/api/**") // 拦截所有请求
.excludePathPatterns("/api/login","/api/register"); // 放行登录 注册
}
}
这里因为拦截了所有请求 但是我们可以自己定义一个注解来判断当我们请求资源的时候需不需呀token验证。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenRequired {
boolean required() default true;
}
当我们在某个资源上加上了这个注解 说明这个资源需要token验证
这是我写的一个登录接口 集成了验证码功能
/*
* @description: 用户的登录
* @param: User user
* @return:result
* @author
* @date: 2023/9/28 10:03
*/
@PostMapping("/login")
public Result login(@RequestBody User user,HttpSession httpSession,HttpServletResponse response){
String checkCode = (String) httpSession.getAttribute("checkCode");
System.out.println(checkCode);
if (user.getCheCode() == null || !user.getCheCode().equalsIgnoreCase(checkCode)) {
return new Result(Code.Err, null, "验证码错误");
}
boolean flag = userService.selectUsernamePwd(user);
boolean is_exist = userService.selectByName(user.getUsername());
if (flag) {
JwtUtil jwtUtil = new JwtUtil();
User user1 = userService.selectByNameToken(user.getUsername());
String token = jwtUtil.getToken(user1);
user.setToken(token);
Cookie cookie = new Cookie("username",user.getUsername());
cookie.setMaxAge(60*60*24*7);
cookie.setPath("/");
response.addCookie(cookie);
httpSession.setAttribute("username", user.getUsername());
return new Result(Code.Ok,user,"登录成功");
}else if(!is_exist){
return new Result(Code.Err,user,"登录失败 用户不存在");
}else{
return new Result(Code.Err,user,"登录失败");
}
}
这个资源是需要token验证访问的资源 加了注解 @TokenRequired
@GetMapping
@TokenRequired
public Result selectAllSingers(HttpServletRequest request) {
List<Singer> singers = songService.selectSingerAll();
Integer code = singers != null ? Code.Ok : Code.Err;
String msg = singers != null ? "" : "数据查询失败 请重试";
return new Result(code, singers, msg);
}
当我们没有token的时候正常访问一下:
可以看到是无法访问的
我们可以先登录获取token 在进行访问试一下
因为我把token封装在user里面 所以返回了token 现在把token放到刚刚无法访问的url的请求头里,再次访问一下:
现在访问有数据了 这些数据都是我自己封装好的,你自己可以随便写写字符串返回来进行测试。
这些就是我在学习token遇到的一些问题和感受,希望对您有用!