项目中使token

如果项目架构采用前后端分离,并采用分布式架构,通过定义接口API,与前端进行数据交互,前端通过html前行实现。若加入移动端(Andriod,ios)实现,可直接使用API接口实现即可。由于该项目进行前后端分离,session就没有意义了。并且移动端也是无法使用session的。那么需要使用token进行session管理,通过搭建一个认证系统负责用户身份验证,并进行这个系统token的维护和管理。

1.1 用户表的设计

认证系统除了用户的自动注册意外,还有可能是第三方登陆(微信,qq,微博等)。如果用户使用微信登陆成功后,这需要进行账号合并,进行数据同步。具体的业务流程如下:对第一次使用第三方账号登陆系统的 用户(不注册,直接hi用微信登陆),那么系统会给他生成一个临时账号(userCode)和一个临时密码(userPassword),并且用户表需要记录微信的ID(微信接口返回),以便于微信用户下次登陆系统时继续使用微信登陆而不绑定注册的手机号或者邮箱。并且登陆成功后,需要把用户信息放到token里进行统一的管理。

那么用户表必须包含如下的字段:

1. id:主键ID

2. userType:用户类型(如果时第三方登陆的话,系统为自动生成唯一的账号密码;自动注册用户这位邮箱、手机号)

3. userPassword 用户密码

4. flatId (自动注册:用户的主键id

第三方登陆(qqid,微信id,微博id):该字段表示第三方登陆账号的唯一标识,token使用)

那么第二次第三方登陆(没有进行账号绑定),此时校验用户身份,需要使用第三方账号和flatId联合校验(为了避免不同平台返回的平台ID(flatId)出现一致的情况),所以不管是第三方登陆,还是自动注册登陆,token里面放的结构数据内容需要一致,并且在认证系统中需要实现自由平台的token维护和第三方账号登陆的维护。

1.2 token的数据结构及内容

token的数据结构为key-value,具体内容如下:

1. key:token,其设计原则:必须保证整个系统中唯一存在,根据不同的客户端(PC、移动端),为了便于同意管理和维护,token的设计生成算法如下:

token:PC/mobile-userCode(加密)-id-date-6位随机字符串

代码:

/**
 *实体类
 */
public class User {
 private Integer id;//主键id
 private String userCode;//若是第三方登录,系统将自动生成唯一账号;自注册用户则为邮箱或者手机号
 private String userPassword;//若是第三方登录,系统将自动生成唯一密码;自注册用户则为自定义密码
 private String userType;//用户类型(标识:0 自注册用户 1 微信登录 2 QQ登录 3 微博登录)
 private String flatId;//平台ID(根据不同登录用户,进行相应存入:自注册用户主键ID、微信ID、QQID、微博ID)
 private Integer activated;//是否激活(0:否 1:是)
 public Integer getId() {
 return id;
 }

 public Integer getActivated() {
 return activated;
 }

 public void setActivated(Integer activated) {
 this.activated = activated;
 }

 public void setId(Integer id) {
 this.id = id;
 }

 public String getUserCode() {
 return userCode;
 }

 public void setUserCode(String userCode) {
 this.userCode = userCode;
 }

 public String getUserPassword() {
 return userPassword;
 }

 public void setUserPassword(String userPassword) {
 this.userPassword = userPassword;
 }

 public String getUserType() {
 return userType;
 }

 public void setUserType(String userType) {
 this.userType = userType;
 }

 public String getFlatId() {
 return flatId;
 }

 public void setFlatId(String flatId) {
 this.flatId = flatId;
 }

MD5加密:

package com.kgc.utils.common;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

public class MD5 {

 public static String getMd5(String plainText,int length) {
 try {
 MessageDigest md = MessageDigest.getInstance("MD5");
 md.update(plainText.getBytes());
 byte b[] = md.digest();

 int i;

 StringBuffer buf = new StringBuffer("");
 for (int offset = 0; offset < b.length; offset++) {
 i = b[offset];
 if (i < 0) {
 i += 256;
 }
 if (i < 16) {
 buf.append("0");
 }
 buf.append(Integer.toHexString(i));
 }
 // 32位
 // return buf.toString();
 // 16位
 // return buf.toString().substring(0, 16);

 return buf.toString().substring(0, length);
 } catch (NoSuchAlgorithmException e) {
 e.printStackTrace();
 return null;
 }

 }

