Springboot+Vue前后端分离实现token登录验证和状态保存

2023-11-03

token可用于登录验证和权限管理。
大致步骤分为:

  1. 前端登录,post用户名和密码到后端。
  2. 后端验证用户名和密码,若通过,生成一个token返回给前端。
  3. 前端拿到token用vuex和localStorage管理,登录成功进入首页。
  4. 之后前端每一次权限操作如跳转路由,都需要判断是否存在token,若不存在,跳转至登录页。
  5. 前端之后的每一个对后端的请求都要在请求头上带上token,后端查看请求头是否有token,拿到token检查是否过期,返回对应状态给前端。
  6. 若token已过期,清除token信息,跳转至登录页。

前端

  • 登录页
  • <template>
    <!-- 登录页面 -->
      <div>
        <Row class="container">
          <i-col span="13" offset="2" class="login-img-box">
            <img src="static/img/logo/htmalllogo.png" alt="">
          </i-col>
          <i-col span="7" class="login-box">
            <div class="login-container">
              <div class="login-header">
                <p>欢迎登录</p>
              </div>
              <div class="form-box">
                <Form ref="formInline" :model="formDate" :rules="ruleInline">
                  <FormItem prop="username">
                      <i-input type="text" v-model="formDate.username" clearable size="large" placeholder="用户名">
                          <Icon type="person" slot="prepend"></Icon>
                      </i-input>
                  </FormItem>
                  <FormItem prop="password">
                      <i-input type="password" v-model="formDate.password" clearable size="large" placeholder="密码">
                          <Icon type="ios-locked-outline" slot="prepend"> </Icon>
                      </i-input>
                  </FormItem>
                  <FormItem>
                      <Button type="error" size="large" @click="handleSubmit('formInline')" long style="background-color: #2d8cf0;border-color:#2d8cf0">登录</Button>
                  </FormItem>
              </Form>
              </div>
            </div>
          </i-col>
        </Row>
      </div>
    
    </template>
    
    <script>
    import store from '@/vuex/store';
    import { mapMutations, mapActions } from 'vuex';
    export default {
      name: 'Login',
      data () {
        return {
          formDate: {
            username: '',
            password: ''
          },
          userToken:'',
          ruleInline: {
            username: [
              { required: true, message: '请输入用户名', trigger: 'blur' }
            ],
            password: [
              { required: true, message: '请输入密码', trigger: 'blur' },
              { type: 'string', min: 6, message: '密码不能少于6位', trigger: 'blur' }
            ]
          }
        };
      },
      methods: {
        ...mapMutations(['SET_USER_LOGIN_INFO','CHANGE_LOGIN']),
        ...mapActions(['login']),
        handleSubmit (name) {
          /*const father = this;
          console.log(this.formDate.username);
          this.$refs[name].validate((valid) => {
            if (valid) {
              this.login(father.formDate).then(result => {
                if (result) {
                  this.$Message.success('登录成功');
                  father.$router.push('/');
                } else {
                  this.$Message.error('用户名或密码错误');
                }
              });
            } else {
              this.$Message.error('请填写正确的用户名或密码');
            }
          });*/
          let params = {
            'username':this.formDate.username,
            'password':this.formDate.password
          };
    
          this.$axios.post("/user/login",params).then(res => {
            // 请求成功
    
            console.log(res.data);
            this.userToken = res.data.token;
            // 将用户token保存到vuex中
            this.CHANGE_LOGIN({ Authorization:this.userToken });
    
           
    
             this.$router.push('/');
          }).catch(error => {
            // 请求失败,
            this.$Message.error("登录失败!请检查登录信息是否正确!")
          });
        },
      },
      store
    };
    </script>
    
    

    vuex状态管理

  • /store/store.js
import * as mutations from './mutations';

const store = new Vuex.Store({
  state: {
   // 存储token
    Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : ''
}
 mutations
});

export default store;

/store/mutations.js

// 修改token,并将token存入localStorage
export const CHANGE_LOGIN = (state, user)=>{
  state.Authorization = user.Authorization;
  localStorage.setItem('Authorization', user.Authorization);
};
  • 路由守卫
    /router/index.js
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
  iView.LoadingBar.start();
  if (to.path === '/Register' || to.path === '/Login') {
    next();
  } else {
    let token = localStorage.getItem('Authorization');
    if (token === 'null' || token === '') {
      next('/Login');
    } else {
      next();
    }
  }
});
  • 主文件中注册并添加拦截器
    /main.js
