一、背景介绍
公司现有一个ssh框架的旧系统,想要转型使用前后端分离vue+springboot的新框架。由于一些原因,无法完全转型,所以考虑新增的业务需求,使用在旧系统中嵌套新系统页面的方式,之后新需求就可以完全使用前后端分离的新框架了。
二、解决方案
用户登录旧系统,展示的菜单是按照新系统开发的前端工程,然后页面上所有操作都与springboot工程接口通信。springboot系统与旧ssh系统操作同一数据库。
三、登录认证
此方案分为ssh系统、vue前端系统以及springboot系统。用户登录的是ssh旧系统,所有后面的请求需要进行登录的认证。方案是ssh系统登录后,生成token,存在localstorage中;然后展示vue前端系统时,将token传递过去;后面调用springboot接口时,将token带在请求的请求头中,这样所有请求都会带着token,即可进行登录身份认证。代码如下:
ssh系统中使用JWT生成token:
String token = this.getToken(userVw.getUser());
getRequest().setAttribute("token", token);
/**
* 生成token
* @param user
* @return
*/
public String getToken(User user) {
/**
* token默认有效时间 1天
*/
Long jwtTimeOut = 86400L;
Date date = new Date(System.currentTimeMillis() + jwtTimeOut);
String token="";
token= JWT.create()
.withAudience(user.getId().toString())// 将 user id 保存到 token 里面
// .withExpiresAt(date)
.sign(Algorithm.HMAC256(user.getPasswd()));// 以 password 作为 token 的密钥
return token;
}
然后跳转vue前台系统时,将token传递过去,代码如下:
var token = localStorage.getItem("token");
url=url+"&token="+token
console.log(url);
self.parent.parent.document.getElementById("mainFrame").src = url;
vue前端系统:
router.beforeEach((to, from, next) => {
debugger
var token = to.query.token;
localStorage.setItem('logintoken', token)
}
请求的请求头中添加上token
const req = (method, url, params) => {
return axios({
method: method,
url: url,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
token: localStorage.getItem('logintoken')
},
data: params,
traditional: true,
transformRequest: [
function (data) {
let ret = ''
for (let it in data) {
ret +=
encodeURIComponent(it) +
'=' +
encodeURIComponent(data[it]) +
'&'
}
return ret
}
]
}).then(res => res.data);
};
springboot后台,添加依赖:
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
配置拦截器:
package com.nomen.ctms.efms.config;
import com.nomen.ctms.efms.base.interceptor.AuthenticationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class IntercepterConfig implements WebMvcConfigurer {
/**
* 对所有请求增加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor(){
return new AuthenticationInterceptor();
}
}
拦截器拦截所有请求:
package com.nomen.ctms.efms.base.interceptor;
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.auth0.jwt.interfaces.DecodedJWT;
import com.nomen.ctms.efms.base.annotation.PassToken;
import com.nomen.ctms.efms.base.annotation.NeedLoginToken;
import com.nomen.ctms.efms.system.user.entity.User;
import com.nomen.ctms.efms.system.user.service.UserService;
import com.nomen.ctms.efms.utils.CurrentUserUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
//如果不是映射到方法直接通过
if(!(handler instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod = (HandlerMethod)handler;
Method method = handlerMethod.getMethod();
//检查方法是否有通过验证的注释passtoken,有则跳过认证
if(method.isAnnotationPresent(PassToken.class)){
PassToken passToken = method.getAnnotation(PassToken.class);
if(passToken.required()){
return true;
}
}
//检查有没有需要用户权限的注解
if(method.isAnnotationPresent(NeedLoginToken.class)){
NeedLoginToken annotation = method.getAnnotation(NeedLoginToken.class);
if(annotation.required()){
//执行认证
if(token==null){
throw new RuntimeException("无token,请重新登录");
}
}
// 获取token中的userId
String userId;
try{
userId = JWT.decode(token).getAudience().get(0);
}catch (JWTDecodeException j){
throw new RuntimeException("401");
}
User user = userService.getById(Long.valueOf(userId));
CurrentUserUtil.addCurrentUser(user);
if (user == null) {
throw new RuntimeException("用户不存在,请重新登录");
}
// 验证token
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(user.getPasswd())).build();
try {
DecodedJWT verify = verifier.verify(token);
System.out.println(verify);
} catch (JWTVerificationException e) {
e.printStackTrace();
throw new RuntimeException("401",e);
}
return true;
}
return true;
}
}
创建注解类:
package com.nomen.ctms.efms.base.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 需要登录才能进行操作的注解
* @Target:注解的作用目标
* @Target(ElementType.TYPE)——接口、类、枚举、注解
* @Target(ElementType.METHOD)——方法
*
* @Retention:注解的保留位置
* RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
*
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLoginToken {
boolean required() default true;
}
package com.nomen.ctms.efms.base.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用来跳过验证的注解
* @Target:注解的作用目标
* @Target(ElementType.TYPE)——接口、类、枚举、注解
* @Target(ElementType.METHOD)——方法
*
* @Retention:注解的保留位置
* RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
在需要认证的接口上添加@NeedLoginToken注解:
@NeedLoginToken
@RequestMapping(value = "/list",method = RequestMethod.POST)
public ResponseResult getTemplate(Page page, DocTemplate template){
ResponseResult responseResult = new ResponseResult();
User currentUser = CurrentUserUtil.getCurrentUser();
System.out.println(currentUser.getUserId());
IPage iPage = this.templateService.queryTemplate(page, template);
List records = iPage.getRecords();
responseResult.setData(records);
responseResult.setSuccess(true);
responseResult.setTotal(iPage.getTotal());
return responseResult;
}
四、总结
由于本人经验也不是很充足,目前也只想到了这个方案,有什么问题或者有更好的方法,还请大佬们多多指教。