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


目录
相关文章
|
3月前
|
存储 JavaScript 前端开发
前端开发:Vue.js入门与实战
【10月更文挑战第9天】前端开发:Vue.js入门与实战
|
3月前
|
JavaScript 前端开发
Vue开发必备:$nextTick方法的理解与实战场景
Vue开发必备:$nextTick方法的理解与实战场景
230 1
|
2月前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
52 0
|
2月前
|
JavaScript UED
"Vue实战技巧大揭秘:一招解决路由跳转页面不回顶部难题,让你的单页面应用用户体验飙升!"
【10月更文挑战第23天】在Vue单页面应用中,点击路由跳转时,默认情况下页面不会自动滚动到顶部,这可能影响用户体验。本文通过一个新闻网站的案例,介绍了如何使用Vue-router的全局前置守卫和`scrollBehavior`方法,实现路由跳转时页面自动滚动到顶部的功能,提升用户浏览体验。
111 0
|
3月前
|
存储 JSON 算法
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)
文章介绍了JWT令牌的基础教程,包括其应用场景、组成部分、生成和校验方法,并在Springboot中使用JWT技术体系完成拦截器的实现。
150 0
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)
|
4月前
|
JavaScript 前端开发 UED
组件库实战 | 用vue3+ts实现全局Header和列表数据渲染ColumnList
该文章详细介绍了如何使用Vue3结合TypeScript来开发全局Header组件和列表数据渲染组件ColumnList,并提供了从设计到实现的完整步骤指导。
|
5月前
|
JavaScript
Vue学习之--------消息订阅和发布、基础知识和实战应用(2022/8/24)
这篇文章介绍了Vue中消息订阅和发布的机制,作为组件间通信的一种方式,特别适用于任意组件间的通信。文章通过基础知识讲解和具体的代码实例,展示了如何使用`pubsub-js`库来安装、订阅、发布消息,并在组件销毁前取消订阅以避免内存泄漏。
Vue学习之--------消息订阅和发布、基础知识和实战应用(2022/8/24)
|
5月前
|
JavaScript 前端开发 开发者
Vue学习之--------深入理解Vuex、原理详解、实战应用(2022/9/1)
这篇文章详细介绍了Vuex的基本概念、使用场景、安装配置、基本用法、实际应用案例以及注意事项,通过一个数字累加器的实战示例,帮助开发者深入理解Vuex的原理和应用。
|
5月前
|
JavaScript
如何通过点击商品的信息(图片或者文字)跳转到更加详细的商品信息介绍(前后端分离之Vue实现)
该博客文章介绍了如何在Vue 2框架下实现前后端分离的商品信息展示和详情页跳转,包括排序筛选、详情展示、加入购物车和分享功能。
如何通过点击商品的信息(图片或者文字)跳转到更加详细的商品信息介绍(前后端分离之Vue实现)
|
5月前
|
JavaScript
Vue中组件化编码使用(实战练习一)
这篇文章是关于Vue中组件化编码的实战练习,介绍了组件化编码的流程、组件间参数传递的方法以及如何通过组件配合完成一个需求。内容包括了组件拆分、动态组件实现、交互绑定事件的步骤,以及使用props进行父子组件通信的注意事项。文章还提供了一个待办事项列表的静态页面效果和相关代码示例,包括TheFooter.vue、TheHeader.vue、TheItem.vue、TheList.vue和App.vue等组件的代码。
Vue中组件化编码使用(实战练习一)

热门文章

最新文章