JWT入门指南

news/2024/11/23 5:19:40/

1、Token认证


随着 Restful API、微服务的兴起,基于 Token 的认证现在已经越来越普遍。基于token的用户认证是一种服务端无状态的认证方式,所谓服务端无状态指的token本身包含登录用户所有的相关数据,而客户端在认证后的每次请求都会携带token,因此服务器端无需存放token数据。

当用户认证后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。

分布式单点登录三种常见方式:(SSO)

  • 第一种,session广播机制实现。(把session复制到另一台服务器中)

    • 缺点:模块较多时,拷贝session比较浪费资源;比如 中间会存在多份一样的数据 ;session默认过期时间30分钟,过期需要重新登录
  • 第二种,使用cookie+redis实现。

    • cookie客户端技术:存在浏览器中,每次发送请求,带着cookie值进行发送

    • redis,读取速度快,基于key-value存储(keys *)

    • 用户登录后,把数据分别放到两个地方cookie、redis

      • ① redis,在key里生成唯一随机值(ip、用户id、uuid) ,在value里放用户数据
      • ② cookie,把redis里面生成key值放到cookie里面。
    • 访问项目其他模块时,发送请求带着cookie进行发送,然后其他模块去获取cookie值,也就是拿着cookie去redis中查询,如果能查到数据表示这个用户已登录。

  • 第三种,token认证(按照一定规则生成字符串,字符串可以包含用户信息) ,jwt


2、JWT 概述


JWT(全称:JSON Web Token),通过数字签名的方式,以JSON对象作为载体,在不同的服务终端之间安全的传输信息。JWT 是实现Token无状态会话认证技术的一种标准。

JWT作用:通常用于web应用程序的 身份验证鉴权 。JWT会在用户登录后生成一个令牌,并在后续每个请求中将该令牌作为身份凭证发送给服务器,系统在处理用户请求之前,都要先进行JWT的安全校验,通过之后再进行相应的业务处理。


3、JWT的组成


JWT令牌由Header、Payload、Signature三部分组成,每部分字符串中间用. 拼接。JWT令牌的最终格式是这样的: xxx.yyy.zzz

# Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjpbeyJ1cmwiOiJodHRwczovL3Rvb2x0dC5jb20ifV0sImlhdCI6MTY0NjExMDgwNSwiZXhwIjoyNTU2MTE1MTk5LCJhdWQiOiIiLCJpc3MiOiJ0b29sdHQuY29tIiwic3ViIjoiIn0.NhUwqiPfYey9pKHSfrG-ptqEOamIQFK3-K7IrTeBFYU

3.1 Header(标头)


Header(标头),通常由两部分组成:令牌的类型 和 所用的加密算法,然后将该JSON对象数据进行Base64 URL编码,得到的字符串就是JWT令牌的第一部分。

{"type":"JWT", # 表示要生成JWT类型的token"alg":"HS256" # 加密算法
}

3.2 Payload(载荷)


Payload(载荷),有效数据存储区,主要定义自定义字段和内置字段数据。通常会把用户信息和令牌过期时间放在这里,同样也是一个JSON对象,里面的key和value可随意设置,然后经过Base64 URL编码后得到JWT令牌的第二部分,由于这个部分是没有加密的(因为Base64是编码,可以直接解码),建议只存放一些非敏感信息。

{"sub": "1234567890","name": "aopmin","admin": true
}

Payload的内置字段:

  • iss(Issuer):令牌的签发者
  • sub(Subject):所面向的用户或实体
  • aud(Audience):令牌的接收者
  • exp(Expiration Time):令牌的过期时间(以UNIX时间戳表示)
  • nbf(Not Before):令牌的生效时间(以UNIX时间戳表示)
  • iat(Issued At):令牌的签发时间(以UNIX时间戳表示)
  • jti(JWT ID):令牌的唯一标识符

3.3 Signature(签名)


Signature(签名), 使用头部Header定义的加密算法,对前两部分Base64编码拼接的结果进行加密,加密时的秘钥服务私密保存,加密后的结果在通过Base64Url编码得到JWT令牌的第三部分。

签名的作用:防止JWT令牌被篡改。

//将头部和载荷base64编码后的数据进行拼接
var encodeStr = base64UrlEncode(header) + "." + base64UrlEncode(payload);
//使用头部定义的算法,对拼接后的数据进行加密 //secret 盐值、秘钥
var signature = HMACSHA256(encodeStr,secret); 

4、JWT的使用


1、创建springboot项目,项目名:springboot-jwt

2、向pom.xml中,导入如下依赖:

<dependencies><!-- springmvc --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- junit --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!-- jwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
</dependencies>

