SpringBoot+Vue豆宝社区前后端分离项目手把手实战系列教程08---用户登录功能jwt实现

简介: SpringBoot+Vue豆宝社区前后端分离项目手把手实战系列教程08---用户登录功能jwt实现

本节代码开源地址


代码地址


用户登录后端(JWT)


0.JwtUtil

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.*;
public class JwtUtil {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
    public static final long EXPIRATION_TIME = 3600_000_000L; // 1000 hour
    public static final String SECRET = "ThisIsASecret";//please change to your own encryption secret.
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String HEADER_STRING = "Authorization";
    public static final String USER_NAME = "userName";
    public static String generateToken(String userId) {
        HashMap<String, Object> map = new HashMap<>();
        //you can put any data in the map
        map.put(USER_NAME, userId);
        String jwt = Jwts.builder()
                .setClaims(map)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
        return jwt; //jwt前面一般都会加Bearer
    }
    public static HttpServletRequest validateTokenAndAddUserIdToHeader(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            try {
                Map<String, Object> body = Jwts.parser()
                        .setSigningKey(SECRET)
                        .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                        .getBody();
                return new CustomHttpServletRequest(request, body);
            } catch (Exception e) {
                logger.info(e.getMessage());
                throw new TokenValidationException(e.getMessage());
            }
        } else {
            throw new TokenValidationException("Missing token");
        }
    }
    public static class CustomHttpServletRequest extends HttpServletRequestWrapper {
        private Map<String, String> claims;
        public CustomHttpServletRequest(HttpServletRequest request, Map<String, ?> claims) {
            super(request);
            this.claims = new HashMap<>();
            claims.forEach((k, v) -> this.claims.put(k, String.valueOf(v)));
        }
        @Override
        public Enumeration<String> getHeaders(String name) {
            if (claims != null && claims.containsKey(name)) {
                return Collections.enumeration(Arrays.asList(claims.get(name)));
            }
            return super.getHeaders(name);
        }
    }
    static class TokenValidationException extends RuntimeException {
        public TokenValidationException(String msg) {
            super(msg);
        }
    }
}

1.dto

@Data
public class LoginDTO {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 15, message = "登录用户名长度在2-15")
    private String username;
    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 20, message = "登录密码长度在6-20")
    private String password;
    private Boolean rememberMe;
}

2.UmsUserController

@PostMapping("/login")
public ApiResult login(@Valid @RequestBody LoginDTO loginDTO) {
    Map<String, String> map = umsUserService.login(loginDTO);
    return ApiResult.success(map, "登录成功");
}

3.UmsUserService

/**
 * 登录
 *
 * @param loginDTO
 * @return
 */
public Map<String, String> login(LoginDTO loginDTO) {
    // 邮箱或用户名是否存在
    String loginUserName = loginDTO.getUsername();
    UmsUser umsUser = this.getOne(new LambdaQueryWrapper<UmsUser>()
            .eq(UmsUser::getUsername, loginUserName)
            .or()
            .eq(UmsUser::getEmail, loginUserName));
    if (ObjectUtils.isEmpty(umsUser)) {
        ApiAsserts.fail("用户名或邮箱不存在");
    }
    // 校验密码
    if (!MD5Utils.getPwd(loginDTO.getPassword()).equals(umsUser.getPassword())) {
        ApiAsserts.fail("密码错误,请重新输入");
    }
    // 生成 token
    String token = JwtUtil.generateToken(loginUserName);
    HashMap<String, String> map = new HashMap<>(16);
    map.put("token",token);
    return map;
}


用户登录前端


1.安装js-cookie

存放浏览器的Cookies

yarn add js-cookie

2.src/util创建auth.js

import Cookies from 'js-cookie'
// 存放token
const uToken = 'u_token'
// 存放白天还是黑夜模式
const darkMode = 'dark_mode';
// 获取Token
export function getToken() {
    return Cookies.get(uToken);
}
// 设置Token,1天,与后端同步
export function setToken(token) {
    return Cookies.set(uToken, token, {expires: 1})
}
// 删除Token
export function removeToken() {
    return Cookies.remove(uToken)
}
export function removeAll() {
    return Cookies.Cookies.removeAll()
}
export function setDarkMode(mode) {
    return Cookies.set(darkMode, mode, {expires: 365})
}
export function getDarkMode() {
    return !(undefined === Cookies.get(darkMode) || 'false' === Cookies.get(darkMode));
}

3.登录路由

src/router/index.js添加路由

,{
    path: '/login',
    name: 'login',
    component: () => import('@/views/auth/login'),
    meta: {title: '登录'}
  }

4./views/auth创建login.vue