// 添加请求拦截器,在请求头中加token
axios.interceptors.request.use(
    config => {
      if (localStorage.getItem('Authorization')) {
        config.headers.Authorization = localStorage.getItem('Authorization');
      }

      return config;
    },
    error => {
      return Promise.reject(error);
    });

测试页面 

 <template>
  <div >
<button @click="getTestData">带token获取数据</button>
            <p>{{testData}}</p>

  </div>
</template>

export default {
data () {
    return {
 testData: ''
    };
  },
  methods: {

getTestData(){
      this.$axios.post("/user/test").then(res => {
        // 请求成功
        console.log(res.data);
        this.testData = res.data;
      }).catch(error => {
        // 请求失败,
        this.$Message.error("登录失败!请检查登录信息是否正确!")
      });
    }
  }
};

 

后端 

  • token生成工具类
    /utils/TokenUtil.java
package com.project.htmall.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.project.htmall.model.User;

import java.util.Date;


/**
 * @version 1.0
 * @author:**
 * @date:2020/3/20
 */
public class TokenUtil {
  private static final long EXPIRE_TIME= 10*60*60*1000;
  private static final String TOKEN_SECRET="HuangTaoHTMALL";  //密钥盐

  /**
   * 签名生成
   * @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("username", user.getLoginName())
        .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("username: " + jwt.getClaim("username").asString());
      System.out.println("过期时间:      " + jwt.getExpiresAt());
      return true;
    } catch (Exception e){
      return false;
    }
  }
}
  • 拦截器类
    /interceptor/TokenInterceptor.java
package com.project.htmall.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.project.htmall.util.TokenUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @version 1.0
 * @author:**
 * @date:2020/3/20
 */
@Component
public class TokenInterceptor implements HandlerInterceptor {
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{
    if(request.getMethod().equals("OPTIONS")){
      response.setStatus(HttpServletResponse.SC_OK);
      return true;
    }
    response.setCharacterEncoding("utf-8");
    String token = request.getHeader("token");
    if(token != null){
      boolean result = TokenUtil.verify(token);
      if(result){
        System.out.println("通过拦截器");
        return true;
      }
    }
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json; charset=utf-8");
    try{
      JSONObject json = new JSONObject();
      json.put("msg","token verify fail");
      json.put("code","50000");
      response.getWriter().append(json.toJSONString());
      System.out.println("认证失败,未通过拦截器");
    }catch (Exception e){
      e.printStackTrace();
      response.sendError(500);
      return false;
    }
    return false;
  }
}
  • 配置拦截器
    /config/WebConfiguration.java
    注意最好写在一个配置类里,且WebMvcConfigurationSupport和WebMvcConfigurerAdapter不要同时存在
    这里包括处理跨域的配置,而且全部改为implements WebMvcConfigurer接口
  • package com.project.htmall.configurer;
    
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import com.alibaba.fastjson.support.config.FastJsonConfig;
    import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4;
    import com.project.htmall.core.ResultCode;
    import com.project.htmall.core.ServiceException;
    import com.project.htmall.core.Result;
    import com.project.htmall.interceptor.TokenInterceptor;
    import org.apache.commons.codec.digest.DigestUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerExceptionResolver;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.NoHandlerFoundException;
    import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.nio.charset.Charset;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.concurrent.Executors;
    
    /**
     * Spring MVC 配置
     */
    @Configuration
    public class WebConfiguration implements WebMvcConfigurer {
    
        private final Logger logger = LoggerFactory.getLogger(WebConfiguration.class);
        @Value("${spring.profiles.active}")
        private String env;//当前激活的配置文件
    
        private TokenInterceptor tokenInterceptor;
    
      //构造方法
      public WebConfiguration(TokenInterceptor tokenInterceptor){
        this.tokenInterceptor = tokenInterceptor;
      }
    
      //解决跨域问题
      @Override
      public void addCorsMappings(CorsRegistry registry) {
          /*addMapping:允许所以映射
          allowedHeaders:允许所以请求头
          allowedMethods:允许所以请求方式,get、post、put、delete
          allowedOrigins:允许所以域名访问
          allowCredentials:允许携带cookie参数*/
        registry.addMapping("/**").allowedHeaders("*")
          .allowedMethods("*")
          .allowedOrigins("*")
          .allowCredentials(true);
      }
    
    
    
    
    
    
    
    
    
    
    