注:如果使用JDK8以后的版本,jwt需要引入额外的4个依赖 jaxb-api、jaxb-impl、jaxb-core、activation。


3、使用JWT生成Token:

package com.baidou.test;import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;import java.util.Date;
import java.util.UUID;/*** 使用JWT生成token和验证token** @author 白豆五* @version 2023/06/19* @since JDK8*/
public class JwtTest {/*** 测试使用JWT生成令牌* 应用场景:用户登录生成token*/@Testpublic void testCreatJwt() {String secretKey = "aopmin"; //秘钥// 使用Jwts工具类构建一个令牌String token = Jwts.builder()// 1.设置JWT头部信息(类型和加密算法).setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256")// 2.设置JWT载荷数据.setId(UUID.randomUUID().toString()) //内置字段jti:表示唯一ID.setSubject("all") //内置字段sub:面向所有用户.setIssuedAt(new Date()) //内置字段ita:token创建时间.setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) //内置字段exp:token过期时间,30分钟.claim("username", "aopmin") //自定义字段,kv格式.claim("userId", "1001") //自定义字段// 3.设置JWT签名信息(加密算法,秘钥).signWith(SignatureAlgorithm.HS256, secretKey).compact(); //最后调用compact()方法生成最终的tokenSystem.out.println("token = " + token);//由于使用UUID生成唯一标识,所以每次生成的token都不一样}
}

输出结果:

token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5MmJkNTQxOC1lNjBkLTRiMjYtYmVkNS01NDlkZmYyOTliZmEiLCJzdWIiOiJhbGwiLCJpYXQiOjE2ODcxOTI4MTQsImV4cCI6MTY4NzE5NDYxNCwidXNlcm5hbWUiOiJhb3BtaW4iLCJ1c2VySWQiOiIxMDAxIn0.H6aI4ozESqiamOfY9NxunnEs0y3AhOTHcXsFFYmPut4

在线token解密:https://tooltt.com/jwt-decode/

在这里插入图片描述

在这里插入图片描述


4、使用JWT验证Token:

/*** 测试使用JWT验证令牌* 应用场景:用户登录后请求系统携带token令牌,系统对token进行验证,判断是否合法* 令牌解析失败的情况:*    1.令牌过期*    2.令牌被篡改* 结论:一个合法的Tokn令牌一定是未过期、未被篡改的令牌*/
@Test
public void testcheckToken() {// 秘钥String secretKey = "aopmin";// 待验证的tokenString tokenStr = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5MmJkNTQxOC1lNjBkLTRiMjYtYmVkNS01NDlkZmYyOTliZmEiLCJzdWIiOiJhbGwiLCJpYXQiOjE2ODcxOTI4MTQsImV4cCI6MTY4NzE5NDYxNCwidXNlcm5hbWUiOiJhb3BtaW4iLCJ1c2VySWQiOiIxMDAxIn0.H6aI4ozESqiamOfY9NxunnEs0y3AhOTHcXsFFYmPut4";// 通过密钥验证签名是否被篡改Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(tokenStr);// 获取头JwsHeader header = claimsJws.getHeader();// 获取载荷Claims body = claimsJws.getBody();// 获取签名String signature = claimsJws.getSignature();System.out.println("头信息:" + header);System.out.println("载荷信息:" + body);System.out.println("签名信息:" + signature);
}

输出结果:

头信息:{typ=JWT, alg=HS256}
载荷信息:{jti=92bd5418-e60d-4b26-bed5-549dff299bfa, sub=all, iat=1687192814, exp=1687194614, username=aopmin, userId=1001}
签名信息:H6aI4ozESqiamOfY9NxunnEs0y3AhOTHcXsFFYmPut4

5、SpringBoot+JWT快速整合


5.1 用户登录生成Token


1、创建实体类

package com.baidou.pojo;import lombok.Data;import java.io.Serializable;/*** 用户实体** @author 白豆五* @version 2023/06/20* @since JDK8*/
@Data
public class User implements Serializable {private String id;private String userName;private String passWord;/*** token字符串*/private String token;
}

2、创建JWT工具类

package com.baidou.utils;import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.web.bind.annotation.GetMapping;import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.UUID;/*** JWT工具类** @author 白豆五* @version 2023/06/20* @since JDK8*/
public class JwtUtil {//过期时间:1个小时public static final long EXPIRE = 1000 * 60 * 60 * 1;//秘钥public static final String APP_SECRET = "aopmin";/*** 创建Token** @param id       用户ID* @param userName 用户名称* @return jwtToken*/public static String createToken(String id, String userName) {// 使用Jwts工具类构建一个令牌String jwtToken = Jwts.builder()// 1.设置JWT头部信息(类型和加密算法).setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256")// 2.设置JWT载荷数据.setId(UUID.randomUUID().toString()) //内置字段jti:表示唯一ID.setSubject("all") //内置字段sub:面向所有用户.setIssuedAt(new Date()) //内置字段ita:token创建时间.setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) //内置字段exp:token过期时间,token过期时间30分钟.claim("username", userName) //自定义字段,kv格式.claim("userId", id) //自定义字段// 3.设置JWT签名(加密算法,秘钥).signWith(SignatureAlgorithm.HS256, APP_SECRET).compact(); //最后调用compact()方法生成最终的tokenreturn jwtToken;}/*** 判断Token是否、有效** @param jwtToken token* @return true:token有效 、false:token失效*/public static boolean checkToken(String jwtToken) {// 非空判断if (StrUtil.isBlank(jwtToken)) {return false;}try {// 通过密钥验证签名是否被篡改Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 判断Token是否存在、有效** @param request 从请求头中拿token* @return true:token有效 、false:token失效*/public static boolean checkToken(HttpServletRequest request) {try {String jwtToken = request.getHeader("token");if (StrUtil.isBlank(jwtToken)) {return false;}// 通过密钥验证签名是否被篡改Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 根据Token获取用户ID** @param request 从请求头中拿token* @return userID*/public static String getUserIdByJwtToken(HttpServletRequest request) {// 从请求头中拿tokenString jwtToken = request.getHeader("token");// 非空判断if (StrUtil.isBlank(jwtToken)) {return "";}// 通过密钥验证签名是否被篡改Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);// 获取载荷信息Claims claims = claimsJws.getBody();// 用户IDreturn (String) claims.get("userId");}}

3、创建控制器类:UserController

package com.baidou.controller;import cn.hutool.core.util.StrUtil;
import com.baidou.pojo.User;
import com.baidou.utils.JwtUtil;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;/*** 用户接口** @author 白豆五* @version 2023/06/20* @since JDK8*/
@RestController //前后端分离使用json格式
@RequestMapping("user")
public class UserController {// region 登录相关/*** 用户登录接口** @param user* @return*/@PostMapping("/login")public User login(@RequestBody User user) {// 把账号密码直接写死(不让他走数据库)String userName = "aopmin";String passWord = "123456";// 非空校验if (StrUtil.isAllBlank(user.getUserName(), user.getPassWord())) {return null;}// 如果用户名和密码都正确,创建tokenif (userName.equals(user.getUserName()) && passWord.equals(user.getPassWord())) {// 创建Token:token保存到user对象中user.setToken(JwtUtil.createToken(user.getId(), user.getUserName()));return user;}// 用户名和密码不正确,返回nullreturn null;}/*** 验证Token是否过期** @param token 用户token* @return true未过期、false已过期*/@GetMapping("/check_token")public boolean checkToken(String token) {return JwtUtil.checkToken(token);}/*** 验证Token是否过期 --- 前端把token放到请求头中** @param request 从请求头中拿token* @return true未过期、false已过期*/@GetMapping("/check_token2")public boolean checkToken(HttpServletRequest request) {return JwtUtil.checkToken(request);}// endregion
}

4、启动项目,使用Apifox测试接口

测试登录接口:http://localhost:8080/user/login

在这里插入图片描述

token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4MzcyNDY3Zi0xOGY4LTQ0YjEtYTIzMi0zNjcwZTk3ODZjZDYiLCJzdWIiOiJhbGwiLCJpYXQiOjE2ODcxOTgxNzIsImV4cCI6MTY4NzIwMTc3MiwidXNlcm5hbWUiOiJhb3BtaW4iLCJ1c2VySWQiOiIxMDAxIn0.8JZMuIeqf1VXuz6-SSDDD48hGRGmjDUNI9xjJd0RjL8

测试验证token接口:http://localhost:8080/user/check_token

在这里插入图片描述


测试验证token接口(前端把token放到请求头中):http://localhost:8080/user/check_token2

在这里插入图片描述


5.2 跨域配置


前后端会存在跨域问题。

在发送请求时,如果出现以下任意一种情况,那么它就是跨域请求:

