环境:vue/cli 4.3 +elementUI+ssm框架+node.js(代理proxyTable 方式的代理,在开发环境下)
我遇到sessionId 总是变化的问题(坑了我好久,故做引路人.....)
1.因为用到的是node.js开发的环境下的代理.所以不存在跨域问题(再说一遍不存在跨域问题).前端代理配置vue.config.js 如下图:
module.exports = {
css: {
sourceMap: true, //设置浏览器调试模式下显示css文件显示路径,方便开发人员调试.
},
devServer: {
port:8080,
host:'127.0.0.1',
https:false,
proxy: {
'/api': {
target: 'http://localhost:8088/api',
changeOrigin: true,
ws: false,
pathRewrite: { '^/api': '/' }
}
}
},
};
如果你也用上面的代理,那么后台代码不用动..因为代理不会涉及跨域问题,代理顾名思义,你可以想象成前端模拟后台接口访问后台,所以不涉及跨域,项目开发完是放到ngnix 上的.如果不是采用代理方式.请参考其他跨域配置(网上一搜一大堆)
注意要说明的是8080后的api是后端项目名称.必须和 '/api'代理名称一致,否则会导致sessionId 一致变换 .我不清楚为什么,知道的朋友请留言..
2.axios 中不需要改变 .. 至于网上说 withCredentials: true 可传cookie 信息,, 仅限于跨域.我们这里采用的是代理,[vue开发环境下的node.js 安装提供的代理], true或false 无所谓..不会对sessionid有影响.或者说.cookie都能被后端接收..
3.下面代码是生成jwt 生成 token , 拦截token认证即返回值等等一系列代码如下:
前端axios 封装如下 require.js
import axios from 'axios';
import {MessageBox, Message} from 'element-ui';
import store from '../js/store/store';
import {getToken} from '@/utils/auth';
import qs from 'qs';
// axios 封装
const service = axios.create({
withCredentials: true,// 指定某个请求应该发送凭据。允许客户端携带跨域cookie,也需要此配置
timeout: 20000, // request timeout
});
service.interceptors.request.use(config => {
//1.只拦截以*.do 结尾的url ,其他不拦截. ;
if (!config.url.endsWith(".do")) {
return config;
}
if (config.method === 'post') { // post请求时,处理数据
config.data = qs.stringify({
...config.data //后台数据接收这块需要以表单形式提交数据,而axios中post默认的提交是json数据,所以这里选用qs模块来处理数据.
});
}
//3.将token 放入axios header 里面,进入后台进行处理判断
if (store.getters.token) {
let token = getToken();
config.headers['X-Token'] = token;
}
return config;
}, error => {
// axios出现请求异常
return Promise.reject(error);
}
);
// axios响应拦截器[这里对后台相应的非200状态拦截,并显示详细信息]
service.interceptors.response.use(response => {
const res = response.data;
if (response.status === 200) { //成功
//对自定义resultVO状态码做判断[即非运行异常]
if (res['code'] === undefined) {
return res;
}
if (res != null && res['code'] === 200) { //自定义状态码
return res;
} else if (res['code'] === 100000) {
MessageBox.confirm('您必须重新登录, 或者点击取消保持当前页', '确认退出', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('userModule/actionResetToken').then(() => {
location.reload();
});
});
return Promise.reject(new Error(res.message || 'Error'));
} else {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
});
return Promise.reject(new Error(res.message || 'Error'));
}
} else {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
});
return Promise.reject(new Error(res.message || 'Error'));
}
});
export default service;
后端获取token
public class TokenManager {
public static Logger logger = Logger.getLogger(TokenManager.class);
/**
* 获取token
* by lw on 2020年5月9日 21:45:22
*/
public static String getToken(User user, HttpServletRequest request) {
String sessionId = request.getSession().getId();
long currentTime = System.currentTimeMillis() + 60 * 60 * 1000;//一小时有效时间
Date end = new Date(currentTime);
String token = JWT.create().withAudience(user.getId().toString()).withIssuedAt(new Date()).
withExpiresAt(end).withKeyId(sessionId).sign(Algorithm.HMAC256(user.getPassword()));
System.out.println("token===================="+token);
return token;
}
}
拦截器拦截token
package com.authentication;
import cn.gjing.result.ResultVo;
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.system.auto_generator.SysUser;
import com.system.dao.UserMapper;
import org.apache.log4j.Logger;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
/**
* 拦截器,拦截请求中是否带有token ,以及token 的有效性
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
@Resource
public UserMapper userMapper;
private static Logger logger = Logger.getLogger(AuthenticationInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) {
String token = httpServletRequest.getHeader("X-Token");// 从 http 请求头中取出 token
HttpSession session = httpServletRequest.getSession();
// step1.如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查是否有PassToken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
// step2.获取 token 中的 用户信息
if (token == null || (! session.getId().equalsIgnoreCase(JWT.decode(token).getKeyId()))) {
throwException2Axios(httpServletResponse);
return false;
}
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throwException2Axios(httpServletResponse);
return false;
}
int id = Integer.parseInt(userId);
SysUser user = userMapper.selectByPrimaryKey(id);
if (user == null) {
throwException2Axios(httpServletResponse);
return false;
}
// step3. 验证 token 有消息
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throwException2Axios(httpServletResponse);
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) {
}
/**
* 返回给axios异常.提示重新登录
*/
private void throwException2Axios(HttpServletResponse response){
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try {
writer = response.getWriter();
writer.write(JSON.toJSONString(ResultVo.error(100000,"无效的令牌")));
} catch (IOException e){
logger.error(e.getMessage(),e);
} finally {
if(writer != null){
writer.close();
}
}
}
}
package com.authentication;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
登陆验证
@PostMapping(value = "/doLogin")
public ResultVo login(SysUser sysUser, HttpServletRequest request) {
try {
if (ParamUtil.isEmpty(sysUser)||ParamUtil.isEmpty(sysUser.getUserName()) || ParamUtil.isEmpty(sysUser.getPassword())) {
return ResultVo.error("提交信息错误,数据缺失");
}
List<User> list = userService.getUserInfoList(sysUser.getUserName());
if (list.size() == 0) {
return ResultVo.error("用户名:" + sysUser.getUserName() + "不存在");
}
if (list.size() > 1) {
return ResultVo.error("数据库存在相同用户名:" + sysUser.getUserName());
}
User user = list.get(0);
if (user.getPassword().equalsIgnoreCase(EncryptionUtil.md5(sysUser.getPassword()))) {
String token = TokenManager.getToken(user, request);
return ResultVo.success("登陆成功", token);
} else {
return ResultVo.error("你的密码错误,请重试。");
}
} catch (CannotGetJdbcConnectionException e) {
e.printStackTrace();
return ResultVo.error("数据库连接异常:" + e.getMessage());
} catch (MyBatisSystemException e) {
e.printStackTrace();
return ResultVo.error("数据库连接异常:" + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
return ResultVo.error("异常详情:" + e.getMessage());
}
最后是上的类的拦截器注册
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置包扫描器 注:不用spring容器下默认扫描(false),用其子容器下的springMVC容器扫描只扫描Controller层-->
<context:component-scan base-package="com.*" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 注册mvc注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!--解决mybatis 延迟加载后对象属性为NULL 输出到前端报错的问题-->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper"></property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="jacksonObjectMapper" class="com.common.CustomMapper"></bean>
<!-- 文件上传的配置 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxInMemorySize" value="40960"></property> <!-- 内存占比,多出占用硬盘 -->
<!-- 指定所上传文件的大小不能超过500MB -->
<property name="maxUploadSize" value="524288000"></property>
</bean>
<!--拦截器处理请求token-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/loginController/doLogin.do"/> <!--排除登录的情况-->
<mvc:exclude-mapping path="/userController/getUserInfoByToken.do"/> <!--排除登录的情况-->
<bean class="com.authentication.AuthenticationInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>