 public static int getRandomCode(){
 int max=9999;
 int min=1111;
 Random random = new Random();
 return random.nextInt(max)%(max-min+1) + min;
 }
 public static void main(String[] args) {
 System.out.println(MD5.getMd5("helloadsfdsffsf",6));
 System.out.println(getRandomCode());
 }

}

生成token的代码:

/**
 * 生成token
 *
 * @param User
 * @param userAgent 判断是移动端还是PC端
需要controller传入 HttpServletRequest request
String userAgent = request.getHeader("user-agent");
 * @return
 */
public String createToken(User ser, String userAgent) throws IOException {
 StringBuffer token=new StringBuffer();
 token.append("token:");
 UserAgentInfo userAgentInfo = UserAgentUtil.getUasParser().parse(userAgent);
 //获取访问设备并拼接
 if(userAgentInfo.getDeviceType().equals(UserAgentInfo.UNKNOWN)){
 if(UserAgentUtil.CheckAgent(userAgent)){
 token.append("MOBILE-");
 }else {
 token.append("PC-");
 }
 }else if(userAgentInfo.getDeviceType().equals("Personal computer")){
 token.append("PC-");
 }else {
 token.append("MOBILE-");
 }
 token.append(MD5.getMd5(ser.getUserCode(),32)+"-");
 token.append(user.getId()+"-");
 token.append(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+"-");
 token.append(MD5.getMd5(userAgent,6));

 return token.toString();
}

2.value:存储用户登陆的信息(数据内容是json格式)

id userCode userPassword userType flatId activated

1.3 token的有效期维护

基于系统的安全性考虑,需要设置token的有效期,为了维护token的有效期,需要把token放到redis进行维护管理。对于不同的客户端(PC端,移动端)token的有效期设置有所不同。

1.3.1 PC端

token的有效期为两个小时,如果两个小时内的token没有进行置换的话,就会自动在该redis里清除token了,那么当用户再次放送请求时,则会提示token失效,情重新登陆。此处应注意:前端需要自动挂你token的生命周期,token存在cookie,web的安全性比较差。

java代码:

controller

@RequestMapping(value = "/api")
@RestController
public class LoginController {

 @Resource
 private TokenService tokenService;

 /**
 * 用户登录
 *
 * @param name
 * @param password
 * @return
 */
 @RequestMapping(value = "/dologin", method = RequestMethod.POST, produces = "application/json")
 public Dto dologin(@RequestParam(value = "name") String name,
 @RequestParam(value = "password") String password, HttpServletRequest request) {
 try {
 String userAgent = request.getHeader("user-agent");
 return tokenService.dologin(name, password,userAgent);
 } catch (Exception e) {
 e.printStackTrace();
 return DtoUtil.returnFail("系统异常", ErrorCode.AUTH_UNKNOWN);
 }
 }

/**


* 用户注销


* @param


* @return


*/


@RequestMapping(value ="/logout",method = RequestMethod.GET,produces = "application/json")


public Dto logout(HttpServletRequest request){


try {


return tokenService.logout(request.getHeader("token"));


}catch (Exception e){


e.printStackTrace();


return DtoUtil.returnFail("系统异常", ErrorCode.AUTH_UNKNOWN);


}


}










/**


* 客户端置换token


* @param request


* @return


*/


@RequestMapping(value ="/retoken",method = RequestMethod.POST,produces = "application/json")


public Dto retoken(HttpServletRequest request){




try {


return tokenService.replacetoken(request.getHeader("token"),request.getHeader("user-agent"));


} catch (Exception e) {


e.printStackTrace();


return DtoUtil.returnFail("系统异常", ErrorCode.AUTH_UNKNOWN);


}


}




}





service代码:

public interface TokenService {
 /**
 * 会话时间
 */
 public final static int SESSION_TIMEOUT=60*2*60;
 /**
 * 置换保护时间
 */
 public final static int REPLACETOKEN_PROTECTION_TIMEOUT=60*60;
 /**
 * 旧的token延迟时间
 */
 public final static int REPLACE=60*2;
 //用户登录
 public Dto dologin(String userCode, String userPassword,String userAgent) throws Exception;

 //用户注销
 public Dto logout(String token) throws Exception;

