赞
踩
目录
1.1 什么是jwt
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间以安全的方式传输信息。JWT 是一种紧凑、自包含的信息载体,可以被解码和验证。它通常用于身份验证和授权服务,特别是在无状态的 Web 应用程序中,比如那些基于 REST 的 API。
1.2 jwt的结构
JWT 由三部分组成,每一部分都由点号(.)分隔开:
头部 (Header): 包含关于类型和签名算法的信息。例如:
{"alg":"HS256","typ":"JWT"}
这个头部通常表明使用 HMAC SHA-256 算法签名。
负载 (Payload): 也称为“声明”(Claims),包含了要传输的信息。这些信息可以是任意的 JSON 数据,但通常包括一些标准的字段,例如:
{"sub":"1234567890","name":"John Doe","admin":true}
这里 "sub"
是主题(Subject),"name"
是姓名,"admin"
是权限声明。
签名 (Signature): 用于验证数据的完整性和确认发送者的身份。签名是通过一个密钥对头部和负载进行加密得到的。
- HMACSHA256(
- base64UrlEncode(header) + "." +
- base64UrlEncode(payload),
- secret
- )
如果使用非对称加密,则密钥可以是公钥或私钥。
1.3 jwt工作流程
创建和签发: 服务器创建一个 JWT,其中包含用户的身份信息和/或其他数据,然后使用一个秘密密钥或私钥对其进行签名。
传输: JWT 通过网络发送给客户端,通常作为 HTTP Authorization header 的一部分。
验证和使用: 当客户端向服务器发送请求时,它将 JWT 作为身份验证的一部分。服务器验证 JWT 的签名,以确保它没有被篡改,并从中读取信息。
过期: JWT 可以设置一个过期时间,在此之后,它将不再有效。
下面将通过Vue + SpringBoot 实现一个jwt鉴权的项目
2.1 引入axios
npm install axios
通过添加前端拦截器配置axios
在src下创建一个utils包,再创建一个axios.js文件
- import axios from 'axios';
-
- // 创建axios实例
- const instance = axios.create();
-
- // 添加请求拦截器
- instance.interceptors.request.use(
- function (config) {
- // 在这里添加token到请求头
- const token = localStorage.getItem('token') || ''; // 从本地存储获取token
- if (token) {
- config.headers.Authorization = `${token}`;
- }
- return config;
- },
- function (error) {
- // 请求错误时的处理
- return Promise.reject(error);
- }
- );
-
- export default instance;
在main.js中配置应用axios
- import axios from './utils/axios';
-
- Vue.prototype.$axios = axios;
2.3 使用axios
在配置全局后,使用axios就并不需要单独引入axios了,直接使用this.$axios即可调用
- this.$axios.get('/api/forum/getAllForumPost', {
- params: {
- pageSize: 1,
- pageNumber: 10
- }
- }).then((response) => {
- console.log(response.data.data);
-
- this.posts = response.data.data;
- });
创建一个TestView.vue测试发送请求时候是否会携带请求头
- <template>
- <div>
-
- <!-- 测试是否会携带请求头 -->
- <button @click="Test"> 发送测试</button>
-
- </div>
- </template>
-
- <script>
- export default {
- data() {
- return {
- };
- },
- methods: {
- Test(){
- // 假设有登录成功后的token
- localStorage.setItem('token', '1234567890');
-
- this.$axios.get('/api/Test').then((response) => {
- console.log(response.data.data);
- });
- }
- },
-
- };
- </script>
在控制台的网络中查看是否有对应的请求头
已经成功携带,并且名称为Authorization
3.1 引入依赖
- <!-- JWT依赖-->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
-
- <dependency>
- <groupId>javax.xml.bind</groupId>
- <artifactId>jaxb-api</artifactId>
- <version>2.3.0</version>
- </dependency>
3.3 由于jwt需要三个属性 密钥 有效期 Token的名称
所以需要配置对应的资源类
- @Component
- @ConfigurationProperties(prefix = "paitool.jwt")
- @Data
- public class JwtProperties {
-
- private String SecretKey;
- private long Ttl;
- private String TokenName;
-
-
- }
application.yml:
- paitool:
- jwt:
- secret-key: Alphamilk
- ttl: 10800000
- token-name: Authorization
-
3.4 创建配置Jwt的工具类 实现快速创建Jwt与解密Jwt方法
- public class JwtUtil {
- /**
- * 生成jwt
- * 使用Hs256算法, 私匙使用固定秘钥
- *
- * @param secretKey jwt秘钥
- * @param ttlMillis jwt过期时间(毫秒)
- * @param claims 设置的信息
- * @return
- */
- public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
- // 指定签名的时候使用的签名算法,也就是header那部分
- SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
-
- // 生成JWT的时间
- long expMillis = System.currentTimeMillis() + ttlMillis;
- Date exp = new Date(expMillis);
-
- // 设置jwt的body
- JwtBuilder builder = Jwts.builder()
- // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
- .setClaims(claims)
- // 设置签名使用的签名算法和签名使用的秘钥
- .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
- // 设置过期时间
- .setExpiration(exp);
-
- return builder.compact();
- }
-
- /**
- * Token解密
- *
- * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
- * @param token 加密后的token
- * @return
- */
- public static Claims parseJWT(String secretKey, String token) {
- // 得到DefaultJwtParser
- Claims claims = Jwts.parser()
- // 设置签名的秘钥
- .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
- // 设置需要解析的jwt
- .parseClaimsJws(token).getBody();
- return claims;
- }
-
- }
3.5 通过ThreadLocal实现后端存储用户信息
- public class BaseContext {
-
- public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
-
- public static void setCurrentId(Long id) {
- threadLocal.set(id);
- }
-
- public static Long getCurrentId() {
- return threadLocal.get();
- }
-
- public static void removeCurrentId() {
- threadLocal.remove();
- }
-
- }
3.6 配置jwt的拦截器
注意:这里的HandlerMehtod是org.springframework.web.method包下的
- @Component
- @Slf4j
- public class JwtTokenInterceptor implements HandlerInterceptor {
-
- @Autowired
- private JwtProperties jwtProperties;
-
- /**
- * 校验jwt
- *
- * @param request
- * @param response
- * @param handler
- * @return
- * @throws Exception
- */
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
-
-
- //判断当前拦截到的是Controller的方法还是其他资源
- if (!(handler instanceof HandlerMethod)) {
- //当前拦截到的不是动态方法,直接放行
- return true;
- }
-
- //1、从请求头中获取令牌
- String token = request.getHeader(jwtProperties.getTokenName());
-
- //2、校验令牌
- try {
- log.info("jwt校验:{}", token);
- Claims claims = JwtUtil.parseJWT(jwtProperties.getSecretKey(), token);
-
- // 获取JWT的过期时间并转换为可读格式
-
- Date expirationDate = claims.getExpiration();
-
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- String formattedExpiration = sdf.format(expirationDate);
- log.info("JWT过期时间:{}", formattedExpiration);
-
-
- Long userId = Long.valueOf(claims.get("userId").toString());
- log.info("当前用户id:", userId);
-
- //通过ThreadLocal保存员工id
- BaseContext.setCurrentId(userId);
- //3、通过,放行
-
- return true;
- } catch (Exception ex) {
- //4、不通过,响应401状态码
- response.setStatus(401);
- return false;
- }
- }
- }
3.7 将配置好的拦截器加入到webMvc配置中(由于本次实战通过用户登陆获取token,记得排除用户登陆时候进行校验的过程)
- @Configuration
- @Slf4j
- public class WebMvcConfig extends WebMvcConfigurationSupport {
-
-
- @Autowired
- private JwtTokenInterceptor jwtTokenInterceptor;
-
-
- @Override
- protected void addInterceptors(InterceptorRegistry registry) {
- log.info("开始注册自定义拦截器...");
- registry.addInterceptor(jwtTokenInterceptor)
- .addPathPatterns("/**")
- .excludePathPatterns("/user/login")
- .excludePathPatterns("/user/GetCaptcha");
-
-
-
- }
1.创建User表单
- create table paitool.user
- (
- id int auto_increment
- primary key,
- account varchar(255) not null,
- password varchar(255) not null,
- phone varchar(20) null,
- address varchar(255) null,
- isVip tinyint(1) default 0 null,
- email varchar(255) null,
- registration_date datetime default CURRENT_TIMESTAMP null,
- last_login datetime null,
- status enum ('active', 'inactive') default 'active' null,
- constraint account_UNIQUE
- unique (account),
- constraint email_UNIQUE
- unique (email),
- constraint phone_UNIQUE
- unique (phone)
- );
通过MyBatisPlusX自动生成架构
2.创建返回结果实体类
- //结果类
- public class Result<T> {
- // 状态码常量
- public static final int SUCCESS = 200;
- public static final int ERROR = 500;
-
- private int code; // 状态码
- private String message; // 消息
- private T data; // 数据
-
- // 构造函数,用于创建成功的结果对象
- private Result(int code, String message, T data) {
- this.code = code;
- this.message = message;
- this.data = data;
- }
-
- // 成功结果的静态方法
- public static <T> Result<T> success(T data) {
- return new Result<>(SUCCESS, "Success", data);
- }
-
-
- // 错误结果的静态方法
- public static <T> Result<T> error(String message) {
- return new Result<>(ERROR, message, null);
- }
-
- // 错误结果的静态方法,可以传入自定义的状态码
- public static <T> Result<T> error(int code, String message) {
- return new Result<>(code, message, null);
- }
-
- // 获取状态码
- public int getCode() {
- return code;
- }
-
- // 设置状态码
- public void setCode(int code) {
- this.code = code;
- }
-
- // 获取消息
- public String getMessage() {
- return message;
- }
-
- // 设置消息
- public void setMessage(String message) {
- this.message = message;
- }
-
- // 获取数据
- public T getData() {
- return data;
- }
-
- // 设置数据
- public void setData(T data) {
- this.data = data;
- }
-
- // 用于转换为Map类型的方法,方便序列化为JSON
- public Map<String, Object> toMap() {
- Map<String, Object> map = new HashMap<>();
- map.put("code", code);
- map.put("message", message);
- map.put("data", data);
- return map;
- }
- }
3.创建验证码(防止密码爆破)工具类 与 Md5加密与解密工具类(防止数据库密码信息泄露)
- public class CaptchaUtil {
-
- private static final int WIDTH = 200;
- private static final int HEIGHT = 75;
- private static final int FONT_SIZE = 36;
- private static final String DEFAULT_FONT = "Arial";
-
- /**
- * 生成验证码图像.
- *
- * @param captchaText 验证码原始文本
- * @return Base64编码的图像字符串
- */
- public static String generateCaptchaImage(String captchaText) {
- if (captchaText == null || captchaText.isEmpty()) {
- throw new IllegalArgumentException("Captcha text cannot be null or empty.");
- }
-
- // 创建图像和图形上下文
- BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
- Graphics2D g = (Graphics2D) image.getGraphics();
-
- // 设置背景颜色
- g.setColor(Color.WHITE);
- g.fillRect(0, 0, WIDTH, HEIGHT);
-
- // 绘制验证码文本
- g.setFont(new Font(DEFAULT_FONT, Font.BOLD, FONT_SIZE));
- g.setColor(getRandomColor());
- g.drawString(captchaText, 45, 50);
-
- // 添加随机线条作为干扰
- addNoiseLines(g);
-
- // 关闭图形上下文
- g.dispose();
-
- // 将图像转换为Base64编码的字符串
- try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
- ImageIO.write(image, "png", baos);
- return Base64.getEncoder().encodeToString(baos.toByteArray());
- } catch (Exception e) {
- throw new RuntimeException("Error generating captcha image", e);
- }
- }
-
- private static void addNoiseLines(Graphics2D g) {
- for (int i = 0; i < 5; i++) {
- g.setColor(getRandomColor());
- g.drawLine(
- getRandomNumber(WIDTH),
- getRandomNumber(HEIGHT),
- getRandomNumber(WIDTH),
- getRandomNumber(HEIGHT)
- );
- }
- }
-
- private static Color getRandomColor() {
- return new Color((int) (Math.random() * 255),
- (int) (Math.random() * 255),
- (int) (Math.random() * 255));
- }
-
- private static int getRandomNumber(int bound) {
- return (int) (Math.random() * bound);
- }
- }
- public final class MD5Util {
-
- /**
- * 使用MD5算法对字符串进行加密。
- *
- * @param input 待加密的字符串
- * @return 加密后的MD5散列值字符串
- */
- public static String encryptToMD5(String input) {
- try {
- MessageDigest md = MessageDigest.getInstance("MD5");
- byte[] hashInBytes = md.digest(input.getBytes());
-
- // 将字节数组转换成十六进制字符串
- StringBuilder sb = new StringBuilder();
- for (byte b : hashInBytes) {
- sb.append(String.format("%02x", b));
- }
- return sb.toString();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("MD5 algorithm not found", e);
- }
- }
-
- public static void main(String[] args) {
- String originalString = "Hello World";
- String encryptedString = encryptToMD5(originalString);
- System.out.println("Original: " + originalString);
- System.out.println("Encrypted: " + encryptedString);
- }
- }
4.创建数据传输与视图的实体类
登陆时候,前端传入数据
- @Data
- public class LoginDTO {
-
- private String account;
-
- private String password;
-
- // 验证码
- private String captcha;
-
-
- }
验证通过后传给前端的数据
- @Data
- public class loginVo {
-
- private Integer id;
-
- private String account;
-
- private Integer isvip;
-
- private Object status;
-
- private String token;
-
-
- }
4.UserController实现登陆功能
- @RestController
- @Slf4j
- @RequestMapping("/user")
- public class UserController {
-
- @Autowired
- UserService userService;
-
-
- @Autowired
- private JwtProperties jwtProperties;
-
- // 登陆时候获取验证码
- @ApiOperation("获取验证码功能")
- @GetMapping("/GetCaptcha")
- public String GetCaptcha(HttpSession session) {
-
- // 随机生成四位验证码原始数据
- String allowedChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
- String randomString = generateRandomString(allowedChars, 4);
- System.out.println("captchaCode " + randomString);
-
- // 将验证码保存到session中
- session.setAttribute("captcha", randomString); // 使用方法参数session
- String ImageByBase64 = CaptchaUtil.generateCaptchaImage(randomString);
- return ImageByBase64;
- }
-
-
- // 实现登陆功能
- @ApiOperation("用户登陆功能")
- @PostMapping("/login")
- public Result<loginVo> Login(@RequestBody LoginDTO loginDTO, HttpSession session) { // 使用同一个HttpSession参数
-
- String captcha = (String) session.getAttribute("captcha");
-
- log.info("用户调用login方法");
- if (loginDTO.getCaptcha() == null || !loginDTO.getCaptcha().equalsIgnoreCase(captcha)) {
- session.removeAttribute("captcha");
- return Result.error("验证码出错了噢!");
- }
-
- // 对密码进行md5加密
- String encryptToMD5 = MD5Util.encryptToMD5(loginDTO.getPassword());
-
- LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
- lambdaQueryWrapper.eq(User::getAccount, loginDTO.getAccount())
- .eq(User::getPassword, encryptToMD5);
-
- User user = userService.getOne(lambdaQueryWrapper);
- if (user == null) {
- return Result.error("很抱歉,查不到此用户");
- }
- loginVo loginVo = new loginVo();
- BeanUtils.copyProperties(user,loginVo);
- Map<String,Object> claims = new HashMap<>();
- claims.put("userId",user.getId());
- String token = JwtUtil.createJWT(jwtProperties.getSecretKey(), jwtProperties.getTtl(), claims);
- loginVo.setToken(token);
-
- return Result.success(loginVo);
- }
- }
前端账户操作View.vue:
- <template>
- <div id="Header">
- <h3>--PaiTool--</h3>
-
- <div class="header-avatar">
- <el-popover placement="bottom" :visible-arrow="false" :visible.sync="showUserInfo">
- <div class="userInfo">
- <p>用户名:{{ account }}</p>
- <p>邮箱:{{ email }}</p>
- <p>是否是vip: {{ isVip }}</p>
- <p>账号状态:{{ status }}</p>
-
- <!-- 登录按钮 -->
- <el-button type="primary" @click="showDialog">登录/注册</el-button>
- <!-- 退出按钮 -->
- <el-button type="text" @click="confirmQuit">退出</el-button>
-
- <!-- 登录对话框 -->
- <el-dialog title="登录与注册" :visible.sync="dialogLoginVisible" width="30%" @close="resetLoginForm" append-to-body
- :modal-append-to-body="false">
-
- <el-tabs v-model="activeName" @tab-click="handleClick">
-
- <el-tab-pane label="登陆" name="first">
- <el-form :model="loginForm" ref="loginFormRef" label-width="80px">
- <el-form-item label="用户名:">
- <el-input v-model="loginForm.account"></el-input>
- </el-form-item>
- <el-form-item label="密码:">
- <el-input v-model="loginForm.password" show-password></el-input>
- </el-form-item>
- <el-form-item label="验证码">
- <el-input v-model="loginForm.captcha" style="width: 20%;"></el-input>
- <img :src="captchaImageUrl" alt="验证码" @click="refreshCaptcha" id="captchaImage">
- </el-form-item>
- </el-form>
- </el-tab-pane>
-
- <el-tab-pane label="注册" name="second">
- <el-form :model="loginForm" ref="registerFormRef" label-width="80px">
- <el-form-item label="注册用户:">
- <el-input v-model="registerFormRef.account"></el-input>
- </el-form-item>
- <el-form-item label="注册密码:">
- <el-input v-model="registerFormRef.password" show-password></el-input>
- </el-form-item>
- <el-form-item label="验证码">
- <el-input v-model="registerFormRef.captcha" style="width: 20%;"></el-input>
- <img :src="captchaImageUrl" alt="验证码" @click="refreshCaptcha" id="captchaImage">
- </el-form-item>
- </el-form>
-
-
- </el-tab-pane>
-
- </el-tabs>
- <span slot="footer" class="dialog-footer">
- <el-button @click="dialogLoginVisible = false">取消</el-button>
- <el-button type="primary" @click="submitLogin">登录|注册</el-button>
- </span>
-
-
- </el-dialog>
-
- <!-- 退出确认对话框 -->
- <el-dialog title="确认退出" :visible.sync="dialogConfirmVisible" width="30%" @close="dialogConfirmVisible = false"
- append-to-body :modal-append-to-body="false">
- <span>您确定要退出吗?</span>
- <span slot="footer" class="dialog-footer">
- <el-button @click="dialogConfirmVisible = false">取消</el-button>
- <el-button type="primary" @click="quit">确定退出</el-button>
- </span>
-
- </el-dialog>
- </div>
- <el-avatar slot="reference" :src="circleUrl" :size="40" class="clickable-avatar"></el-avatar>
- </el-popover>
- </div>
- </div>
- </template>
-
- <script>
- import axios from 'axios';
- import Cookies from 'js-cookie';
-
- export default {
- data() {
- return {
- showUserInfo: false, // 控制个人信息弹窗的显示状态
- circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
- isVip: '否',
- account: '未登录',
- status: '正常',
- email: 'none',
- activeName: 'first',
- loginOrRegistFlag: true,
-
- dialogLoginVisible: false,
- dialogConfirmVisible: false,
- loginForm: {
- username: '',
- password: '',
- },
- registerFormRef: {
- username: '',
- password: '',
- },
- captchaImageUrl: '', // 初始化为一个空字符串
-
- }
- },
- mounted() {
- this.loadUserDataFromCookie();
- },
- methods: {
- loadUserDataFromCookie() {
- // 从cookie中读取account
- const account = Cookies.get('account');
- if (account) {
- this.account = account;
- }
-
-
- // 从cookie中读取isVip
- const isVip = Cookies.get('isVip');
- if (isVip !== undefined) {
- // 注意:从cookie读取的数据是字符串类型,需要转换成布尔型
- this.isVip = isVip === 'true';
- }
-
-
- // 从cookie中读取status
- const status = Cookies.get('status');
- if (status) {
- this.status = status;
- }
-
-
- // 从cookie中读取email
- const email = Cookies.get('email');
- if (email) {
- this.email = email;
- }
-
- },
- // 打开登录对话框
- open() {
- this.dialogLoginVisible = true;
- },
-
- resetLoginForm() {
- this.$refs.loginFormRef.resetFields();
- },
- // 提交登录
- submitLogin() {
-
- // 判断是注册还是登录
- if (this.loginOrRegistFlag == true) {
-
- // 这里添加验证逻辑(如果需要)
- console.log('登录表单提交:', this.loginForm);
-
- this.dialogLoginVisible = false;
-
- // 将this.loginForm作为参数上传
- axios.post("/api/user/login", this.loginForm)
- .then(response => {
- console.log(response.data);
- if (response.data.code === 500) {
- // 重新获取验证码
- this.refreshCaptcha();
-
- this.$message.error(response.data.message);
- } else if (response.data.code === 200) {
- this.$message({
- showClose: true,
- message: '登陆成功!',
- type: 'success'
- });
-
- // 设置cookie,可以设置过期时间
- Cookies.set('account', response.data.data.account, { expires: 7 });
- Cookies.set('isVip', response.data.data.isVip, { expires: 7 });
- Cookies.set('status', response.data.data.status, { expires: 7 });
- Cookies.set('email', response.data.data.email, { expires: 7 });
- Cookies.set('userId', response.data.data.id, { expires: 7 })
-
- localStorage.setItem('token', response.data.data.token);
-
-
- this.account = response.data.data.account;
- this.isVip = response.data.data.isVip;
- this.status = response.data.data.status;
- this.email = response.data.data.email;
- }
-
- })
- .catch(error => {
- // 处理错误响应
- console.error('登录失败:', error);
- this.$message.error('登陆错了哦,这是一条错误消息')
- });
-
-
- } else {
-
- axios.post('/api/user/register', this.registerFormRef).then(response => {
- if (response.data.code === 200) {
- this.$message({
- showClose: true,
- message: '注册成功!',
- type: 'success'
- });
-
- this.dialogLoginVisible = false;
- } else {
- this.$message.error(response.data.message);
- }
-
- });
-
- }
-
- },
- // 打开退出确认对话框
- confirmQuit() {
- this.dialogConfirmVisible = true;
- },
- // 执行退出操作
- quit() {
- // 这里执行实际的退出逻辑
- console.log('执行退出操作');
- this.dialogConfirmVisible = false;
-
- // 将Cookie所有字段删除
- Cookies.remove('account');
- Cookies.remove('isVip');
- Cookies.remove('status');
- Cookies.remove('email');
- Cookies.remove('userId');
- this.account = '未登录';
- this.isVip = '否';
- this.status = '离线';
- this.email = 'none';
-
- this.$message({
- showClose: true,
- message: '退出成功!',
- type: 'success'
- });
-
- },
-
-
- // 刷新验证码的示例函数
- refreshCaptcha() {
- // 实现刷新验证码的逻辑
- console.log('刷新验证码');
- this.fetchCaptcha();
- },
- fetchCaptcha() {
- axios.get('/api/user/GetCaptcha')
- .then(response => {
-
- this.captchaImageUrl = 'data:image/png;base64,' + response.data;
- })
- .catch(error => {
- console.error('获取验证码失败:', error);
- });
- },
- showDialog() {
- this.fetchCaptcha(); // 先获取验证码
- this.dialogLoginVisible = true; // 然后显示登录对话框
- },
- handleClick(tab) {
-
- if (tab.name === 'first') {
- this.loginOrRegistFlag = true;
- } else {
- this.loginOrRegistFlag = false;
- }
-
- }
-
- }
- }
- </script>
-
- <style scoped>
- h3 {
- color: #E9EEF3;
- float: left;
- width: 1307px;
- height: 60px;
- margin-left: 15%;
- }
-
- .header-avatar {
- position: relative;
- /* 为绝对定位的子元素提供上下文 */
- float: right;
- z-index: 1000;
- /* 设置一个较高的 z-index 值以确保其位于其他元素之上 */
- margin-top: 10px;
- }
-
- .clickable-avatar {
- /* 添加点击手势效果 */
- cursor: pointer;
- }
-
- .userInfo {
- text-align: left;
- padding: 10px;
- }
-
- #captchaImage {
- cursor: pointer;
- width: 136px;
- height: 45px;
- border: 1px solid black;
- float: right;
- margin-right: 54%;
- }
- </style>
数据库创建用户与(123456)加密后的密码
account: admin
password: e10adc3949ba59abbe56e057f20f883e
进入前端并进行登陆
查看返回结果的token,前端的login函数已经自动存入了token中了
使用其它功能,查看是否有效
这里看到,后端正常识别到并解析出来了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。