<template>
  <div class="columns py-6">
    <div class="column is-half is-offset-one-quarter">
      <el-card shadow="never">
        <div slot="header" class="has-text-centered has-text-weight-bold">
          用户登录
        </div>
        <div>
          <el-form
            v-loading="loading"
            :model="ruleForm"
            status-icon
            :rules="rules"
            ref="ruleForm"
            label-width="100px"
            class="demo-ruleForm"
          >
            <el-form-item label="账号" prop="name">
              <el-input v-model="ruleForm.name"></el-input>
            </el-form-item>
            <el-form-item label="密码" prop="pass">
              <el-input
                type="password"
                v-model="ruleForm.pass"
                autocomplete="off"
              ></el-input>
            </el-form-item>
            <el-form-item label="记住" prop="delivery">
              <el-switch v-model="ruleForm.rememberMe"></el-switch>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="submitForm('ruleForm')"
                >提交</el-button
              >
              <el-button @click="resetForm('ruleForm')">重置</el-button>
            </el-form-item>
          </el-form>
        </div>
      </el-card>
    </div>
  </div>
</template>
<script>
export default {
  name: "Login",
  data() {
    return {
      redirect: undefined,
      loading: false,
      ruleForm: {
        name: "",
        pass: "",
        rememberMe: true,
      },
      rules: {
        name: [
          { required: true, message: "请输入账号", trigger: "blur" },
          {
            min: 2,
            max: 15,
            message: "长度在 2 到 15 个字符",
            trigger: "blur",
          },
        ],
        pass: [
          { required: true, message: "请输入密码", trigger: "blur" },
          {
            min: 6,
            max: 20,
            message: "长度在 6 到 20 个字符",
            trigger: "blur",
          },
        ],
      },
    };
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.loading = true;
          this.$store
            .dispatch("user/login", this.ruleForm)
            .then(() => {
              this.$message({
                message: "恭喜你,登录成功",
                type: "success",
                duration: 2000,
              });
              setTimeout(() => {
                this.loading = false;
                this.$router.push({ path: this.redirect || "/" });
              }, 0.1 * 1000);
            })
            .catch(() => {
              this.loading = false;
            });
        } else {
          return false;
        }
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    },
  },
};
</script>
<style scoped>
</style>

5.API请求地址

// 登录
export function login(data) {
    return request({
      url: '/auth/user/login',
      method: 'post',
      data
    })
  }

6.src/store创建modules/user.js

import { getUserInfo, login, logout } from "@/api/auth/auth";
import { getToken, setToken, removeToken } from "@/utils/auth";
const state = {
  token: getToken(), // token
  user: "", // 用户对象
};
const mutations = {
  SET_TOKEN_STATE: (state, token) => {
    state.token = token;
  },
  SET_USER_STATE: (state, user) => {
    state.user = user;
  },
};
const actions = {
  // 用户登录
  login({ commit }, userInfo) {
    console.log(userInfo);
    const { name, pass, rememberMe } = userInfo;
    return new Promise((resolve, reject) => {
      login({ username: name.trim(), password: pass, rememberMe: rememberMe })
        .then((response) => {
          const { data } = response;
          commit("SET_TOKEN_STATE", data.token);
          setToken(data.token);
          resolve();
        })
        .catch((error) => {
          reject(error);
        });
    });
  },
  // 获取用户信息
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getUserInfo()
        .then((response) => {
          const { data } = response;
          if (!data) {
            commit("SET_TOKEN_STATE", "");
            commit("SET_USER_STATE", "");
            removeToken();
            resolve();
            reject("Verification failed, please Login again.");
          }
          commit("SET_USER_STATE", data);
          resolve(data);
        })
        .catch((error) => {
          reject(error);
        });
    });
  },
  // 注销
  logout({ commit, state }) {
    return new Promise((resolve, reject) => {
      logout(state.token)
        .then((response) => {
          console.log(response);
          commit("SET_TOKEN_STATE", "");
          commit("SET_USER_STATE", "");
          removeToken();
          resolve();
        })
        .catch((error) => {
          reject(error);
        });
    });
  },
};
export default {
  namespaced: true,
  state,
  mutations,
  actions,
};

7.src/store的index.js

index.js的全部内容

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
    modules: {
        user
    }
})
export default store


8.测试页面


输入正确的用户名和密码之后,在Cookies中会生成token


image.png

image-20210212121757027


登录欢迎侧边栏


1.veiws/card/LoginWelcome.vue

复制一下内容替换

