前端项目搭建参考:
Vue项目的搭建和启动_vue项目启动 csdn-CSDN博客
Vue + ElementUI 登录页面_vue用户登录页面-CSDN博客
跨域问题前端解决-CSDN博客
实现思路:
1. 实现的目的:为了保护网站安全信息,使用jwt进行权限验证,也就是说只有登录用户才可以看到网站的一些特定内容。那么浏览器每次发起请求时,就需要带上携带加密登录用户信息的token令牌。而token是用户登录时后端返回给前端的。
2. 编写登录接口,传入的登录form表单跟数据库储存的数据一致时,接口返回给前端之前,生成一个token,跟着登录信息一起返回
3. 前端拿到从服务端返回的token,储存于localStorage中,然后在前端配置发起请求时统一的拦截器,在请求头中塞入这个token,这样每个请求就都携带了token
4. 后端这个时候就需要校验了,你这个请求想要查看我网站的安全信息,我得先看看你有没有相应用户的权限,是否之前登录过,这个依据就是是否携带了正确的token。后端需要对大多数请求都进行这个校验,而每个请求的校验逻辑是一样的,所以后端需要写一个拦截器,拦截所有的请求,只有检验你是带着正确的token来的,才可以访问资源,当然登录接口需要放开。
5. 最后还需要在前端的接收请求拦截器中,当某个请求没有权限,即返回code=401时,需要跳转到登录页面引导用户登录或者注册。
1. pom文件添加JWT依赖
<!--jwt-->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>${jwt-version}</version>
</dependency>
2. 生成token,在登录接口返回给前端
/*** 生成token* @param userId 账号* @param sign 密码* @return String*/public static String getToken(String userId, String sign) {return JWT.create().withAudience(userId) //将userId荷载.withExpiresAt(DateUtil.offsetHour(new Date(), 2)) //当前时间2小时后过期.sign(Algorithm.HMAC256(sign)); //password作为token的密钥}
@RestController
public class LoginController {@Autowiredprivate UserService userService;@PostMapping("/login")public Result<JSONObject> login(@RequestBody User user) {Result<JSONObject> result = new Result<JSONObject>();if (StrUtil.isBlank(user.getUserName()) || StrUtil.isBlank(user.getPassword())) {return Result.error("数据输入不合法");}User dbUser = userService.login(user);//生成tokenString token = TokenUtils.getToken(user.getUserName(), user.getPassword());JSONObject obj = new JSONObject();obj.set("user", dbUser);obj.set("token", token);result.setResult(obj);result.success("登录成功");return result;}
}
3. 前端将token存入localStorage,设置请求拦截器,给每个请求带上token
submitForm() {this.$refs.form.validate((valid) => {if (valid) {let loginUrl = '/login'postAction(loginUrl, this.form).then((res) => {if (res.success) {let token = res.result.token;localStorage.setItem("token", token)this.$message.success("登录成功!")router.push("/")}})}else {this.$message({message: '请输入用户名和密码',type: 'warning'});}return false;})}
// request拦截器
// 可以在请求发送之前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
// 可以在这里添加认证 tokenconfig.headers['Content-Type'] = 'application/json;charset=utf-8';// config.headers['Authorization'] = 'Bearer your-token';config.headers['token'] = localStorage.getItem("token");return config;},error => {return Promise.reject(error);}
);
4. 后端设置jwt拦截器,并配置
/*** @Author: EstellaQ* @Date: 2025/4/7 9:20* @Description: jwt拦截器**/
public class JwtInterceptor implements HandlerInterceptor {@Resourceprivate UserMapper userMapper;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("token");if (StrUtil.isBlank(token)) {token = request.getParameter("token");}//如果不是映射到方法直接通过if (handler instanceof HandlerMethod) {//通过反射拿到方法上的注解AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class);if (annotation != null) {return true;}}//执行认证if (StrUtil.isBlank(token)) {throw new ServiceException(401, "请登录");}//获取token中的useridString userId;try {//解码userId = JWT.decode(token).getAudience().get(0);} catch (JWTDecodeException e) {throw new ServiceException(401, "请登录");}//根据token中的userid查询数据库User user = userMapper.selectByUserName(userId);if (user == null) {throw new ServiceException(401, "请登录");}//用户密码加签验证 tokenJWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();try {jwtVerifier.verify(token); //验证token} catch (JWTVerificationException e) {throw new ServiceException(401, "请登录");}return true;}
}
/*** @Author: EstellaQ* @Date: 2025/4/7 9:54* @Description: 配置拦截规则**/
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {@Overrideprotected void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**") //拦截所有的请求路径.excludePathPatterns("/login"); //将登录放行super.addInterceptors(registry);}@Beanpublic JwtInterceptor jwtInterceptor() {return new JwtInterceptor();}
}
5. 前端设置接口接收之后的拦截器,若权限认证失败,返回401,则返回登录接口
request.interceptors.response.use(response => {
// 处理响应数据let res = response.data// 兼容服务端返回的字符串数据if (typeof res == 'string') {res = res ? JSON.parse(res) : res}if (res.code === 401) {router.push("/login")}return res},error => {
// 处理响应错误return Promise.reject(error);}
);