引入JWT前后端交互

JsonWebToken(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。JWT就是一段字符串,分为三段【头部、载荷、签证】。

1 后端配置

1.1 引入依赖
        <!--   JWT     -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
1.2 封装JWTUtil工具类

封装的工具类用于根据字段生成、验证token。


package com.hikaru.crowd.util; import com.hikaru.crowd.util.constance.JWTConstant;
import io.jsonwebtoken.*;
import org.bouncycastle.util.encoders.Base64; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date; public class JWTUtil {
/**
* 签发创建JWT
*
*/
public static String createJWT(String id, String subject, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
SecretKey secretKey = generalKey();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject) // 主题
.setIssuer(JWTConstant.JWT_USER) // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey); // 签名算法及秘钥
if(ttlMillis > 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
// 设置过期时间
builder.setExpiration(expDate);
}
return builder.compact();
}
/**
* 解析JWT
* @param jwtStr
* @return
*/
public static Claims parseJWT(String jwtStr) {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwtStr)
.getBody();
} /**
* JWT token验证
* @param jwtStr
* @return
*/
public static CheckResult validateJwt(String jwtStr) {
CheckResult checkResult = new CheckResult();
Claims claims = null;
try {
claims = parseJWT(jwtStr);
checkResult.setSuccess(true);
checkResult.setClaims(claims); } catch (ExpiredJwtException e) {
// token 过期
checkResult.setErrorCode(JWTConstant.JWT_ERROR_CODE_EXPIRE);
checkResult.setSuccess(false);
} catch (SignatureException e) {
// token 验证失败
checkResult.setErrorCode(JWTConstant.JWT_ERROR_CODE_FAIL);
checkResult.setSuccess(false);
} catch (Exception e) {
// 其他异常
checkResult.setErrorCode(JWTConstant.JWT_ERROR_CODE_FAIL);
checkResult.setSuccess(false);
}
return checkResult;
} /**
* 生成加密的key
* @return
*/
public static SecretKey generalKey() {
byte[] encodeKey = Base64.decode(JWTConstant.JWT_SECRET);
SecretKey key = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
return key;
} /**
* 根据用户名返回Jwt token
* @param userName
* @return
*/
public static String getJWTToken(String userName) {
return createJWT(userName, userName, JWTConstant.JWT_TTL);
} public static void main(String[] args) throws InterruptedException {
String token = createJWT("1", "1", 3000);
CheckResult checkResult = validateJwt(token);
System.out.println(checkResult.getErrorCode()); // 0 Thread.sleep(3 * 1000); checkResult = validateJwt(token);
System.out.println(checkResult.getErrorCode()); // 4001 }
}
1.3 封装JWT CheckResult验证返回结果集
public class CheckResult {
private int errorCode;
private boolean success;
private Claims claims; public int getErrorCode() {
return errorCode;
} public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
} public boolean isSuccess() {
return success;
} public void setSuccess(boolean success) {
this.success = success;
} public Claims getClaims() {
return claims;
} public void setClaims(Claims claims) {
this.claims = claims;
} public CheckResult() { }
}
1.4 创建JWT常量类
public class JWTConstant {
public static final Integer JWT_ERROR_CODE_NULL = 4000; // token异地登录
public static final Integer JWT_ERROR_CODE_EXPIRE = 4001; // token过期
public static final Integer JWT_ERROR_CODE_FAIL = 4002; //token验证失败 public static final String JWT_SECRET = "9b91643073cdda1d93507ec66591315c";
public static final long JWT_TTL =60 * 60 * 1000;
public static final String JWT_USER = "tod4";
}

2 前端配置

根据最开始的流程图,前端会在提交完用户名和密码之后得到后端传来的token,然后将其保存,随后每次发送请求前都需要将token放在请求头上才能成功请求服务器。

2.1 登录完成时localStorage、vuex保存token

这里以一个vue后台管理模板为例,首先提交登录表单发送登录请求,可以看到这里是向user vue模块仓库中的名为login的action派发(dispatch)的请求。

    handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}

actions的login执行异步请求成功得到token之后,一方面调用工具方法setToken将token保存到浏览器的本地存储,另一方面commitmutations将数据保存到state(vuex仓库)

import Cookies from 'js-cookie'

const TokenKey = 'crowdfunding_token'

export function getToken() {
// return Cookies.get(TokenKey)
return localStorage.getItem(TokenKey)
} export function setToken(token) {
// return Cookies.set(TokenKey, token)
return localStorage.setItem(TokenKey, token)
} export function removeToken() {
// return Cookies.remove(TokenKey)
return localStorage.removeItem(TokenKey)
}

这一部分vuex的整体代码如下:

