这个项目是本人在工作之余利用闲暇时间进行开发的,主要是为了弥补大学毕业设计的遗憾(本人并不是没有过_,只是没有做自己喜欢的毕业设计)。酒店信息管理系统是一个前后端分离项目,前端使用vue,后端使用java开发,开发完后部署在云服务器上。
废话少说直接上项目地址,http://121.43.170.105:8004
账号:black 密码:root (也可以自行注册)
如果有不明白可以看我的主页关于酒店信息管理项目系列的文章
- 酒店信息管理项目——JWT令牌授权和登录
- 前言
- 一、项目结构和技术栈的使用
- 二、功能介绍
- 1.JWT令牌授权和登录
- 总结
前言
有需要代码的小伙伴私信我WX: 1208191350
一、项目结构和技术栈的使用
项目的整体结构采用的是前后端分离的形式:
后端框架的使用:spring-boot maven mybatis-plus mybatisX
后端技术栈的使用:
1.JWT令牌权限校验与授权登录
2.短信验证码登录,使用阿里云短信服务
3.swagger api接口文档生成器
4.mybatis-plus-generator 代码生成
5.七牛云图片上传服务,将图片保存到三方服务器
6.结合数据库使用spring-boot的定时调度任务
7.redis进行短信验证码的验证,和JWT令牌(Token)的激活(保活)
后台项目结构:
前端框架的使用:vue-cli3 element-ui vue-router
前端技术栈的使用:html css scss js vue axsio
前端项目结构:
1.1使用jwtUtil工具类:
package com.hotel.jwt;
import com.hotel.json.JsonUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.ResourceUtils;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.*;
public class JwtUtils {
private static final String JWT_PAYLOAD_USER_KEY = "user";
public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {
//计算过期时间
Calendar c = Calendar.getInstance();
c.add(Calendar.MINUTE,expire);
return Jwts.builder()
.claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
.setId(new String(base64.getEncoder().encode(UUID.randomUUID().toString().getBytes())))
.setExpiration(c.getTime())
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {
//计算过期时间
Calendar c = Calendar.getInstance();
c.add(Calendar.SECOND,expire);
return Jwts.builder()
.claim(JWT_PAYLOAD_USER_KEY, com.hotel.json.JsonUtils.toString(userInfo))
.setId(new String(base64.getEncoder().encode(UUID.randomUUID().toString().getBytes())))
.setExpiration(c.getTime())
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
public static Object getInfoFromToken(String token, PublicKey publicKey, Class userType) {
//解析token
Jws claimsJws = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
String userInfoJson = body.get(JWT_PAYLOAD_USER_KEY).toString();
return JsonUtils.toBean(userInfoJson, userType);
}
public static byte[] readBytes3(InputStream in) throws IOException {
BufferedInputStream bufin = new BufferedInputStream(in);
int buffSize = 1024;
ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize);
byte[] temp = new byte[buffSize];
int size = 0;
while ((size = bufin.read(temp)) != -1) {
out.write(temp, 0, size);
}
bufin.close();
byte[] content = out.toByteArray();
return content;
}
public static void main(String[] args) throws Exception{
//生成jwt令牌
Map data = new HashMap(){{
put("password","123456");
}};
//加载私钥
PrivateKey privateKey = RsaUtils.getPrivateKey(ResourceUtils.getFile("classpath:rsa.pri").getPath());
String jwtToken = JwtUtils.generateTokenExpireInSeconds(data, privateKey, 10);
System.out.println(jwtToken);
//加载公钥
PublicKey publicKey = RsaUtils.getPublicKey(ResourceUtils.getFile("classpath:rsa.pub").getPath());
//解析令牌
Map infoFromToken = (Map) JwtUtils.getInfoFromToken(jwtToken, publicKey, Map.class);
System.out.println(infoFromToken.get("name"));
System.out.println(infoFromToken.get("age"));
}
}
1.2使用RsaUtil工具类:
package com.hotel.jwt;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.base64;
public class RsaUtils {
private static final int DEFAULT_KEY_SIZE = 2048;
public static PublicKey getPublicKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPublicKey(bytes);
}
public static PrivateKey getPrivateKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPrivateKey(bytes);
}
public static PublicKey getPublicKey(byte[] bytes) throws Exception {
bytes = base64.getDecoder().decode(bytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
public static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException,
InvalidKeySpecException {
bytes = base64.getDecoder().decode(bytes);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(spec);
}
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String
secret, int keySize) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom(secret.getBytes());
keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 获取公钥并写出
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
publicKeyBytes = base64.getEncoder().encode(publicKeyBytes);
writeFile(publicKeyFilename, publicKeyBytes);
// 获取私钥并写出
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
privateKeyBytes = base64.getEncoder().encode(privateKeyBytes);
writeFile(privateKeyFilename, privateKeyBytes);
}
private static byte[] readFile(String fileName) throws Exception {
return Files.readAllBytes(new File(fileName).toPath());
}
private static void writeFile(String destPath, byte[] bytes) throws IOException {
File dest = new File(destPath);
if (!dest.exists()) {
dest.createNewFile();
}
Files.write(dest.toPath(), bytes);
}
public static void main(String[] args)throws Exception {
String publicKeyPath = "E:\key\rsa.pub";
String privateKeyPath = "E:\key\rsa.pri";
generateKey(publicKeyPath,privateKeyPath,"wfx",32);
}
}```
## 2.读入数据
代码如下(示例):
```c
data = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())
1.3设置拦截器和对指定的接口添加白名单,放行静态资源和指定的api:
package com.hotel.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// excludePathPatterns,不能写在一个里面可能不生效,要分开来
registry.addInterceptor(loginInterceptor)
.addPathPatterns("favicon.ico")
;//放行的服务,对这些服务设置白名单
}
}
1.4登录成功颁发令牌(内含JWT令牌保活逻辑),将令牌返回给前端:
@Override
public ResultVo login(Map map) throws Exception {
String username = (String) map.get("username");
String password = (String) map.get("password");
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
return new ResultVo(2000,"账号或密码为空");
// 查询数据库是否存在对应的用户名和密码,注册的时候会进行判断,如果用户名存在就不让注册
// 注册的时候用户名保证了唯一
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("user_name",username);
User user = userMapper.selectOne(queryWrapper);
if (user == null) return new ResultVo(2000,"账号不存在");
// 解密
boolean matches = bCryptPasswordEncoder.matches(password, user.getPassword());
if (!matches) return new ResultVo(2000,"账户名或者密码错误");
// user存在,登录成功
// 颁发JWT令牌
user.setPassword("");
PrivateKey privateKey = null;
try {
ClassPathResource resource = new ClassPathResource("rsa.pri");
InputStream inputStream = resource.getInputStream();
privateKey = RsaUtils.getPrivateKey(JwtUtils.readBytes3(inputStream));
} catch (Exception e) {
e.printStackTrace();
}
// 把user信息存储到token,设置token过期时间为一天
// 并且把 user信息带给前端
String jwtToken = JwtUtils.generateTokenExpireInMinutes(user, privateKey, 24 * 60);
// 登录成功之后将token放到redis里面,并设置过期时间为30分钟
// 后面对jwt进行验证,先拿到请求头的token和redis里面的token
// 1.判断redis里面的token和请求头的token存在与否,不存在判定为token过期,返回过期信息前端重新登录
// 2.redis的token存在,判断token与请求头的token相等,如果不想等,则为token错误
// 3.如果相等则为token信息正确,去解析redis里面的token,并且将redis里面的token的过期时间重置为30分钟
// 实现思路,通过redis缓存可以设置时间和重置时间,来达到发送正确的请求token保活的效果
redisUtil.set("token",jwtToken,30 * 60);
return new ResultVo(1000,user,jwtToken);
}
1.5前端通过请求头携带JWT令牌给后端,后端解析令领票的正确性:
前端添加请求拦截器,给每一个请求都加上一个token请求头
package com.hotel.interceptor;
import com.hotel.IUserService;
import com.hotel.json.JsonUtils;
import com.hotel.redis.RedisUtil;
import com.hotel.vo.ResultVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private IUserService iUserService;
@Resource
private RedisUtil redisUtil;
// 开启ThreadLocal
private static ThreadLocal local = new ThreadLocal<>();
//全局的验证功能,拦截器拦截请求
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//放行请求方法为OPTIONS的请求
String method = request.getMethod();
if(method.equals("OPTIONS")){
return true;
}
String headerToken = request.getHeader("token");
String redisToken = (String) redisUtil.get("token");
// 判断redis的token是否为空redisToken,为空则过期
// 判断header的token是否为空,为空则是没有权限的请求,需要重新登录获取token
if (StringUtils.isEmpty(redisToken) || StringUtils.isEmpty(headerToken)) {
//响应客户端
response(response);
return false;
}
// 判定redisToken和headerToken是否相同,不相同不让登录(疑似换token越过验证)
if (!redisToken.equals(headerToken)) {
//响应客户端
response(response);
return false;
}
//校验令牌
ResultVo result = iUserService.verify(redisToken);
// 获取用户Id
String userId = result.getData().toString();
if(result.getCode() == 1000){
// 把userId放到线程池
local.set(userId);
// 重新设置令牌的有效时间(token保持活性,重置为30分钟)
redisUtil.expire("token",30 * 60);
return true ; // 放行
}else{
// token错误
response(response);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
local.remove();
}
private void response(HttpServletResponse response) throws Exception{
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
//响应数据
ResultVo result = new ResultVo(2000,"令牌无效");
String resultStr = JsonUtils.toString(result);
writer.write(resultStr);
writer.close();
}
// 请求被拦截,解析tokenThreadLocal里面放入userId
// 请求之间可以跨域上下文使用userId,调用getUserId()
// 请求结束之后,userId会被删除
public static String getUserId() {
return local.get();
}
}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了酒店管理系统如何使用JWT授权的过程。



