前后端分离,简单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. 同一套代码部署多个实例来并行完成mysql某项任务,且避免重复执行

    我经常会碰到一些耗时较长的任务,譬如更新5千万条表数据中的某个字段,代码中可以通过分页依次读取db,然后更新即可.但是耗时极长,那么能否通过将代码部署多个实例,譬如启动多个docker来并行执行任务, ...

  2. 阐述 final、finally、finalize 的区别?

    final:修饰符(关键字)有三种用法:如果一个类被声明为 final,意味 着它不能再派生出新的子类,即不能被继承,因此它和 abstract 是反义词.将 变量声明为 final,可以保证它们在使 ...

  3. kafka 的高可用机制是什么?

    这个问题比较系统,回答出 kafka 的系统特点,leader 和 follower 的关系,消息 读写的顺序即可.

  4. 解释 WEB 模块?

    Spring 的 WEB 模块是构建在 application context 模块基础之上,提供一个适 合 web 应用的上下文.这个模块也包括支持多种面向 web 的任务,如透明地处理 多个文件上 ...

  5. Robinhood基于Apache Hudi的下一代数据湖实践

    1. 摘要 Robinhood 的使命是使所有人的金融民主化. Robinhood 内部不同级别的持续数据分析和数据驱动决策是实现这一使命的基础. 我们有各种数据源--OLTP 数据库.事件流和各种第 ...

  6. 如何基于 ZEGO SDK 实现 Android 一对一音视频聊天应用

    疫情期间,很多线下活动转为线上举行,实时音视频的需求剧增,在视频会议,在线教育,电商购物等众多场景成了"生活新常态". 本文将教你如何通过即构ZEGO sdk在Android端搭建 ...

  7. node-webkit文档翻译#package.json

    title: node-webkit文档翻译#package.json date: 2013-12-07 21:38:25 tags: node-webkit 基本示例 { "main&qu ...

  8. React中Ref 的使用 React-踩坑记_05

    React中Ref 的使用 React v16.6.3 在典型的React数据流中,props是父组件与其子组件交互的唯一方式.要修改子项,请使用new props 重新呈现它.但是,在某些情况下,需 ...

  9. ecahrts实现动态刷新(隔几秒重新显示)

    代码: <script> var chartDom = document.getElementById('main3'); var myChart = echarts.init(chart ...

  10. spark-shell报错java.lang.IllegalArgumentException: java.net.UnknownHostException: namenode

    在使用spark on yarn启动spark-shell时,发现报错: 是说找不到主机名为namenode的主机,那么应该是配置文件出错了. 经过检查,发现是spark-defaults.conf文 ...