import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
import da from "element-ui/src/locale/lang/da"; const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: ''
}
} const state = getDefaultState() const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
}
} const actions = {
// user login
async login({ commit }, userInfo) {
const { username, password } = userInfo
// return new Promise((resolve, reject) => {
// login({ loginName: username.trim(), passWord: password }).then(response => {
// const { token } = response.data
// commit('SET_TOKEN', token)
// console.log(token)
// setToken(token)
// resolve()
// }).catch(error => {
// reject(error)
// })
// })
let res = await login({ loginName: username.trim(), passWord: password })
const { token } = res.data
commit('SET_TOKEN', token)
setToken(token)
return res
}, // get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(response => {
const { data } = response if (!data) {
return reject('Verification failed, please Login again.')
} const { name, avatar } = data commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
resolve(data) }).catch(error => {
reject(error)
})
})
}, // user logout
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // must remove token first
resetRouter()
commit('RESET_STATE')
resolve()
}).catch(error => {
reject(error)
})
})
}, // remove token
resetToken({ commit }) {
return new Promise(resolve => {
removeToken() // must remove token first
commit('RESET_STATE')
resolve()
})
}
} export default {
namespaced: true,
state,
mutations,
actions
}
2.2 每次请求时都将token放在请求头上面

要完成这一点则需要借助我们重写的axios二次封装,在请求拦截器判断一下vuex仓库中有没有token,如果有的话就将其加到请求的请求头上面。

// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
}) // request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)

3 不基于SpringSecurity的前后端分离JWT登录、token验证

大致流程是首次请求登录会跳过拦截器,经过登录验证之后会由后端向前端响应一个token,然后前端得到token后在vuex进行保存,以后每次发送请求时都需要在请求头上面添加token,后端的拦截器则会拦截非登录请求判断token是否过期或非法,然后放行请求。

3.1 登录验证

UserController

    /**
* 登录
*
* @param requestBody
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public R<Object> loginHandle(@RequestBody String requestBody) {
JSONObject jsonObject = JSON.parseObject(requestBody);
String loginName = (String) jsonObject.get("loginName");
String passWord = (String) jsonObject.get("passWord");
// 验证用户名和密码
userService.userVerification(loginName, passWord);
// 返回token
Map<String, String> map = userService.getTokenByLoginName(loginName); return R.successWithData(map);
}

UserServiceImpl

    /**
* 验证用户账户及密码
*
* @param formLoginName, formPassWord
* @return
*/
@Override
public void userVerification(String formLoginName, String formPassWord) { // 对密码进行md5加密
formPassWord = CrowdUtil.md5(formPassWord); UserExample userExample = new UserExample();
userExample.createCriteria().andLoginNameEqualTo(formLoginName);
List<User> users = userMapper.selectByExample(userExample);
// 用户名不存在
if (users.size() <= 0) {
throw new LoginFailedException(CrowdConstant.MASSAGE_LOGIN_FAILED);
} String dbPassword = users.get(0).getPassWord();
// 密码不正确
if (!Objects.equals(formPassWord, dbPassword)) {
throw new LoginFailedException(CrowdConstant.MASSAGE_LOGIN_FAILED);
}
}
    /**
* 根据用户名生成token
*
* @param loginName
* @return
*/
@Override
public Map<String, String> getTokenByLoginName(String loginName) { String token = JWTUtil.getJWTToken(loginName);
Map<String, String> map = new HashMap<>();
map.put("token", token); return map;
}
3.2 基于拦截器的token验证
登录拦截器的配置
package com.hikaru.crowd.mvc.interceptor;