 //客户端置换token
 public Dto replacetoken(String token,String userAgent) throws Exception;

}

impl

@Service("LoginService")
public class TokenServerImpl implements TokenService {
 @Resource
 private UserMapper UserMapper;
 @Resource
 private RedisAPI redisAPI;

 /**
 * 登录业务
 *
 * @param userCode
 * @param userPassword
 * @return
 * @throws Exception
 */
 @Override
 public Dto dologin(String userCode, String userPassword, String userAgent) throws Exception {
 Map<String, Object> userMap = new HashMap<>();
 userMap.put("userCode", userCode);
 user user = userMapper.getListByMap(userMap).get(0);
 //用户是否存在
 if (EmptyUtils.isNotEmpty(user)) {
 //判断用户密码是否正确
 if (DigestUtil.hmacSign(userPassword, "kgc").equals(user.getUserPassword())) {
 String tokenString = createToken(user, userAgent);
 //存到缓存服务器中
 redisAPI.set(tokenString, JSONObject.toJSONString(user));
 System.out.println("tokenString=="+tokenString);
 //返回给前端
 TokenVO tokenVO = new TokenVO(tokenString, Calendar.getInstance().getTimeInMillis() + SESSION_TIMEOUT * 1000, Calendar.getInstance().getTimeInMillis());
 return DtoUtil.returnDataSuccess(tokenVO);
 } else {
 return DtoUtil.returnFail("用户密码错误", ErrorCode.AUTH_PARAMETER_ERROR);
 }
 } else {
 return DtoUtil.returnFail("用户不存在", ErrorCode.AUTH_USER_ALREADY_NOTEXISTS);
 }

 }

 @Override
 public Dto logout(String token) throws Exception {
 //删除服务端
 redisAPI.del(token);
 return DtoUtil.returnSuccess();
 }

 /**
 * 客户端置换token
 * @param token
 * @return
 * @throws Exception
 */
 @Override
 public Dto replacetoken(String token,String userAgent) throws Exception {
 //判断token是否存在
 if (!redisAPI.exists(token)){
 return DtoUtil.returnFail("token不存在",ErrorCode.AUTH_TOKEN_INVALID);
 }
 String [] tokens=token.split("-");
 SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMssHHmmss");
 Date startDate=simpleDateFormat.parse(tokens[3]);
 String format=simpleDateFormat.format(new Date());
 long logtime=simpleDateFormat.parse(format).getTime()-startDate.getTime();
 if (logtime<REPLACETOKEN_PROTECTION_TIMEOUT*1000){
 return DtoUtil.returnFail("token处于保护时间,禁止替换",ErrorCode.AUTH_REPLACEMENT_FAILED);
 }
 //以上情况都符合
 User user=JSON.parseObject(redisAPI.get(token),User.class);
 //生成新的token
 String newtoken=createToken(user,userAgent);
 //覆盖新的请求,减少过期时间
 redisAPI.set(token,JSONObject.toJSONString(user),REPLACE);
 redisAPI.set(newtoken,JSONObject.toJSONString(user),SESSION_TIMEOUT);
 //返回给前端
 TokenVO tokenVO = new TokenVO(newtoken, Calendar.getInstance().getTimeInMillis() + SESSION_TIMEOUT * 1000, Calendar.getInstance().getTimeInMillis());

 return DtoUtil.returnDataSuccess(tokenVO);
 }

 /**
 * 生成token
 *
 * @param User
 * @param userAgent 判断是移动端还是PC端
 * @return
 */
 public String createToken(User user, String userAgent) throws IOException {
 StringBuffer token=new StringBuffer();
 token.append("token:");
 UserAgentInfo userAgentInfo = UserAgentUtil.getUasParser().parse(userAgent);
 //获取访问设备并拼接
 if(userAgentInfo.getDeviceType().equals(UserAgentInfo.UNKNOWN)){
 if(UserAgentUtil.CheckAgent(userAgent)){
 token.append("MOBILE-");
 }else {
 token.append("PC-");
 }
 }else if(userAgentInfo.getDeviceType().equals("Personal computer")){
 token.append("PC-");
 }else {
 token.append("MOBILE-");
 }
 token.append(MD5.getMd5(user.getUserCode(),32)+"-");
 token.append(user.getId()+"-");
 token.append(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+"-");
 token.append(MD5.getMd5(userAgent,6));

 return token.toString();
 }
}

redis:

import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import javax.annotation.Resource;

@Component
public class RedisAPI {
 @Resource
 private JedisPool jedisPool;

