本节代码开源地址
用户登录后端(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-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-20210212123532258
4.重启查看页面