SpringBoot+Vue豆寶社區(qū)前后端分離項目手把手實(shí)戰(zhàn)系列教程08---用戶登錄功能jwt實(shí)現(xiàn)

本節(jié)代碼開源地址

代碼地址

用戶登錄后端(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("用戶名或郵箱不存在");
    }
    // 校驗(yàn)密碼
    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創(chuàng)建auth.js

import Cookies from 'js-cookie'

// 存放token
const uToken = 'u_token'
// 存放白天還是黑夜模式
const darkMode = 'dark_mode';

// 獲取Token
export function getToken() {
    return Cookies.get(uToken);
}

// 設(shè)置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創(chuàng)建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創(chuàng)建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的全部內(nèi)容

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

登錄歡迎側(cè)邊欄

1.veiws/card/LoginWelcome.vue

復(fù)制一下內(nèi)容替換

<template>
  <el-card class="box-card" shadow="never">
    <div slot="header">
      <span>?? 發(fā)帖</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>? 發(fā)表想法</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"> 社區(qū)登入</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/創(chuàng)建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.重啟查看頁面

image-20210212124047884
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逗抑,隨后出現(xiàn)的幾起案子吹艇,更是在濱河造成了極大的恐慌,老刑警劉巖蝴蜓,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異俺猿,居然都是意外死亡茎匠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門押袍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诵冒,“玉大人,你說我怎么就攤上這事谊惭∑觯” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵圈盔,是天一觀的道長豹芯。 經(jīng)常有香客問我,道長驱敲,這世上最難降的妖魔是什么铁蹈? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮癌佩,結(jié)果婚禮上木缝,老公的妹妹穿的比我還像新娘。我一直安慰自己围辙,他們只是感情好我碟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著姚建,像睡著了一般矫俺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天厘托,我揣著相機(jī)與錄音友雳,去河邊找鬼。 笑死铅匹,一個胖子當(dāng)著我的面吹牛押赊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播包斑,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼流礁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了罗丰?” 一聲冷哼從身側(cè)響起神帅,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萌抵,沒想到半個月后找御,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绍填,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年霎桅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沐兰。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡哆档,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出住闯,到底是詐尸還是另有隱情,我是刑警寧澤澳淑,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布比原,位于F島的核電站,受9級特大地震影響杠巡,放射性物質(zhì)發(fā)生泄漏量窘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一氢拥、第九天 我趴在偏房一處隱蔽的房頂上張望蚌铜。 院中可真熱鬧,春花似錦嫩海、人聲如沸冬殃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽审葬。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涣觉,已是汗流浹背痴荐。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留官册,地道東北人生兆。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像膝宁,于是被迫代替她去往敵國和親鸦难。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容