  • 协议不同,如 http 、https;

  • 域名不同,如 www.taobao.com、www.jd.com、www.baidu.com

  • 端口不同,如 http:localhost:8080、http:localhost:8081

后端解决方案:

package com.baidou.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 跨域配置** @author 白豆五* @version 2023/06/20* @since JDK8*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {/*** 添加跨域配置* @param registry 注册器*/@Overridepublic void addCorsMappings(CorsRegistry registry) {// 覆盖所有请求registry.addMapping("/**") // 配置可以跨域的路径,/**表示匹配所有请求路径.allowedOrigins("*") // 允许所有的请求,也可以指定具体的请求,例如 allowedOrigins("http://example.com").allowedHeaders("*") // 允许所有请求头访问,也可以指定具体的请求头访问,例如 allowedHeaders("Content-Type", "Authorization").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD") // 允许的HTTP方法,根据需要添加或删除特定的HTTP方法.maxAge(3600); // 预检请求的缓存时间,单位为秒}
}

5.3 使用拦截器验证Token


1、创建验证token的拦截器

package com.baidou.interceptor;import com.baidou.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** Token拦截器 ———— 验证Token** @author 白豆五* @version 2023/06/20* @since JDK8*/
@Slf4j
@Configuration
public class TokenInterceptor implements HandlerInterceptor {// 在控制器请求处理方法被调用之前执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("验证token的拦截器执行了,token:{}",request.getHeader("token"));// 要求前端必须把token放到请求头中if (!JwtUtil.checkToken(request)) {return false; //验证失败}return true; //放行}
}

2、创建WebConfig配置类,注册拦截器

package com.baidou.config;import com.baidou.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** SpringMVC配置类** @author 白豆五* @version 2023/06/20* @since JDK8*/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate TokenInterceptor tokenInterceptor;// 注册拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tokenInterceptor).addPathPatterns("/**"); // 添加拦截器,并指定要拦截的路径}
}

3、编写测试接口:

/*** 从token中获取用户名** @param request* @return*/
@GetMapping("/getName")
public String getUserName(HttpServletRequest request) {// 从请求头中拿tokenString token = request.getHeader("token");// 非空判断if (StrUtil.isBlank(token)) {return "";}// 通过密钥验证签名是否被篡改Jws<Claims> claimsJws = Jwts.parser().setSigningKey("aopmin").parseClaimsJws(token);// 获取载荷信息Claims claims = claimsJws.getBody();// 用户IDreturn (String) claims.get("username");
}

4、测试:http://localhost:8080/user/getName

在这里插入图片描述
在这里插入图片描述


6、加密算法(扩展)


6.1 常用的加密算法


在这里插入图片描述


5.2 密码加密技术选型


在这里插入图片描述

6.2.1 MD5加密方式


MD5一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。

示例:

package com.baidou.test;import org.springframework.util.DigestUtils;/*** 测试MD5加密算法** @author 白豆五* @version 2023/06/20* @since JDK8*/
public class MD5Test {public static void main(String[] args) {// 使用spring框架提供的DegestUtils工具类实现MD5加密String s1 = DigestUtils.md5DigestAsHex("hello".getBytes());String s2 = DigestUtils.md5DigestAsHex("hello".getBytes());System.out.println(s1); // 5d41402abc4b2a76b9719d911017c592System.out.println(s2); // 5d41402abc4b2a76b9719d911017c592System.out.println(s1.equals(s2)); // true}
}

注意:md5对相同的内容加密,每次加密后的密文是相同的,所以不太安全。


6.2.2 MD5+盐


基于md5+随机字符串进行手动加密,增加破解md5的复杂度。(这种方式盐需要保存到表中)

在md5的基础上进行手动加盐(salt)处理:

package com.baidou.test;import org.springframework.util.DigestUtils;/*** 测试:MD5+盐方式** @author 白豆五* @version 2023/06/20* @since JDK8*/
public class Md5SaltTest {public static void main(String[] args) {// 盐值String salt = "2023-04-29"; // 明文密码String pwd = "admin";// MD5加密的密码String md5Pwd = DigestUtils.md5DigestAsHex(pwd.getBytes());// MD5+盐加密的密码String md5Pwd2 = DigestUtils.md5DigestAsHex((pwd + salt).getBytes());System.out.println(md5Pwd); // 21232f297a57a5a743894a0e4a801fc3System.out.println(md5Pwd2); // 1676be8379ca0a334d035cbd32cb24de}
}

注意:这种方式,同样的密码,如果盐的值是随机字符串,那么加密多次的密码是不相同的;


6.2.3 Bcrypt加密方式


在用户模块中,对于用户密码的保护,我们通常对密码进行加密,然后存放在数据库中,在用户进行登录的时候,将其输入的密码进行加密然后与数据库中存放的密文进行比较,以验证用户密码是否正确。 目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全。

BCrypt官网:http://www.mindrot.org/projects/jBCrypt/

1、从官网下载源码,将源码类BCrypt拷贝到工程中;(当然Hutool工具类中也提供了BCrypt加密)(盐不需要保存表中)

2、新建测试类,main方法中编写代码,实现对密码的加密;

3、BCrypt不支持反运算,只支持密码校验。

BCrypt常用工具方法:

