1.引言
企业开发中的前端工程一般不会让程序员自己从零搭建,所以咱们要学会使用别人封装好的架子,或者低代码开发平台,这里 咱们快速上手一个第三方的脚手架。
参考资料 花裤衩 (panjiachen) - Gitee.com
2.安装步骤
# 克隆项目
git clone https://github.com/PanJiaChen/vue-admin-template.git
# 进入项目目录
cd vue-admin-template
# 安装依赖
npm install
# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# 如果 上面的命令 执行的之后卡住了 可以尝试 切换镜像源 npm config set registry https://registry.npmmirror.com
# 然后 在 npm install
# 启动服务1
npm run dev
3.登录流程分析
为什么启动工程 就打开了登录页面,默认路径是 / 对应的是 dashboard?
因为 permission.js 里面配置了 路由守卫 ,每次路由之前 进行 一些逻辑判断 ,因为未登录 重定向到登录页面
vuex-> user.js
api->user.js
utils->request.js
在没有后端接口时,走的是前端mock 数据 ,这个请求会到 mock 下面的 user.js
token的数据结构
mock 的返回结果 被 util 下的 响应拦截获取
响应又传到 vuex 的 user.js的 login 方法的响应中
紧接着 还是 路由守卫 调用 getInfo 获取用户信息
api->user.js -> getInfo
请求 utils 下 request.js , 他会对请求进行拦截 让请求 携带请求头
请求到 mock 下的user.js 的 用户信息的数据
返回的用户信息的数据结构
页面跳转到 dashboard
切换为后端接口,修改 vue.config.js
proxy: {
[process.env.VUE_APP_BASE_API]: {
// target: 'http://localhost:8080', // 配置 请求后台的地址
target: 'http://localhost:81', // 配置 nginx 请求后台的地址
changeOrigin: true, // 开启跨域
pathRewrite: {
// /dev/api/vue-admin-template/user/login ---- > /user/login
['^' + process.env.VUE_APP_BASE_API + '/vue-admin-template']: ''
}
}
}
// before: require('./mock/mock-server.js')
这里 咱们切断了 前端请求自身mock 数据的路线,由proxy 代理到 后端 接口
补充:禁用eslint 步骤
(70条消息) Vue Admin Template关闭eslint校验,lintOnSave:false设置无效解决办法_lint-staged 关闭_H-rosy的博客-CSDN博客
后端 创建相应的接口 返回 前端需要的数据
@PostMapping("/login")
@UnInterception
public R login(@RequestBody User user){
System.out.println("登录操作");
User currentUser = userService.login(user);
return R.ok().data("token","admin-token").code(20000);
}
@GetMapping("/info")
public R info(String token){
System.out.println(token);
HashMap<Object, Object> map = new HashMap<>();
map.put("roles","admin");
map.put("introduction","I am a super administrator");
map.put("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
map.put("name","Super Admin");
return R.ok().data(map).code(20000);
}
4.jwt快速入门
1.介绍
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
实现是 一个 加密的字符串 ,这个字符串存储了用户的身份凭证 等信息
2.快速开始
2.1添加依赖
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
2.2创建工具类
package com.glls.eduonline.utils;
import com.glls.eduonline.common.CheckResult;
import com.glls.eduonline.constant.SystemConstant;
import io.jsonwebtoken.*;
import org.bouncycastle.util.encoders.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SignatureException;
import java.util.Date;
/**
* 前端发送认证请求 比如用户名密码 认证通过后 后台生成一个 token
* 发送给前端 前端存储到 localstorage 以后每次请求 都带这个token 发送到后台 去验证
*
*
*
* @date 2021/5/9 20:44
*/
public class JwtUtils {
/**
* 签发JWT
* @param id
* @param subject 可以是JSON数据 尽可能少
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject) // 主体 一般是用户名
.setIssuer("glls") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis; // 当前时间 + 有效时间
Date expDate = new Date(expMillis); // 过期时间
builder.setExpiration(expDate); // 设置过期时间
}
return builder.compact();
}
/**
* 验证JWT
* @param jwtStr
* @return
*/
public static CheckResult validateJWT(String jwtStr) {
CheckResult checkResult = new CheckResult();
Claims claims = null;
try {
claims = parseJWT(jwtStr);
checkResult.setSuccess(true);
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) {
checkResult.setErrCode(SystemConstant.JWT_ERRCODE_EXPIRE);
checkResult.setSuccess(false);
} catch (SignatureException e) {
checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
} catch (Exception e) {
checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
}
return checkResult;
}
/**
* 生成加密Key
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decode(SystemConstant.JWT_SECERT);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析JWT字符串
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
public static void main(String[] args) throws InterruptedException {
// 后端生成token
String sc=createJWT("1","jack",SystemConstant.JWT_TTL);
System.out.println(sc);
// 后端验证token
CheckResult checkResult = validateJWT(sc);
System.out.println(checkResult.isSuccess());
System.out.println(checkResult.getErrCode());
Claims claims=checkResult.getClaims();
System.out.println(claims);
System.out.println(claims.getId());
System.out.println(claims.getSubject());
// 刷新token 重新生成token
Claims claims2=validateJWT(sc).getClaims();
String sc2=createJWT(claims2.getId(),claims2.getSubject(),SystemConstant.JWT_TTL);
System.out.println(sc2);
}
}
//eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwic3ViIjoiamFjayIsImlzcyI6ImdsbHMiLCJpYXQiOjE2NjA1Mjg2NzgsImV4cCI6MTY2MDUzMjI3OH0.qHUoDo6-BdNmHMzyLFZ-nlUD98te0QU7eenVJGsEiKo
配套 常量类 和 结果类
package com.glls.eduonline.constant;
/**
* @ClassName : SystemConstant
* @Author : glls
* @Date: 2021/5/9 20:30
* @Description :
*/
public class SystemConstant {
/**
* token
*/
public static final int JWT_ERRCODE_NULL = 4000; //Token不存在
public static final int JWT_ERRCODE_EXPIRE = 4001; //Token过期
public static final int JWT_ERRCODE_FAIL = 4002; //验证不通过
/**
* JWT
*/
public static final String JWT_SECERT = "8677df7fc3a34e26a61c034d5ec8245d"; //密匙
public static final long JWT_TTL = 60 * 60 * 1000; //token有效时间
}
package com.glls.eduonline.common;
import io.jsonwebtoken.Claims;
public class CheckResult {
private int errCode;
private boolean success;
private Claims claims;
public int getErrCode() {
return errCode;
}
public void setErrCode(int errCode) {
this.errCode = errCode;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public Claims getClaims() {
return claims;
}
public void setClaims(Claims claims) {
this.claims = claims;
}
}
3.认证成功 发送token
@PostMapping("/login")
public JsonResult login(@RequestBody User user){
User currentUser = userService.login(user);
if(currentUser==null){
//认证失败
return JsonResult.ok(currentUser);
}else {
//认证成功 向前端 发送token
String token = JwtUtils.createJWT(String.valueOf(currentUser.getUserId()),currentUser.getRealname(), SystemConstant.JWT_TTL);
return JsonResult.ok(token);
}
}
4.前端接受token 并存起来 以后每次请求在请求头上携带token
//整合 jwt 返回的是 token 后面的请求 就需要携带token
var token = resp.data.data;
//把响应结果的token 存到 localStorage
window.localStorage.setItem("token",token);
后续请求携带token,这里是手动把token 拼到axios的请求头上面,如果使用vue-admin-template的话 会在 请求拦截器上拼接
let token = window.localStorage.getItem("token");
this.axios.defaults.headers.common["token"] = token; // 请求头上带token
校验token
是在 请求到达目标资源之前 判断请求是否合法,这里咱们通过 校验token 的形式来实现
可以使用 拦截器 或者 过滤器 对请求 进行 拦截 获取请求携带的token, 对其进行校验
拦截器
package com.glls.phoneservice.interceptor;
import com.glls.common.R;
import com.glls.common.SystemConstant;
import com.glls.common.utils.CheckResult;
import com.glls.common.utils.JwtUtils;
import com.glls.phoneservice.annotation.UnInterception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
/**
* @date 2023/7/14
* @desc
*/
@Slf4j // 加了这个注解 可以直接使用 log 对象输出日志
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(request.getRequestURI()); // 输出请求路径
if(handler instanceof HandlerMethod){
// 如果接口方法处理器 就会走这里
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
String methodName = method.getName();
log.info("====拦截到了方法:{},在该方法执行之前执行====", methodName);
//得到方法上的注解 根据注解判断 这个方法需不需要拦截
// 使用UnInterception 注解标注的方法 直接放行
UnInterception annotation = method.getAnnotation(UnInterception.class);
if(null != annotation){
return true;
}
String token = request.getHeader("token");
if(StringUtils.isEmpty(token)){
log.info("token为空,请先登录");
R r = R.error().code(SystemConstant.JWT_ERRCODE_NULL).message("token为空");
// 向前端发送相应结果
print(response,r);
return false;
}else {
CheckResult checkResult = JwtUtils.validateJWT(token);
if(checkResult.isSuccess()){
return true;
}else{
switch (checkResult.getErrCode()){
case SystemConstant.JWT_ERRCODE_FAIL:{
System.out.println("token校验不通过");
R r = R.error().code(SystemConstant.JWT_ERRCODE_FAIL).message("token校验不通过");
// 向前端发送相应结果
print(response,r);
return false;
}
case SystemConstant.JWT_ERRCODE_EXPIRE:{
System.out.println("token校验不通过");
R r = R.error().code(SystemConstant.JWT_ERRCODE_EXPIRE).message("token已过期");
// 向前端发送相应结果
print(response,r);
return false;
}
}
}
}
}
// 如果是 资源处理器 比如 ResourceHttpRequestHandler 就不走上面的if
return true;
}
public void print(HttpServletResponse response,R r){
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.println(r);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("后置拦截,执行了controller 中的方法之后 响应视图之前 会执行这个方法");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("整个请求执行完毕 可以做一些收尾工作,如果请求过程中出现异常 这里也可以得到异常信息");
}
}
配置类中注册拦截器
package com.glls.phoneservice.config;
import com.glls.phoneservice.interceptor.MyInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @date 2023/7/10
* @desc
*/
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
//sb 配置 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 实现WebMvcConfigurer不会导致静态资源被拦截
registry.addInterceptor(new MyInterceptor())
// 放行 某些请求路径
.excludePathPatterns("/webjars/**","/swagger-resources/**","/doc.html")
//拦截 路径
.addPathPatterns("/**");
}
}