前后端分离,简单JWT登录详解

JWT登录流程

  • 前端vue+axios+router
  • 后端springboot+mybatisplus

1. 用户认证处理

@Service
@Transactional
public class AdminServiceImpl implements AdminService { @Autowired
private AdminMapper adminMapper; @Override
public ResponseResult adminLogin(Admin loginAdmin) {
// 从数据库查出用户信息
LambdaQueryWrapper<Admin> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Admin::getUsername,loginAdmin.getUsername());
Admin admin = adminMapper.selectOne(queryWrapper);
// 用户不存在,直接认证失败
if (admin == null) {
return new ResponseResult(ResponseResult.FORBIDDEN,"登录失败");
}
// 用户名与密码校验
if (admin.getUsername().equals(loginAdmin.getUsername())
&& admin.getPassword().equals(loginAdmin.getPassword())) {
// 校验成功,生成jwt返回数据
String jwt = JwtUtil.createJWT(String.valueOf(loginAdmin.getId()), 1000L * 60 * 60 * 24);
Map<String,String> map = new HashMap<>();
map.put("token",jwt);
return new ResponseResult(ResponseResult.OK,"登录成功",map);
}
// 密码校验失败
return new ResponseResult(ResponseResult.FORBIDDEN,"登录失败");
}
}

2. 前端登录

login({ username: this.username, password: this.password }).then(response => {
let data = response.data;
// 登录成功
if (data.code == 200) {
message.success(data.msg)
// 保存token到localstorage
window.localStorage.setItem("token", data.data.token)
// 用户信息保存
window.localStorage.setItem("username", this.username)
// 跳转到主页
this.$router.push("/admin");
} else {
message.error(data.msg)
}
}
);

3. 前端请求处理

  • 前端每次发出请求时,请求头要携带之前登录成功保存的token
  • 这里用axios拦截器示例
const http = axios.create({
baseURL: 'http://localhost:8080',
// timeout: 4000
}) // 配置请求拦截器
http.interceptors.request.use(
config => {
config.headers = {
// 每次请求前带上Token
token: window.localStorage.getItem("token")
}
return config;
},
err => Promise.reject(err)
); // 配置响应拦截器,主要做登录过期的处理,后端在解析token失效时返回401
http.interceptors.response.use(res => {
// 登录过期,跳转到登录页面
if (res.data.code == 401) {
message.error(res.data.msg)
router.push("/admin/login")
}
return res;
}, err => {
return Promise.reject(err)
});

4. 后端请求处理

  • 每次接收到请求,要校验token是否合法
  • 这里用拦截器示例

public class RequestCheckTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setContentType("text/html; charset=utf-8");
// OPTIONS为预检请求,直接放行
if ("OPTIONS".equals(request.getMethod().toUpperCase())) {
return true;
}
// 获取token
String token = request.getHeader("token");
// 如果没有token直接拦截,返回错误信息
if (token == null) {
// 这里直接用的jackson
ObjectMapper mapper = new ObjectMapper();
String result = mapper.writeValueAsString(new ResponseResult(ResponseResult.UNAUTHORIZED, "登录失效"));
response.getWriter().print(result);
return false;
}
try {
Claims claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
// token解析失败
e.printStackTrace();
// token不合法 拦截,返回错误信息
ObjectMapper mapper = new ObjectMapper();
String result = mapper.writeValueAsString(new ResponseResult(ResponseResult.UNAUTHORIZED, "登录失效"));
response.getWriter().print(result);
return false;
}
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}

5. 前端页面跳转处理

  • 前端在跳转页面前,应当也要校验登录状态信息
  • 这里用vue router的全局路由守卫示例
// 全局路由导航守卫
router.beforeEach((to, from) => {
// console.log(to); // 去哪儿
// console.log(from); // 从哪儿来
if (to.meta.requiresAuth == true) {
let token = getToken()
if (token == null || token == "") {
message.warning("登录失效")
router.push("/admin/login")
return false;
}
// 校验Token合法性
checkLoginState().then(response => {
let data = response.date
if (data.code == 200) {
return true
} else {
message.warning(data.msg)
router.push("/admin/login")
return false;
}
}) }
return true;
});

6. 退出登录

logout() {
// 无为而治
window.localStorage.removeItem("token")
window.localStorage.removeItem("username")
this.$router.push("/admin/login");
},

7. 本文关联代码

7.1 前端

  • request.js
import axios from 'axios'

import { getToken, message } from '@/utils'

import router from '@/router'

const http = axios.create({
baseURL: 'http://localhost:8080',
// timeout: 4000
}) // 配置请求拦截器
http.interceptors.request.use(
config => {
config.headers = {
// 每次请求前带上Token
token: getToken()
}
return config;
},
err => Promise.reject(err)
); // 配置响应拦截器
http.interceptors.response.use(res => {
// console.log("响应拦截");
// 登录过期
if (res.data.code == 401) {
message.error(res.data.msg)
router.push("/admin/login")
}
return res;
}, err => {
return Promise.reject(err)
}); export const get = (url, param) => {
return http.get(url, param)
}
export const post = (url, param) => {
return http.post(url, param)
}
export const put = (url, param) => {
return http.put(url, param)
}
export const del = (url, param) => {
return http.delete(url, param)
}
  • api.js