  • gensalt():生成盐;(随机字符串)
  • hashpw(明文密码,盐):加密方法;
  • checkpw(明文密码, 密文密码):验证方法;

示例:

package com.baidou.test;/*** 测试Bcrypt加密方式** @author 白豆五* @version 2023/06/20* @since JDK8*/
public class BcryptTest {private static String pwdEncrypt = null; //模拟数据库表中的密码public static void main(String[] args) {// 模拟用户注册register("123456");// 模拟用户登录checkPwd("123456");}/*** 用户注册方法** @param pwd 明文密码* @return 盐*/public static void register(String pwd) {// 生成盐值String salt = BCrypt.gensalt();// 加密pwdEncrypt = BCrypt.hashpw(pwd, salt);System.out.println("盐: " + salt + ",加密后密码: " + pwdEncrypt);}/*** 模拟用户登录* @param pwd 用户输入的密码*/public static void checkPwd(String pwd) {// 解密boolean isMatch = BCrypt.checkpw(pwd, pwdEncrypt);if (isMatch) {System.out.println("密码正确!");} else {System.out.println("密码错误!");}}
}

http://www.ppmy.cn/news/477485.html

相关文章

在坦桑尼亚如何打国际长途,打给你中国的朋友?

只要你充值了&#xff0c;就可以直接打回 国内任何电话 当然要在号码前加拨 “ 86”例如拨号132********就拨“86132********” 座机 如010******** 就拨8610*********不要拨座机区号前面的0 就可以了,发信息也是一样的。 记得要拨一个“”号&#xff0c;要不&#xff0c;联系不…

国内用户访问国内服务器,国外用户访问国外服务器

1、域名智能解析 通过来源&#xff0c;来解析访问服务器 添加记录值页面进行操作 2、域名判断 每个项目国内外配置域名&#xff0c;判断ip地址&#xff0c;访问不同域名。来达到效果 3、dns 在全局流量管理中&#xff0c;点击创建进行配置访问策略

php 验证座机,验证国内手机号与座机号的正则表达式

这次给大家带来验证国内手机号与座机号的正则表达式&#xff0c;验证国内手机号与座机号的正则表达式的注意事项有哪些&#xff0c;下面就是实战案例&#xff0c;一起来看一下。验证手机号:^((13[0-9])|(14[7])|(15[^4,\\D])|(18[0,0-9])|(17[0,0-9]))\\d{8}$ 验证座机和手机号…

国际和国内电话区号

精选30云产品&#xff0c;助力企业轻松上云&#xff01;>>> https://baike.baidu.com/item/%E5%8C%BA%E5%8F%B7/1866086 https://zh.wikipedia.org/wiki/%E7%94%B5%E8%AF%9D%E5%8C%BA%E5%8F%B7 https://zh.wikipedia.org/wiki/%E5%9B%BD%E9%99%85%E7%94%B5%E8%AF%9D…

阿里云服务器是国内的还是国外的?

阿里云服务器地域遍布全球&#xff0c;有国内的也有国外的&#xff0c;国内地域云服务器需要ICP备案&#xff0c;中国香港地域和国外节点不需要备案。阿里云百科来详细说下阿里云服务器国内地域和国外地域分布表&#xff1a; 阿里云服务器是国内的吗&#xff1f; 阿里云服务器…

全国各省市座机电话区号整理

excel数据整理下载地址 https://download.csdn.net/download/MtiredM/87620876 json格式数据整理 const areaCodes {//热门城市"010": "北京市","024": "沈阳市","0371": "郑州市","020": "广…

全国区号对照表

全国区号对照表 北京市 北京(010) 天津市 天津(022) 河北省 邯郸(0310)石家庄(0311)保定(0312)张家口(0313)承德(0314)唐山(0315)廊坊(0316)沧州(0317)衡水(0318)邢台(0319)秦皇岛(0335)山西省朔州(0349)忻州(0350)太原(0351)大同(0352)阳泉(0353)晋中(0354)长治(0355)晋城(035…

国内程序员真的不如国外国外程序员?到底差在哪里?

差在环境背景不同。 任何抛开比较对象所在的环境背景去讨论两者能力强弱的都是在耍流氓。 那么国内外互联网环境究竟差别在哪里方面呢&#xff1f; 1.年龄焦虑 在国外&#xff0c;35岁并不是一个程序员的事业分水岭&#xff0c;不存在35岁了&#xff0c;不是转业就是转管理岗的…