springboot+vue3.0+token 安全验证

2 篇文章 1 订阅
本文介绍了在前后端分离的Vue项目中,使用SpringBoot结合JWT进行Token安全验证的步骤。包括后端添加JWT依赖,创建Token工具类,设置拦截器进行Token验证,配置跨域和登录接口;前端创建store文件夹,添加路由守卫,设置请求拦截器,以及登录测试。详细阐述了Token的生成、验证和拦截过程。
摘要由CSDN通过智能技术生成

目录

一、说明

在前后端完全分离的情况下,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的加密与解密方法。

 <!--jwt 依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

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")
    //相当于<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    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);//<property name="securityManager" ref="securityManager"/>
        Map<String, Filter> 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<String, String> filterChainDefinitionMap = new LinkedHashMap<>();//相当于<property name="filterChainDefinitions">
        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<AxiosRequestConfig> => {
  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实现TOKEN登录以及访问验证.
链接: Vue项目中实现用户登录及token验证.

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值