import com.google.gson.Gson;
import com.hikaru.crowd.util.CheckResult;
import com.hikaru.crowd.util.JWTUtil;
import com.hikaru.crowd.util.R;
import com.hikaru.crowd.util.constance.JWTConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; @Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("拦截到请求:" + request.getRequestURI());
String token = request.getHeader("X-Token");
R<Object> resultEntity;
// 请求头中不含token
if(token == null) {
resultEntity = new R<>(JWTConstant.JWT_ERROR_CODE_EXPIRE, null, null); Gson gson = new Gson();
String json = gson.toJson(resultEntity); response.getWriter().write(json); return false;
}
CheckResult checkResult = JWTUtil.validateJwt(token);
// token过期或者不合法
if(!checkResult.isSuccess()) {
int errorCode = checkResult.getErrorCode();
resultEntity = new R<>(errorCode, null, null); Gson gson = new Gson();
String json = gson.toJson(resultEntity); response.getWriter().write(json); return false;
}
return true;
}
}
登录拦截器的注册
    /**
* 拦截器注册
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/file/saveAvatar")
.excludePathPatterns("/test")
.excludePathPatterns("/user/logout")
.excludePathPatterns("/user/login"); }

也如标题所说这是不基于SpringSecurity的前后端分离登录验证,下面介绍的基于SpringSecurity的方式则可以让我们舍弃拦截器,大大简化我们的代码。

【SSM项目】尚筹网(四)JWT以及基于拦截器的前后端分离登录验证的更多相关文章

  1. 基于spring security 实现前后端分离项目权限控制

    前后端分离的项目,前端有菜单(menu),后端有API(backendApi),一个menu对应的页面有N个API接口来支持,本文介绍如何基于spring security实现前后端的同步权限控制. ...

  2. 基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  3. SpringBootSecurity学习(13)前后端分离版之JWT

    JWT 使用 前面简单介绍了把默认的页面登录改为前后端分离的接口异步登录的方法,可以帮我们实现基本的前后端分离登录功能.但是这种基本的登录和前面的页面登录还有一个一样的地方,就是使用session和c ...

  4. Spring Cloud实战 | 最八篇:Spring Cloud +Spring Security OAuth2+ Axios前后端分离模式下无感刷新实现JWT续期

    一. 前言 记得上一篇Spring Cloud的文章关于如何使JWT失效进行了理论结合代码实践的说明,想当然的以为那篇会是基于Spring Cloud统一认证架构系列的最终篇.但关于JWT另外还有一个 ...

  5. 基于Vue的前后端分离项目实践

    一.为什么需要前后端分离 1.1什么是前后端分离  前后端分离这个词刚在毕业(15年)那会就听说过,但是直到17年前都没有接触过前后端分离的项目.怎么理解前后端分离?直观的感觉就是前后端分开去做,即功 ...

  6. 八个开源的 Spring Boot 前后端分离项目,一定要收藏!

    八个开源的 Spring Boot 前后端分离项目 最近前后端分离已经在慢慢走进各公司的技术栈,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,我们也非常建议大家学习一下前 ...

  7. k.tt 研究下生成的逻辑代码:从壹开始前后端分离 [.netCore 填坑 ] 三十二║ 四种方法快速实现项目的半自动化搭建

    更新 1.更新小伙伴 @大龄Giser 提出好点子:试试VS的插件扩展:VSIX.ItemProject等,将T4模板给制作插件,这里先记下,有懂的小伙伴可以自己先试试,我会在以后更新. 2.感谢小伙 ...

  8. 前后端分离java、jwt项目进行CORS跨域、解决非简单请求跨域问题、兼容性问题

    情况描述: 最近在部署一个前后端分离的项目出现了跨域问题*, 项目使用jwt进行鉴权,需要前端请求发起携带TOKEN的请求*,请求所带的token无法成功发送给后端, 使用跨域后出现了兼容性问题:Ch ...

  9. 从零玩转SpringSecurity+JWT整合前后端分离

    从零玩转SpringSecurity+JWT整合前后端分离 2021年4月9日 · 预计阅读时间: 50 分钟 一.什么是Jwt? Json web token (JWT), 是为了在网络应用环境间传 ...

  10. 前后端分离之vue2.0+webpack2 实战项目 -- webpack介绍

    webpack的一点介绍 Webpack 把任何一个文件都看成一个模块,模块间可以互相依赖(require or import),webpack 的功能是把相互依赖的文件打包在一起.webpack 本 ...

随机推荐

  1. 《JavaScript高级程序设计》Chapter04 变量,作用域,内存

    原始值&引用值 原始值(primitive value):Undefined, Null, Boolean, Number, String, Symbol 按值访问,直接操作存储在变量中的实际 ...

  2. FCC 中级算法题 Everything Be True

    Everything Be True 所有的东西都是真的! 完善编辑器中的every函数,如果集合(collection)中的所有对象都存在对应的属性(pre),并且属性(pre)对应的值为真.函数返 ...

  3. Using Semaphores in Delphi, Part 2: The Connection Pool

    Abstract: Semaphores are used to coordinate multiple threads and processes. That semaphores provide ...

  4. linux shell 目录

    linux shell 目录 目录 linux shell 目录 类型 unix支持三大主流shell linux支持的shell(可有四种) 部分相关命令 查询进程 ps pstree kill 查 ...

  5. C++的万能引用解析

    C++11除了带来了右值引用以外,还引入了一种称为"万能引用"的语法:通过"万能引用",对某型别的引用T&&,既可以表达右值引用,也可以表达左值 ...

  6. JDMasking v0.1.0-beta 发布

    JDMasking 全称是jdbc data masking,是一款零代码修改.无重启.基于JDBC的动态数据脱敏软件. JDMasking 主要对实现jdbc的驱动进行字节码的增强,支持对运行中的程 ...

  7. Python第六章实验报告

    一.实验内容:<零基础学Python>第六章实例和实战,以及一道作业题 二.实验环境:IDLE Shell 3.9.7 三.实验目的和要求:掌握定义和调用函数.变量的作用域.匿名函数.参数 ...

  8. Maven 切换JDK版本

    欢迎访问我的个人博客:xie-kang.com 查看Maven安装目录的conf目录可以看到有 settings.xml\toolchains.xml文件.settings.xml主要是设置切换Mav ...

  9. Oracle-登录的用户名和密码大小写敏感

    Oracle-登录的用户名和密码大小写敏感

  10. unidbgrid按回车键切换到右侧CELL

    打开UniDBGrid的ClientEvents->ExtEvents属性,编辑Ext.grid.Panel的reconfig函数,输入如下代码就可以实现当UniDBGrid表格的ReadOnl ...