前后端分离,简单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. git 的使用(新手)

    git的使用心得 windows版本下载git地址 git config --global user.name 用户名 在使用git前要注册用户名(个人称谓) git config --global ...

  2. Delete、truncate、drop都是删除语句,它们有什么分别?

    delete 属于DML语句,删除数据,保留表结构,需要commit,可以回滚,如果数据量大,很慢. truncate 属于DDL语句,删除所有数据,保留表结构,自动commit,不可以回滚,一次全部 ...

  3. Dubbo 支持服务降级吗?

    以通过 dubbo:reference 中设置 mock="return null".mock 的值也可以修改 为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规 ...

  4. pyinstaller打包Django项目

    系统:ubuntu18.04 / Centos 7自带Python3.61.安装pip3     apt-get install -y python3-pip     pip3 install --u ...

  5. Spring MVC 框架有什么用?

    Spring Web MVC 框架提供 模型-视图-控制器 架构和随时可用的组件,用于开 发灵活且松散耦合的 Web 应用程序.MVC 模式有助于分离应用程序的不同方 面,如输入逻辑,业务逻辑和 UI ...

  6. Tcp三次握手四次挥手个人学习

    最近想跳槽,学习了tcp中的三次握手与四次挥手,特意记录下,加深记忆 SYN 代表请求创建连接 FIN 表示请求关闭连接 ACK 代表确认接受,不管是三次握手还是四次分手,在回应的时候都会加上ACK= ...

  7. 纯干货数学推导_傅里叶级数与傅里叶变换_Part4_傅里叶级数的复数形式

  8. web页面性能优化之接口前置

    上个Q做了一波web性能优化,积累了一点点经验 记录分享一下. 先分享一个比较常用的接口前置 的优化方案吧 优化前首屏秒开大约在40%左右 首屏秒开大约提高了25% 先发一张优化成果图 前置原因 对于 ...

  9. JavaScript遍历表单元素

    运行效果: 源代码: 1 <!DOCTYPE html> 2 <html lang="zh"> 3 <head> 4 <meta char ...

  10. 微信小程序获取今天,昨天,后天

    today 是需要计算的某一天的日期例如"2018-12-12",传 null 默认今天,addDayCount 是要推算的天数, -1是前一天,0是今天,1是后一天. onLoa ...