vue|springboot+vue3.0+token 安全验证


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验证.

    推荐阅读