 /**
 * 以键值对的方式保存数据到redis
 *
 * @param key
 * @param value
 */
 public void set(String key, String value) {
 //获取连接
 Jedis jedis = jedisPool.getResource();
 try {
 String result = jedis.set(key, value);
 // 资源还回到连接池当中
 //返还到连接池
 jedisPool.returnResource(jedis);

 } catch (Exception e) {
 e.printStackTrace();
 //销毁资源
 jedisPool.returnBrokenResource(jedis);

 }
 }

 /**
 * 以键值对的方式保存数据到redis
 *
 * @param key
 * @param value
 * @param expire 时间 单位[秒]
 */
 public void set(String key, String value, int expire) {
 //获取连接
 Jedis jedis = jedisPool.getResource();
 try {
 String result = jedis.setex(key, expire, value);
 // 资源还回到连接池当中
 jedisPool.returnResource(jedis);
 } catch (Exception e) {
 e.printStackTrace();
 //销毁资源
 jedisPool.returnBrokenResource(jedis);
 }
 }

 /**
 * 取值
 *
 * @param key
 */
 public String get(String key) {
 //获取连接
 Jedis jedis = jedisPool.getResource();
 try {
 String result = jedis.get(key);
 // 资源还回到连接池当中
 jedisPool.returnResource(jedis);
 return result;
 } catch (Exception e) {
 e.printStackTrace();
 //销毁资源
 jedisPool.returnBrokenResource(jedis);
 return null;
 }
 }

 /**
 * 获取剩余秒数
 *
 * @param key
 */
 public Long ttl(String key) {
 //获取连接
 Jedis jedis = jedisPool.getResource();
 try {
 Long result = jedis.ttl(key);
 // 资源还回到连接池当中
 jedisPool.returnResource(jedis);
 return result;
 } catch (Exception e) {
 e.printStackTrace();
 //销毁资源
 jedisPool.returnBrokenResource(jedis);
 return null;
 }
 }

 /**
 * 判断key是否存在
 *
 * @param key
 */
 public Boolean exists(String key) {
 //获取连接
 Jedis jedis = jedisPool.getResource();
 try {
 System.out.println("key=========="+key);
 Boolean result = jedis.exists(key);
 // 资源还回到连接池当中

 jedisPool.returnResource(jedis);
 return result;
 } catch (Exception e) {
 e.printStackTrace();
 //销毁资源
 jedisPool.returnBrokenResource(jedis);
 return false;
 }
 }

 /**
 * 删除
 *
 * @param key
 */
 public Long del(String key) {
 //获取连接
 Jedis jedis = jedisPool.getResource();
 try {
 Long result = jedis.del(key);
 // 资源还回到连接池当中
 jedisPool.returnResource(jedis);
 return result;
 } catch (Exception e) {
 e.printStackTrace();
 //销毁资源
 jedisPool.returnBrokenResource(jedis);
 return null;
 }
 }
}
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

@ApiModel(value ="TokenVO",description = "用户认证凭证信息")
public class TokenVO {
 @ApiModelProperty("用户认证凭据")
 private String token;
 @ApiModelProperty("过期时间,单位:毫秒")
 private long expTime;
 @ApiModelProperty("生成时间,单位:毫秒")
 private long genTime;

 public TokenVO() {
 }

 public TokenVO(String token, long expTime, long genTime) {
 this.token = token;
 this.expTime = expTime;
 this.genTime = genTime;
 }

 public String getToken() {
 return token;
 }

 public void setToken(String token) {
 this.token = token;
 }

 public long getExpTime() {
 return expTime;
 }

 public void setExpTime(long expTime) {
 this.expTime = expTime;
 }

 public long getGenTime() {
 return genTime;
 }