      @Override
      public void configureAsyncSupport(AsyncSupportConfigurer configurer){
        configurer.setTaskExecutor(new ConcurrentTaskExecutor(Executors.newFixedThreadPool(3)));
        configurer.setDefaultTimeout(30000);
      }
    
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        List<String> excludePath = new ArrayList<>();
        //排除拦截,除了注册登录(此时还没token),其他都拦截
        excludePath.add("/user/register");  //登录
        excludePath.add("/user/login");     //注册
        excludePath.add("/static/**");  //静态资源
        excludePath.add("/assets/**");  //静态资源
    
        registry.addInterceptor(tokenInterceptor)
          .addPathPatterns("/**")
          .excludePathPatterns(excludePath);
        WebMvcConfigurer.super.addInterceptors(registry);
      }
    
    
      //使用阿里 FastJson 作为JSON MessageConverter
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            FastJsonHttpMessageConverter4 converter = new FastJsonHttpMessageConverter4();
            FastJsonConfig config = new FastJsonConfig();
            config.setSerializerFeatures(SerializerFeature.WriteMapNullValue,//保留空的字段
                    SerializerFeature.WriteNullStringAsEmpty,//String null -> ""
                    SerializerFeature.WriteNullNumberAsZero);//Number null -> 0
            converter.setFastJsonConfig(config);
            converter.setDefaultCharset(Charset.forName("UTF-8"));
            converters.add(converter);
        }
    
    
        //统一异常处理
       /* @Override
        public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
            exceptionResolvers.add(new HandlerExceptionResolver() {
                public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
                    Result result = new Result();
                    if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误”
                        result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
                        logger.info(e.getMessage());
                    } else if (e instanceof NoHandlerFoundException) {
                        result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
                    } else if (e instanceof ServletException) {
                        result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
                    } else {
                        result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
                        String message;
                        if (handler instanceof HandlerMethod) {
                            HandlerMethod handlerMethod = (HandlerMethod) handler;
                            message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
                                    request.getRequestURI(),
                                    handlerMethod.getBean().getClass().getName(),
                                    handlerMethod.getMethod().getName(),
                                    e.getMessage());
                        } else {
                            message = e.getMessage();
                        }
                        logger.error(message, e);
                    }
                    responseResult(response, result);
                    return new ModelAndView();
                }
    
            });
        }*/
    
    }
    

     

  • 测试:

 

 

 

 

优化:

当登录成功,生成的Tokent中携带用户标识(如userId),并且当请求时携带的token通过验证后,取出token中的用户标识,不能简单的使用一个静态变量来保存取出来的用户标识,因为这样容易造成紊乱。所以使用线程内部存储类,ThreadLocal。

这样一来,便无需使用Session,

其实在用axios解决跨域问题后,是解决了Session会话问题(解决了跨域问题后,SessionId会像以前一样随着请求带过来,但是Session是存储在内存中的,如果始终访问的是一台机器还好),
但如果有多台服务器,登录时访问的是服务器A,Session信息也存在A,若接下来有请求访问其他服务器,此时其他服务器上没有相关的Session信息,则就会为空。

 

threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据,官方解释如下。

 

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 */

/ **

  *此类提供线程局部变量。 这些变量不同之处在于它们的访问对应对象是每个线程(通过其访问)

  * {@code get}或{@code set}方法)有自己的,独立初始化的

  *变量的副本。 {@code ThreadLocal}实例通常是私有的

  *希望将状态与线程相关联的类中的静态字段(例如,

  *用户ID或交易ID)。

  * /

针对以上优化:

新建一个

UserContextHolder类


import java.util.List;

/**
 * token验证通过后,将token中的用户标识,如userId取出来并赋值给该类中的一个 线程变量
 * @version 1.0
 * @author:HuangTao
 * @date:2020/3/27
 */
public class UserContextHolder {
 // threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。
  //ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
  //当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
  private static final ThreadLocal<Long> currentUserId = new ThreadLocal<Long>();

  public static void setCurrentUserId(Long userId){
     currentUserId.set(userId);
  }