import axios from 'axios'
import { post,get,put,del } from './request' // 后台管理登录
export const login = param => post("/admin/login",param) // 校验登录状态
export const checkLoginState = () => get("/admin/checkLoginState")
  • utils.js
import { ElMessage } from "element-plus";

export const message = {
error: msg => {
ElMessage({
showClose: true,
message: msg,
type: "error",
});
},
success: msg => {
ElMessage({
showClose: true,
message: msg,
type: "success",
});
},
warning: msg => {
ElMessage({
showClose: true,
message: msg,
type: "warning",
});
}
}

7.2 后端

  • ResponseResult.java
public class ResponseResult {

    /**
* 表明该请求被成功地完成,所请求的资源发送到客户端
*/
public static final Integer OK = 200;
/**
* 请求要求身份验证,常见对于需要登录而用户未登录的情况。
*/
public static final Integer UNAUTHORIZED = 401;
/**
* 服务器拒绝请求,常见于机密信息或复制其它登录用户链接访问服务器的情况。
*/
public static final Integer FORBIDDEN = 403;
/**
* 服务器无法取得所请求的网页,请求资源不存在。
*/
public static final Integer NOT_FOUND = 404;
/**
* 服务器内部错误。
*/
public static final Integer SERVER_ERROR = 500; private Integer code;
private String msg = "";
private Object data = new int[0]; public ResponseResult() {
} public ResponseResult(Integer code) {
this.code = code;
} public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
} public ResponseResult(Integer code, Object data) {
this.code = code;
this.data = data;
} public ResponseResult(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
} public Integer getCode() {
return code;
} public void setCode(Integer code) {
this.code = code;
} public String getMsg() {
return msg;
} public void setMsg(String msg) {
this.msg = msg;
} public Object getData() {
return data;
} public void setData(Object data) {
this.data = data;
} @Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
  • JwtUtil.java
  • 依赖于jjwt
/**
* JWT工具类
*/
public class JwtUtil { //有效期为
public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "shfciksadasfa21435ggdlhvjanv"; public static String getUUID(){
return UUID.randomUUID().toString().replaceAll("-", "");
} /**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
} /**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
} /**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
} private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("huanyv") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
} /**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
} /**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
 <dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>

前后端分离,简单JWT登录详解的更多相关文章

  1. Spring Security OAuth2.0认证授权六:前后端分离下的登录授权

    历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...

  2. vue+springboot前后端分离实现单点登录跨域问题处理

    最近在做一个后台管理系统,前端是用时下火热的vue.js,后台是基于springboot的.因为后台系统没有登录功能,但是公司要求统一登录,登录认证统一使用.net项目组的认证系统.那就意味着做单点登 ...

  3. 前后端分离使用 Token 登录解决方案

    前后端分离使用 Token 登录解决方案:https://juejin.im/post/5b7ea1366fb9a01a0b319612

  4. 实战!spring Boot security+JWT 前后端分离架构认证登录!

    大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...

  5. [django]前后端分离之JWT用户认证

    在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...

  6. 前后端分离之JWT用户认证(转)

    在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...

  7. 前后端分离之JWT用户认证zf

    在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...

  8. [转] 前后端分离之JWT用户认证

    [From] http://www.jianshu.com/p/180a870a308a 在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当 ...

  9. 前后端分离之JWT用户认证

    在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...

随机推荐

  1. Java使用多线程异步执行批量更新操作

    import com.google.common.collect.Lists; import org.apache.commons.collections.CollectionUtils; impor ...

  2. java 集合及其线程安全 及其 set linkedList map table 区别

    早在jdk的1.1版本中,所有的集合都是线程安全的.但是在1.2以及之后的版本中就出现了一些线程不安全的集合,为什么版本升级会出现一些线程不安全的集合呢?因为线程不安全的集合普遍比线程安全的集合效率高 ...

  3. 解释 Spring 支持的几种 bean 的作用域?

    Spring 框架支持以下五种 bean 的作用域:singleton : bean 在每个 Spring ioc 容器中只有一个实例.prototype:一个 bean 的定义可以有多个实例.req ...

  4. 复杂JSON字符串解析,可以少走弯路

    发现一个好文章:装载至http://www.verejava.com/?id=17174254038220 package com.json5;    import org.json.JSONArra ...

  5. IE中的编码位置

    进入设置 找到Editor 找到File Encodings

  6. 攻防世界 easytornado

    easytornado 进入环境就这样子 我们逐一访问看看 进入flag.txt提示flag in /fllllllllllllag我们访问fllllllllllllag看看 报了一个error,且在 ...

  7. 深入Linux 内核架构之 CFS

    linux内核分析--CFS(完全公平调度算法)   1.1 CFS原理 cfs定义了一种新的模型,它给cfs_rq(cfs的run queue)中的每一个进程安排一个虚拟时钟,vruntime.如果 ...

  8. 前端每日实战:133# 视频演示如何用 CSS 和 GSAP 创作有多个关键帧的连续动画

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/eLMKJG 可交互视频 此视频是可 ...

  9. js修改html中class属性

    document.getElementById("tr").setAttribute("class","styleclass"); 其中  ...

  10. 自学java如何快速地达到工作的要求?

    自学java如何快速地达到工作的要求,是很多初学者都比较关心的问题,对于初学者来说,盲目自学不但不能快速入门,还会浪费大量的时间. 今天知了堂就来分享自学Java如何快速达到找工作的要求. 1.自学J ...