前后端分离,简单JWT登录详解
前后端分离,简单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. 退出登录
- 可以看这篇文章:https://blog.csdn.net/a1098766713/article/details/102914354
- 这里使用最简单的方法
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登录详解的更多相关文章
- Spring Security OAuth2.0认证授权六:前后端分离下的登录授权
历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...
- vue+springboot前后端分离实现单点登录跨域问题处理
最近在做一个后台管理系统,前端是用时下火热的vue.js,后台是基于springboot的.因为后台系统没有登录功能,但是公司要求统一登录,登录认证统一使用.net项目组的认证系统.那就意味着做单点登 ...
- 前后端分离使用 Token 登录解决方案
前后端分离使用 Token 登录解决方案:https://juejin.im/post/5b7ea1366fb9a01a0b319612
- 实战!spring Boot security+JWT 前后端分离架构认证登录!
大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...
- [django]前后端分离之JWT用户认证
在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...
- 前后端分离之JWT用户认证(转)
在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...
- 前后端分离之JWT用户认证zf
在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...
- [转] 前后端分离之JWT用户认证
[From] http://www.jianshu.com/p/180a870a308a 在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当 ...
- 前后端分离之JWT用户认证
在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...
随机推荐
- git 的使用(新手)
git的使用心得 windows版本下载git地址 git config --global user.name 用户名 在使用git前要注册用户名(个人称谓) git config --global ...
- Delete、truncate、drop都是删除语句,它们有什么分别?
delete 属于DML语句,删除数据,保留表结构,需要commit,可以回滚,如果数据量大,很慢. truncate 属于DDL语句,删除所有数据,保留表结构,自动commit,不可以回滚,一次全部 ...
- Dubbo 支持服务降级吗?
以通过 dubbo:reference 中设置 mock="return null".mock 的值也可以修改 为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规 ...
- pyinstaller打包Django项目
系统:ubuntu18.04 / Centos 7自带Python3.61.安装pip3 apt-get install -y python3-pip pip3 install --u ...
- Spring MVC 框架有什么用?
Spring Web MVC 框架提供 模型-视图-控制器 架构和随时可用的组件,用于开 发灵活且松散耦合的 Web 应用程序.MVC 模式有助于分离应用程序的不同方 面,如输入逻辑,业务逻辑和 UI ...
- Tcp三次握手四次挥手个人学习
最近想跳槽,学习了tcp中的三次握手与四次挥手,特意记录下,加深记忆 SYN 代表请求创建连接 FIN 表示请求关闭连接 ACK 代表确认接受,不管是三次握手还是四次分手,在回应的时候都会加上ACK= ...
- 纯干货数学推导_傅里叶级数与傅里叶变换_Part4_傅里叶级数的复数形式
- web页面性能优化之接口前置
上个Q做了一波web性能优化,积累了一点点经验 记录分享一下. 先分享一个比较常用的接口前置 的优化方案吧 优化前首屏秒开大约在40%左右 首屏秒开大约提高了25% 先发一张优化成果图 前置原因 对于 ...
- JavaScript遍历表单元素
运行效果: 源代码: 1 <!DOCTYPE html> 2 <html lang="zh"> 3 <head> 4 <meta char ...
- 微信小程序获取今天,昨天,后天
today 是需要计算的某一天的日期例如"2018-12-12",传 null 默认今天,addDayCount 是要推算的天数, -1是前一天,0是今天,1是后一天. onLoa ...