  public static Long getCurrentUserId(){
    return currentUserId.get();
  }

  public static void clearCurrentUserId(){
    currentUserId.get();
  }

}

 

然后在验证通过时,取出token中的值,并且赋值给线程存储变量

TokenUtil.java
package com.project.htmall.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.project.htmall.core.UserContextHolder;
import com.project.htmall.model.User;

import java.util.Date;


/**
 * @version 1.0
 * @author:HuangTao
 * @date:2020/3/20
 */
public class TokenUtil {
  //过期时间为3小时
  private static final long EXPIRE_TIME= 3*60*60*1000;
  private static final String TOKEN_SECRET="HTMALL";  //密钥盐


  /*JWT包含了三部分:
  Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)
  Payload 负载 (载荷就是存放有效信息的地方。)
  Signature 签名/签证*/

  /**
   * 签名生成
   * @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("huangtao")
        .withClaim("currentUserName", user.getLoginName())
        .withClaim("currentUserId",user.getId())
        .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("huangtao").build();
      DecodedJWT jwt = verifier.verify(token);
      //认证通过之后,将token中用户标识userId赋值给一个线程内部存储变量
      UserContextHolder.setCurrentUserId(jwt.getClaim("currentUserId").asLong());


      System.out.println("认证通过:");
      System.out.println("currentUserName: " + jwt.getClaim("currentUserName").asString());
      System.out.println("currentUserId: " + jwt.getClaim("currentUserId").asLong());
      System.out.println("过期时间:      " + jwt.getExpiresAt());
      return true;
    } catch (Exception e){
      return false;
    }finally {
      //若出现异常,应该清理上面的线程内部存储变量
      UserContextHolder.clearCurrentUserId();
    }
  }
}

注意在JWT取值时,其类型要准确,如此处我的“currentUserId”类型为Long,若是用.asString()则会取不到值。

 

 

在需要取得用户标识的地方,直接取就好了,如:

 @GetMapping("/getCurrentUserInfo")
  public Result getInfoById(){
    User user = new User();
    user.setId(UserContextHolder.getCurrentUserId());
    System.out.println("获取当前用户信息:"+UserContextHolder.getCurrentUserId());
    user = userService.findOne(user);
    if(user!=null){
      return ResultGenerator.genSuccessResult(user);
    }
    return ResultGenerator.genFailResult("数据不存在!");
  }

 

 

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Springboot+Vue前后端分离实现token登录验证和状态保存 的相关文章

随机推荐

  • OLED显示模块驱动原理及应用

    OLED显示模块驱动原理及应用 本文以中景园OLED显示模块为例 介绍模块的应用和OLED显示及驱动的基本原理 文中介绍了显示模块 SSD1306驱动芯片以及GT20L16S1Y字库芯片相关技术内容及原理 并加上了作者的理解和应用记录 一
  • 【计算机视觉

    文章目录 一 A Dataset of Multispectral Potato Plants Images 二 Active Terahertz 三 Apron Dataset 四 AquaTrash 五 BBBC041 P vivax
  • 电脑开机转圈圈不停-转圈后系统各窗口闪退-桌面黑屏解决探讨

    如题 如果在电脑出现开机黑屏 进入系统后仍系统闪退等问题后 那么可能是因为某些系统文件或网络相关的配置文件损坏或配置错误导致的 通过重置 Winsock 目录 可以恢复网络连接的正常运行 同时也可以修复与网络相关的系统问题 从而才能使电脑恢
  • (20200920)编程每日一题_C程序设计_计算整数段和

    1 描述 输出从整数A到整数B的所有整数以及这些数的和 来源 https pintia cn problem sets 14 problems 794 2 输入格式 输入在一行中给出2个整数A和B 其中 100 A B 100 其间以空格分
  • 改进排序算法:快速排序(对冒泡排序的改进)

    快速排序的基本思想 通过一趟排序将待排序记录分割成独立的两部分 其中一部分记录的关键字均比另一部分记录的关键字小 则可分别对这两部分记录继续进行排序 以达到整个序列有序的目的 Partition函数的作用 选取当中的一个关键字 将它放到一个
  • 150名打工人被AI判定效率低遭开除,“属实是人工智能,能治工人“

    人工智能 能治工人 惨还是打工人惨 谁能想到 最近又有俄罗斯一家游戏支付服务公司直接用AI算法开除了150名员工 判断依据就是通过后台数据看员工有没有 摸鱼 老板给这批 不幸 的打工人发的解雇邮件是这么写的 您收到这封电子邮件是因为我的大数
  • weex:使用list实现下拉刷新和上拉加载更多,书籍+视频+学习笔记+技能提升资源库

    this refreshing true setTimeout gt this lists 1 2 3 4 5 this refreshing false 2000 onpullingdown event modal toast messa
  • 密码学基础

    密码学基本概念 密码学 Cryptology 是结合数学 计算机科学 电子与通信等学科于一体的交叉学科 研究信息系统安全的科学 起源于保密通信技术 具体来讲 研究信息系统安全保密和认证的一门科学 密码编码学 通过变换消息 对信息编码 使其保
  • 微信小程序上线流程

    1 登陆小程序后台 登陆地址 https mp weixin qq com 2 将项目中测试appid换成正式appid 正式appid 小程序后台 gt 开发管理 gt 开发设置 测试appid 小程序开发工具 gt 右侧详情 gt 基本
  • 软件系统概要设计的三大要素

    概要设计是一个设计师根据用户交互过程和用户需求来形成交互框架和视觉框架的过程 其结果往往以反映交互控件布置 界面元素分组以及界面整体板式的页面框架图的形式来呈现 这是一个在用户研究和设计之间架起桥梁 使用户研究和设计无缝结合 将对用户目标与
  • Python反编译exe文件为py文件

    目的如标题 首先要安装python环境 1 下载pyinstxtractor py文件 地址 链接 https pan baidu com s 1PwQim8B8yau5 MWgKzTKeg pwd 2023 提取码 2023 2 将pyi
  • linux-三剑客(sed、awk、grep)

    三剑客sed awk grep 三剑客与正则表达式是什么关系呢 正则表达式 grep 文本过滤工具 sed 操作 过滤和转换文本内容的强大工具 组合多个表达式 引用 定界符 命令中字符 在sed中作为定界符使用 也可以使用任意的定界符 se
  • 小程序canvas上绘制图片真机不显示

    小程序在画布上绘制图片时真机不显示 分析 小程序的画布不能绘制网络图片 所以需要wx downloadFile 或者wx getImageInfo 方法把网络图片加载到本地再进行绘制 使用wx downloadFile获取到图片的临时路径
  • wireshark 抓取 https

    背景介绍 首先 https双向加密的 如果需要解密数据 除非知道 客户端 服务器 任何一方私钥 否则无法解密 常用手段 基于中间人攻击抓包 使用fiddler 或 charles 实现的是中间人代理 将客户端私钥改为 fiddler 或 c
  • Roslyn介绍

    介绍 一般来说 编译器是一个黑箱 源代码从一端进入 然后箱子中发生一些奇妙的变化 最后从另一端出来目标文件或程序集 编译器施展它们的魔法 它们必须对所处理的代码进行深入的理解 不过相关知识不是每个人都需要知道 除了实现编译器的大法师 因此在
  • 李宏毅机器学习:self-attention(自注意力机制)和transformer及其变形

    目录 self attention self attention的输入 文字处理领域 语音领域 图 自注意力机制的输出 输出序列长度与输入序列相同 输出序列长度为1 模型决定输出序列长度 Self attention 原理 self att
  • codeforces Epic Game 题解

    Simon and Antisimon play a game Initially each player receives one fixed positive integer that doesn t change throughout
  • MOT学习 - 卡尔曼滤波

    参考资料 https www bzarg com p how a kalman filter works in pictures 协方差矩阵 https youzipi blog csdn net article details 48788
  • 关于Qt和C++中的反射机制实现与运用(2)

    看到一个很好的实例 这里就转载过来了 原文地址 C 反射机制 一 前言 Java有着一个非常突出的动态相关机制 Reflection 用在Java身上指的是我们可以于运行时加载 探知 使用编译期间完全未知的classes 换句话说 Java
  • Springboot+Vue前后端分离实现token登录验证和状态保存

    token可用于登录验证和权限管理 大致步骤分为 前端登录 post用户名和密码到后端 后端验证用户名和密码 若通过 生成一个token返回给前端 前端拿到token用vuex和localStorage管理 登录成功进入首页 之后前端每一次