springboot+vue+token安全验证
- 目录
-
- 一、说明
- 二、后台(springboot)
-
- 1、添加依赖包
- 2、添加token工具类
- 3、创建拦截器
- 4、入口拦截
- 5、配置跨域
- 6、登录接口
- 三、前端(vue)
-
- 1、src目录下创建store文件夹
- 2、添加路由守卫
- 3、添加拦截器
- 4、登录测试
目录 一、说明 在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下(懒得打字,这一部分引用了该博主的内容 Vue项目中实现用户登录及token验证):
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面
4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、每次调后端接口,都要在请求头中加token
6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面
二、后台(springboot) 1、添加依赖包
在springboot项目中添加jwt依赖包,这个主要作用是token的加密与解密方法。
io.jsonwebtoken
jjwt
0.9.1
2、添加token工具类
添加token工具类,TokenUtil
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.lin.springMVC.domain.User;
import java.util.Date;
public class TokenUtil {
private static final long EXPIRE_TIME= 10*60*60*1000;
private static final String TOKEN_SECRET="txdy";
//密钥盐
/**
* 签名生成
* @param user
* @return
*/
public static String sign(User user){
String token = null;
try {
Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
token = JWT.create()
.withIssuer("auth0")
.withClaim("userAccount", user.getUserAccount())
.withExpiresAt(expiresAt)
// 使用了HMAC256加密算法。
.sign(Algorithm.HMAC256(TOKEN_SECRET));
} catch (Exception e){
e.printStackTrace();
}
return token;
}
/**
* 签名验证
* @param token
* @return
*/
public static boolean verify(String token){
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
DecodedJWT jwt = verifier.verify(token);
System.out.println("认证通过:");
System.out.println("userAccount: " + jwt.getClaim("userAccount").asString());
System.out.println("过期时间:" + jwt.getExpiresAt());
return true;
} catch (Exception e){
return false;
}
}
}
3、创建拦截器
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
@Component
public class TokenInterceptor implements HandlerInterceptor {
private static final String token = "token#";
@Autowired
RedisHandler redisHandler;
@Value("${api.api_prefix}")
public String api_prefix;
/**
* 在请求处理之前进行调用,在进入Controller方法之前先进入此方法
* 返回true才会继续向下执行,返回false取消当前请求
* 登录拦截,权限资源控制工作
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
String userAccount = null;
String redirectUrl = "login";
if ((httpServletRequest.getParameter("language") != null && !httpServletRequest.getParameter("language").equals("en"))) {
redirectUrl += "_en";
}
Cookie[] cookies = httpServletRequest.getCookies();
//从cookie中取值
String token1 = httpServletRequest.getHeader("Access-Token");
// 从 http 请求头中取出 token
if(token1 != null){
boolean result = TokenUtil.verify(token1);
if(result){
System.out.println("通过拦截器");
return true;
}
}
try{
JSONObject json = new JSONObject();
json.put("msg","token verify fail");
json.put("status","401");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json;
charset=utf-8");
PrintWriter out = null;
out = httpServletResponse.getWriter();
out.append(json.toString());
return false;
}catch (Exception e){
e.printStackTrace();
JSONObject json = new JSONObject();
json.put("msg","error");
json.put("status","500");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json;
charset=utf-8");
PrintWriter out = null;
out = httpServletResponse.getWriter();
out.append(json.toString());
return false;
}}
4、入口拦截
配置入口拦截,ShiroConfiguration类
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
/**
* shiro配置类
*/
@Configuration
public class ShiroConfiguration {
/**
* Shiro的Web过滤器Factory 命名:shiroFilter
*/
@Bean(name = "shiroFilter")
//相当于
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
//for defining the master Shiro Filter
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//Shiro的核心安全接口Sets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
//This is a required property - failure to set it will throw an initialization exception.
shiroFilterFactoryBean.setSecurityManager(securityManager);
//Map filterMap = new LinkedHashMap<>();
//Most implementations subclass one of the
//{@link AccessControlFilter}, {@link AuthenticationFilter}, {@link AuthorizationFilter} classes to simplify things,
//and each of these 3 classes has configurable properties that are application-specific.
filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());
//添加过滤器
shiroFilterFactoryBean.setFilters(filterMap);
Map filterChainDefinitionMap = new LinkedHashMap<>();
//相当于filterChainDefinitionMap.put("/", "anon");
//filterChainDefinitionMap.put("/debug/**", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/register", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}
5、配置跨域
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
/*是否允许请求带有验证信息*/
corsConfiguration.setAllowCredentials(true);
/*允许访问的客户端域名*/
corsConfiguration.addAllowedOrigin("*");
/*允许服务端访问的客户端请求头*/
corsConfiguration.addAllowedHeader("*");
/*允许访问的方法名,GET POST等*/
corsConfiguration.addAllowedMethod("*");
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
6、登录接口
@PostMapping("/login")
@ResponseBody
public ModelMap userLogin(@RequestBody User userform, HttpServletRequest request, HttpServletResponse response, @RequestHeader("language") String language) {
String status;
String url = redirectUrl;
String token1="";
int code;
ModelMap result = new ModelMap();
Subject currentUser = SecurityUtils.getSubject();
try {
currentUser.login(token);
User user = userService.getUserInfo(userform.getUserAccount(), ShiroUtils.generatePwdEncrypt(userform.getUserPassword(), salt));
if (user != null) {
try {
token1 = TokenUtil.sign(userform);
status = "success";
code = 200;
} catch (JedisConnectionException | RedisConnectionFailureException e) {
//用户登录记录
userLog(user, request.getRemoteUser(), "login_error:" + e.toString());
status = "fail";
code = 400;
}
} else {
User userTemp = new User();
userTemp.setUserId("null");
userTemp.setUserAccount("null");
userLog(userTemp, request.getRemoteUser(), "login_error:user is not exist!");
status = "fail";
code = 401;
}
} catch (AuthenticationException e) {
//用户登录记录
User userTemp = new User();
userTemp.setUserId("null");
userTemp.setUserAccount("null");
userLog(userTemp, request.getRemoteUser(), "login_error:user is not exist!");
status = "fail";
code = 401;
}
result.put("status", status);
result.put("code", code);
result.put("token",token1);
result.put("url", "null");
return result;
}
三、前端(vue) 1、src目录下创建store文件夹
在store文件夹下新建 index.js:
[SET_TOKEN]: (state, token: string) => {
state.token = token;
ls.set(STORAGE_TOKEN_KEY, token);
},
2、添加路由守卫
import router from '@/router';
import store from '@/store';
import localStorage from '@/utils/local-storage';
import { allowList, loginRoutePath } from '../define-meta';
import { STORAGE_TOKEN_KEY } from '@/store/mutation-type';
// eslint-disable-next-line
import { GENERATE_ROUTES, GENERATE_ROUTES_DYNAMIC, GET_INFO } from '@/store/modules/user/actions';
router.beforeEach(async to => {
const userToken = localStorage.get(STORAGE_TOKEN_KEY);
// token check
if (!userToken) {
// 白名单路由列表检查
if (allowList.includes(to.name as string)) {
return true;
}
if (to.fullPath !== loginRoutePath) {
// 未登录,进入到登录页
return {
path: loginRoutePath,
replace: true,
};
}
return to;
}// check login user.role is null
if (store.getters['user/allowRouters'] && store.getters['user/allowRouters'].length > 0) {
return true;
} else {
const info = await store.dispatch(`user/${GET_INFO}`);
// 使用当前用户的 权限信息 生成 对应权限的路由表
const allowRouters = await store.dispatch(`user/${GENERATE_ROUTES}`, info);
if (allowRouters) {
return { ...to, replace: true };
}
return false;
}
});
router.afterEach(() => {});
3、添加拦截器
// 请求拦截器
const requestHandler = (
config: AxiosRequestConfig,
): AxiosRequestConfig | Promise => {
const savedToken = localStorage.get(STORAGE_TOKEN_KEY);
// 如果 token 存在
// 让每个请求携带自定义 token, 请根据实际情况修改
if (savedToken) {
config.headers[REQUEST_TOKEN_KEY] = savedToken;
}
config.headers[language] = 'cn';
return config;
};
4、登录测试
[LOGIN]({ commit }, info: LoginParams) {
return new Promise((resolve, reject) => {
// call ajax
postAccountLogin(info)
.then(res => {
commit(SET_TOKEN, res.token);
resolve(res);
})
.catch(error => {
reject(error);
});
});
},
代码很多,写的比较杂,记录下,怕自己忘记了,之前项目用的cookie,在此基础上修改了下。也多谢下面的大神提供的帮助。
【vue|springboot+vue3.0+token 安全验证】链接: VUE SPRINGBOOT实现TOKEN登录以及访问验证.
链接: Vue项目中实现用户登录及token验证.
推荐阅读
- python基础|[python基础] 面向对象——封装,继承
- Java|Java Web程序设计——图书管理系统
- MySQL数据库|关于JDBC连接MySQL数据库发生的异常
- 笔记|Springboot----项目整合微信支付(用户取消订单)
- java|@GetMapping、@PostMapping 和 @RequestMapping详细区别附实战代码(全)
- Java面向对象程序设计|Java程序设计——JDBC基础(JDBC编程)
- “李记餐厅”微信点餐小程序+后台管理系统
- 校园生活助手小程序
- Flink|Flink入门