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


目录
相关文章
|
4天前
|
XML JavaScript 前端开发
springboot配合Freemark模板生成word,前台vue接收并下载【步骤详解并奉上源码】
springboot配合Freemark模板生成word,前台vue接收并下载【步骤详解并奉上源码】
|
3天前
|
前端开发 JavaScript Java
Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)五(前端页面
Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)五(前端页面
Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)五(前端页面
|
4天前
|
JavaScript Java 关系型数据库
基于springboot+vue+Mysql的交流互动系统
简化操作,便于维护和使用。
16 2
|
5天前
|
开发框架 监控 Java
深入探索Spring Boot的监控、管理和测试功能及实战应用
【5月更文挑战第14天】Spring Boot是一个快速开发框架,提供了一系列的功能模块,包括监控、管理和测试等。本文将深入探讨Spring Boot中监控、管理和测试功能的原理与应用,并提供实际应用场景的示例。
17 2
|
5天前
|
JSON JavaScript Java
从前端Vue到后端Spring Boot:接收JSON数据的正确姿势
从前端Vue到后端Spring Boot:接收JSON数据的正确姿势
26 0
|
5天前
|
资源调度 JavaScript 前端开发
【vue】vue中的路由vue-router,vue-cli脚手架详细使用教程
【vue】vue中的路由vue-router,vue-cli脚手架详细使用教程
|
5天前
|
前端开发 NoSQL 数据库
切图仔做全栈:React&Nest.js社区平台(一)——基础架构与邮箱注册、JWT登录实现
切图仔做全栈:React&Nest.js社区平台(一)——基础架构与邮箱注册、JWT登录实现
|
5天前
|
JavaScript 前端开发 BI
采用前后端分离Vue,Ant-Design技术开发的(手麻系统成品源码)适用于三甲医院
开发环境 技术架构:前后端分离 开发语言:C#.net6.0 开发工具:vs2022,vscode 前端框架:Vue,Ant-Design 后端框架:百小僧开源框架 数 据 库:sqlserver2019
28 4
采用前后端分离Vue,Ant-Design技术开发的(手麻系统成品源码)适用于三甲医院
|
5天前
|
安全 关系型数据库 MySQL
node实战——后端koa结合jwt连接mysql实现权限登录(node后端就业储备知识)
node实战——后端koa结合jwt连接mysql实现权限登录(node后端就业储备知识)
28 3
|
5天前
|
JavaScript 前端开发
vue3+ts+element home页面侧边栏+头部组件+路由组件组合页面教程
这是一个Vue.js组件代码示例,展示了带有侧边栏导航和面包屑导航的布局。模板中使用Element Plus组件库,包含可折叠的侧边栏,其中左侧有 Logo 和导航列表,右侧显示更具体的子菜单。`asideDisplay`控制侧边栏宽度。在`script`部分,使用Vue的响应式数据和生命周期钩子初始化路由相关数据,并从localStorage恢复状态。样式部分定义了组件的颜色、尺寸和布局。
28 1