<template>
  <el-card class="box-card" shadow="never">
    <div slot="header">
      <span>💐 发帖</span>
    </div>
    <div v-if="token != null && token !== ''" class="has-text-centered">
      <b-button type="is-danger" tag="router-link" :to="{path:'/post/create'}" outlined>✍ 发表想法</b-button>
    </div>
    <div v-else class="has-text-centered">
      <b-button type="is-primary" tag="router-link" :to="{path:'/register'}" outlined>马上入驻</b-button>
      <b-button type="is-danger" tag="router-link" :to="{path:'/login'}" outlined class="ml-2"> 社区登入</b-button>
    </div>
  </el-card>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
  name: 'LoginWelcome',
  computed: {
    ...mapGetters([
      'token'
    ])
  }
}
</script>
<style scoped>
</style>

2.src/store/创建getters.js

const getters = {
    token: state => state.user.token,   // token
    user: state => state.user.user,     // 用户对象
}
export default getters

3.修改src/store/index.js


image.png

image-20210212123532258


4.重启查看页面

image.png


目录
相关文章
|
XML 前端开发 Java
SpringBoot实现文件上传下载功能
本文介绍了如何使用SpringBoot实现文件上传与下载功能,涵盖配置和代码实现。包括Maven依赖配置(如`spring-boot-starter-web`和`spring-boot-starter-thymeleaf`)、前端HTML页面设计、WebConfig路径映射配置、YAML文件路径设置,以及核心的文件上传(通过`MultipartFile`处理)和下载(利用`ResponseEntity`返回文件流)功能的Java代码实现。文章由Colorful_WP撰写,内容详实,适合开发者学习参考。
1309 0
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
1014 2
|
JavaScript 安全
vue3使用ts传参教程
Vue 3结合TypeScript实现组件传参,提升类型安全与开发效率。涵盖Props、Emits、v-model双向绑定及useAttrs透传属性,建议明确声明类型,保障代码质量。
861 0
|
缓存 前端开发 Java
SpringBoot 实现动态菜单功能完整指南
本文介绍了一个动态菜单系统的实现方案,涵盖数据库设计、SpringBoot后端实现、Vue前端展示及权限控制等内容,适用于中后台系统的权限管理。
1364 1
|
安全 Java API
Spring Boot 功能模块全解析:构建现代Java应用的技术图谱
Spring Boot不是一个单一的工具,而是一个由众多功能模块组成的生态系统。这些模块可以根据应用需求灵活组合,构建从简单的REST API到复杂的微服务系统,再到现代的AI驱动应用。
1886 8
|
SQL 前端开发 Java
深入理解 Spring Boot 项目中的分页与排序功能
本文深入讲解了在Spring Boot项目中实现分页与排序功能的完整流程。通过实际案例,从Service层接口设计到Mapper层SQL动态生成,再到Controller层参数传递及前端页面交互,逐一剖析每个环节的核心逻辑与实现细节。重点包括分页计算、排序参数校验、动态SQL处理以及前后端联动,确保数据展示高效且安全。适合希望掌握分页排序实现原理的开发者参考学习。
1010 4
|
存储 Java 定位技术
SpringBoot整合高德地图完成天气预报功能
本文介绍了如何在SpringBoot项目中整合高德地图API实现天气预报功能。从创建SpringBoot项目、配置依赖和申请高德地图API开始,详细讲解了实体类设计、服务层实现(调用高德地图API获取实时与预报天气数据)、控制器层接口开发以及定时任务的设置。通过示例代码,展示了如何获取并处理天气数据,最终提供实时天气与未来几天天气预报的接口。文章还提供了测试方法及运行步骤,帮助开发者快速上手并扩展功能。
|
监控 安全 Java
Java 开发中基于 Spring Boot 3.2 框架集成 MQTT 5.0 协议实现消息推送与订阅功能的技术方案解析
本文介绍基于Spring Boot 3.2集成MQTT 5.0的消息推送与订阅技术方案,涵盖核心技术栈选型(Spring Boot、Eclipse Paho、HiveMQ)、项目搭建与配置、消息发布与订阅服务实现,以及在智能家居控制系统中的应用实例。同时,详细探讨了安全增强(TLS/SSL)、性能优化(异步处理与背压控制)、测试监控及生产环境部署方案,为构建高可用、高性能的消息通信系统提供全面指导。附资源下载链接:[https://pan.quark.cn/s/14fcf913bae6](https://pan.quark.cn/s/14fcf913bae6)。
2947 0
|
JavaScript 前端开发 Java
Spring Boot 与 Vue.js 前后端分离中的数据交互机制
本文深入探讨了Spring Boot与Vue.js在前后端分离架构下的数据交互机制。通过对比传统`model.addAttribute()`方法与RESTful API的设计,分析了两者在耦合性、灵活性及可扩展性方面的差异。Spring Boot以RESTful API提供数据服务,Vue.js借助Axios消费API并动态渲染页面,实现了职责分明的解耦架构。该模式显著提升了系统的灵活性和维护性,适用于复杂应用场景如论坛、商城系统等,为现代Web开发提供了重要参考。
1729 0