 public void setGenTime(long genTime) {
 this.genTime = genTime;
 }
}

1.3.2 移动端

token永不失效,修改密码后需要置换token。由于移动端的token不需要过期,只有但PC页面进行密码修改后,移动端才会推出重新登录,或者当移动端进行密码修改后,用户也不需要进行推出登录,知道在redis中更新该token中的密码即可。

1.4.2 Token 置换

Token 置换规则定义:前端获取 Token 的 1.5 时后可进行 Token 置换,若在最后的半个小时内,客户端发出请求,则会进行 Token 置换,拿到重新生成的 Token(包括:token(key)、

生成时间、失效时间),若客户端在最后的半个小时内没有发送任何请求,那么两个小时后自动过期,即:该 Token 自动从 Redis 里清除,用户须重新登录。 需要注意事项:

1> 不论是最后半个小时的置换时间还是 Token 的 2 个小时有效期,都是根据系统的业务需求所设计的策略方案。

2> 为了防止客户端恶意的进行 Token 置换,需要保证生成 Token 后的 1 个小时内不允许置换。

3> 需要保证客户端传递有效的 Token 进行置换。

4> 为了解决页面的并发问题,在进行置换 Token 时,生成新 Token,但是旧 Token 不能立即失效,应设置为置换后的时间延长 2 分钟。

token的使用

在 Controller 的处理方法中通过 request.getHeader("token")来获取 token 字符串,为了方便进行 Token 的验证,提供统一的 ValidationToken.java,该工具类主要负责通过传入的 token(key) 去 Redis 里 进 行 value 的 查 找 (User currentUser =validationToken.getCurrentUser(token);),若找到相应的 value,则返回 currentUser(当前用户),若无,则返回 null。

/**
 * Token验证
 *
 */
@Component
public class ValidationToken {

 private Logger logger = Logger.getLogger(ValidationToken.class);

 private @Resource
 RedisAPI redisAPI;

 public RedisAPI getRedisAPI() {
 return redisAPI;
 }
 public void setRedisAPI(RedisAPI redisAPI) {
 this.redisAPI = redisAPI;
 }
 public ser getCurrentUser(String tokenString){
 //根据token从redis中获取用户信息
 /*
 test token:
 key : token:1qaz2wsx
 value : {"id":"100078","userCode":"myusercode","userPassword":"78ujsdlkfjoiiewe98r3ejrf","userType":"1","flatID":"10008989"}

 */
 User ser = null;
 if(null == tokenString || "".equals(tokenString)){
 return null;
 }
 try{
 String userInfoJson = redisAPI.get(tokenString);
 ser = JSONObject.parseObject(userInfoJson,User.class);
 }catch(Exception e){
 ser = null;
 logger.error("get userinfo from redis but is error : " + e.getMessage());
 }
 return ser;
 }

}

后端

Auth 系统需要提供 API 如下:

1> 生成 Token

该接口返回的数据内容包括:Token 的 key(注:需要对敏感信息进行加密处理)、 Token 的生成时间、Token 的失效时间(注:过期时间减去生成时间一定是两个小时)

2> Token 置换

该接口返回新 Token。实现过程中需要注意如下几点:

a) 生成 Token 后的 1 个小时内不允许置换(注:主要是为了防止客户端恶意的进行 Token 置换)

b) 由于需要保证客户端传递的置换 Token 为真实存在并有效的,故需要在该

API 方法内首先判断 Token 是否有效。

c) 在进行置换 Token,生成新 Token,旧 Token 不能立即失效,应设置为置换后的时间延长 2 分钟。

前端

1> 登录成功后,接收 Token 放入 cookie 中,请求的时候从 cookie 中取出放入到 header 里,如下:

$.ajax({ headers:{

Accept:"application/json;charset=utf-8",

Content-Type:"application/json;charset=utf-8",

//从 cookie 中获取

token:"token:PC-3066014fa0b10792e4a762-23-20170531133947-4f6496"

},

type:"post",

.....

})

2> 负责服务器时间同步(根据 API 返回的 Token 生成时间、失效时间进行同步)

3> 置换 Token 需要同步处理,即:保证只有一个请求置换 Token

原文地址:https://www.cnblogs.com/wangshuang123/p/11357071.html

时间: 2024-10-15 06:46:31

项目中使token的相关文章

在Django项目中使用富文本编辑器

1 开发要点 现在网上有很多的富文本编辑器,包括Markdown.tinymce.UEditor.KindEditor.ckeditor等等.在项目中使用这些编辑器主要有以下几个问题: 编辑页面 在HTML页面渲染编辑器: 定制编辑器的功能,比如有哪些文本样式.图片上传.代码插入: 定制编辑器的样式,指的是编辑器整体的样式,比如高度.宽度.显示位置等等: 预览内容: 获取内容: 显示页面 显示内容: 2 Django APP 下表列出一些常用的APP,它们都可以在GitHub上找的到,链接见下文

