token令牌:通过账号密码换取一个有时效的token令牌,基于token去进行认证和授权相关的操作。
在进行登录验证时,我们需要session或cookie会话进行验证,前端包括浏览器还有微信小程序、app、公众号,而采用前后端分离并且不使用浏览器的情况下就会导致前端无法用cookie存储信息,这时我们就需要使用token令牌进行登录验证。
使用token令牌前后端交互验证登录过程如下图:
从上图可以看到和session验证时很相似的。
基于内存存储token令牌示例如下:
先在springboot项目中创建一个util包,再创建一个TokenUtil类:
代码如下:
public class TokenUtil {
/**
* 创建map用于存储所有的令牌
*/
private static Map<String, User> tokenMap=new HashMap<>();
/**
* 生成token,存储token-user对应关系
* 返回token令牌
*/
public static String generateToken(User user){
String token = UUID.randomUUID().toString();
tokenMap.put(token,user);
return token;
}
/**
* 验证token是否合法
* @param token
* @return
*/
public static boolean verify(String token){
return tokenMap.containsKey(token);
}
/**
* 根据token获取用户信息
* @param token
* @return
*/
public static User getUser(String token){
return tokenMap.get(token);
}
}
在controller层中生成token令牌:
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private HttpSession session;
@Autowired
private UserService userService;
@RequestMapping(value = "login",method = {RequestMethod.GET,RequestMethod.POST})
public Map<String,Object> login(User user){
Map<String,Object> map=new HashMap<>();
if(StringUtils.isEmpty(user.getUsername())||StringUtil.isEmpty(user.getPassword())){
map.put("code",0);
map.put("msg","用户未输入账户或密码");
}
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("username",user.getUsername());
queryWrapper.eq("password",user.getPassword());
User one = userService.getOne(queryWrapper);
if(one!=null){
//生成token令牌
String token = TokenUtil.generateToken(user);
session.setAttribute("user",user.getUsername());
map.put("date",one);
map.put("code",1);
map.put("token",token);
}else{
map.put("msg","用户名或密码错误");
}
return map;
}
}
最后在拦截器中进行验证:
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private HttpSession httpSession;
//Controller逻辑执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
System.out.println(uri);
/**
* HandlerMethod=>Controller中标注@RequestMapping的方法
* 需要配置静态资源不拦截时,添加这块逻辑 => 前后端分离项目
*/
if (!(handler instanceof HandlerMethod)) {
return true;
}
//从header中获取token令牌信息
String token = request.getHeader("token");
//进行验证
if (!TokenUtil.verify(token) &&httpSession.getAttribute("user") == null) {
// 未登录跳转到登录界面
response.sendRedirect("/user/toLogin");
return false;
} else {
return true;
}
}
//Controller逻辑执行完毕但是视图解析器还未进行解析之前
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
//Controller逻辑和视图解析器执行完毕
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
生成拦截器:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor()).addPathPatterns("/**")
// 那些路径不拦截
.excludePathPatterns("/user/login","/error","/swagger-ui.html/**","/swagger-resources/**");
}
@Bean
public LoginInterceptor loginInterceptor(){
return new LoginInterceptor();
}
}
进行测试时需要在请求头中放入token令牌信息。
token和session都是有实效的。
在登录后,每次重新启动项目都要进行一次登录,如果用户多的情况就会使数据库过载,使用token令牌时还有一个作用就是我们可以使用Redis缓存中间件对token令牌信息进行存储,在重新启动项目时,不会对已经登录的用户造成影响,不需要进行再次登录,让用户有更好的体验。
如下图:
也可以使用mysql数据库存储令牌信息但是Redis读写效率比数据库高