在SpringBoot的Web项目中使用于Thymeleaf

Thymeleaf是一个用于web和独立环境的现代服务器端Java模板引擎. Thymeleaf的主要目标是为您的开发工作流带来优雅的自然模板——HTML,它可以在浏览器中正确显示,也可以作为静态原型工作,允许在开发团队中进行更强的协作. 有了Spring Framework的模块.与您最喜欢的工具的大量集成,以及插入您自己的功能的能力,Thymeleaf是现代HTML5 JVM web开发的理想选择——尽管它可以做的还有很多. 以上来自于官方的介绍. 1.新建一个SpringBoot的Web项

在前后端分离的SpringBoot项目中集成Shiro权限框架

项目背景 公司在几年前就采用了前后端分离的开发模式,前端所有请求都使用ajax.这样的项目结构在与CAS单点登录等权限管理框架集成时遇到了很多问题,使得权限部分的代码冗长丑陋,CAS的各种重定向也使得用户体验很差,在前端使用vue-router管理页面跳转时,问题更加尖锐.于是我就在寻找一个解决方案,这个方案应该对代码的侵入较少,开发速度快,实现优雅.最近无意中看到springboot与shiro框架集成的文章,在了解了springboot以及shiro的发展状况,并学习了使用方法后,开始在网上

silverlight中使用富文本编辑器

因为项目的需要在silverlight项目中使用富文本编辑器,好用的在线富文本编辑器有很多Fckeditor,百度的ueditor,但是这些富文本编辑器都是html的,想在silverlight中使用必须承载html页面再使用这些富文本编辑器. 之前找到了一个在silverliht中承载html页面的第三方控件htmlhost,但是这个控件有一个不好的地方时,用了只有silverlight项目中所有服务端控件都不能输入中文. 最近找到了一个大牛自己写的一个控件,能够满足我们的需要,自己改造之后放

My97DatePicker时间控件在项目中的应用

一.下载My97DatePicker的压缩包My97DatePicker.rar,解压. 注:My97DatePicker最新版本有开发包,项目中使用时删掉,以便节省空间,提高程序的运行效率. 二.在MyEclipse中创建web project名为Wdate,将解压后的My97DatePicker文件拷贝到WebRoot目录下. 三.jsp页面中引入WdatePicker.js即可. <script  language="javascript" type="text/

18年7月实训unity项目中遇到的问题记录

1.在一个项目中做好的UI做成预置体后在另一个项目中使用时发现无法响应鼠标的输入事件 解决:在使用预置体的项目中添加EventSystem. 2.使用DontDestroyOnLoad()来避免场景切换后GameObject被销毁存在的问题 假如A场景中有一个GameObject名为player,为了不让player在场景切换到B时消失而使用DontDestroyOnLoad(player),这样会导致场景再次切换回A时,A场景中同时出现两个player,并且两个player在场景切换后都不会消

一个项目中如果有重复代码,如何变成一个标签使其通用?

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="index.aspx.cs" Inherits="Web.index" %> Page:所使用的是page页面,也就是所谓的aspx页面 AutoEventWireup:是否自动关联某些特殊事件(例如:Page_Load(),)默认true CodeBehind:属性并不是一个真正的 ASP.NET

ARC 与非ARC 之间的转换,以及如何使一个项目中,ARC与非ARC共存

1,非ARC 转 ARC的操作 XCode 的 Edit -- Refactor -- Convert to Object-C ARC (注意,一般在一个大项目中,很少直接使用此方法,其正确率有待考虑,毕竟手动内存管理较为复杂,如果出现错误,很难排查) 2,ARC 与 非ARC共存 需求来源:当我们在项目中使用一些古老的框架时,该框架就可能使用手动内存管理.而公司开发的项目可能采取ARC.此时如何将两者整合到一起? 解决方法:选中改项目---Build Phase -- 双击非ARC 文件,输入

关于vue的前端项目中token使用以及验证机制 携带token登录详情 vue-router的跳转说明

在login.vue中通过发送http请求获取token//根据api接口获取tokenvar url = ‘http://www.baidudd.com’ + "/session";this.$axios.post(url, {username: this.loginForm.username,password: this.loginForm.pass}).then(res => {// console.log(res.data);this.$message.success('