广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >SpringBoot集成JWT实现Token登录验证
  • 471
分享到

SpringBoot集成JWT实现Token登录验证

springboot后端java 2023-09-06 21:09:52 471人浏览 安东尼
摘要

目录 1.1 JWT是什么? 1.2 JWT主要使用场景 1.3 JWT请求流程 1.4 JWT结构 二,SpringBoot集成JWT具体实现过程 2.1添加相关依赖 2.2自定义跳出拦截器的注解 2.3自定义全局统一返回值方法,异常类

目录

1.1 JWT是什么?

1.2 JWT主要使用场景

1.3 JWT请求流程

1.4 JWT结构

二,SpringBoot集成JWT具体实现过程

2.1添加相关依赖

2.2自定义跳出拦截器的注解

2.3自定义全局统一返回值方法,异常类及相关枚举

2.4编写JWT工具类,用于生成Token令牌

2.5编写拦截器并注入容器

三,测试

3.1放行一般类接口

3.2放行登录接口


1.1 Jwt是什么?

JWT官网

在JWT官网中可以明确看到关于它的定义

JSON WEB令牌(JWT)是一种开放的标准(RFC 7519),它定义了一种紧凑而独立的方式在各方之间安全地传输信息为jsON对象。该信息可以被验证和信任,因为它是数字签名的。JWT可以使用秘密(使用HMac算法)或使用RSA或ECDSA的公开/私有密钥类型签名。 虽然JWT可以被加密以提供各方之间的保密,但我们将重点关注签名的令牌。被签名的令牌可以验证包含在其中的声明的完整性,而加密的令牌对其他各方隐藏这些声明。当使用公钥/私钥对签名时,签名还证明只有持有私钥的一方才是签名方。

1.2 JWT主要使用场景

  1. 授权(Authorization):这是使用JWT最常见的场景。一旦用户登录,每个后续请求都将包括JWT,允许用户访问该令牌允许的路由、服务和资源。

  2. 单点登录(Single Sign On ):单点登录是当今广泛使用的JWT特性,因为它的小规模和易于跨不同领域使用的能力。

  3. 信息交换(lnformation Exchange):信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。

  4. 传输信息(transmitting information):在各方之间传输信息。由于JWT可以签名--例如,使用公共/私钥对--您可以确保发件人是他们所说的发送者。此外,由于签名是使用头和有效载荷计算的,您还可以验证内容没有被篡改。

1.3 JWT请求流程

  1. 用户使用账号和密码发出post请求;
  2. 服务器使用私钥创建一个jwt;
  3. 服务器返回这个jwt给浏览器;
  4. 浏览器将该jwt串在请求头中像服务器发送请求;
  5. 服务器验证该jwt;
  6. 返回响应的资源给浏览器。

1.4 JWT结构

JWT是由三段信息构成的,将这三段信息文本用.连接一起就构成了JWT字符串

就像这样:

eyJhbGCioiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2Qt4fwpMeJf36POk6yJV_adQssw5c

 JWT包含了三部分:

  • Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)

  • Payload 负载 (类似于飞机上承载的物品)

  • Signature 签名/签证

Header

JWT的头部承载两部分信息:token类型和采用的加密算法。

{  "alg": "HS256",  "typ": "JWT"} 

声明类型:这里是jwt

声明加密的算法:通常直接使用 HMAC SHA256

加密算法是单向函数散列算法,常见的有MD5、SHA、HAMC。

MD5(message-digest alGorithm 5) (信息-摘要算法)缩写,广泛用于加密和解密技术,常用于文件校验。校验?不管文件多大,经过MD5后都能生成唯一的MD5值

SHA (Secure Hash Algorithm,安全散列算法),数字签名等密码学应用中重要的工具,安全性高于MD5

HMAC (Hash Message Authentication Code),散列消息鉴别码,基于密钥的Hash算法的认证协议。用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。常用于接口签名验证

Payload

载荷就是存放有效信息的地方。

有效信息包含三个部分:

  1. 标准中注册的声明

  2. 公共的声明

  3. 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者

  • sub: 面向的用户(jwt所面向的用户)

  • aud: 接收jwt的一方

  • exp: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间)

  • nbf: 定义在什么时间之前,该jwt都是不可用的.

  • iat: jwt的签发时间

  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明:

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明:

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

Signature

jwt的第三部分是一个签证信息

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。最主要的目的:服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名,如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个Http 401 Unauthorized响应。

密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和进行验证,所以需要保护好。

这些信息在官网上也有相关信息的说明:

二,SpringBoot集成JWT具体实现过程

这里由于只涉及对验证功能的实现,因此其他数据库,业务类编写一概省略,只对相关步骤做说明。

2.1添加相关依赖

既然要使用JWT我们肯定需要引入其依赖

             com.auth0            java-jwt            3.10.3        

2.2自定义跳出拦截器的注解

方便我们后续不用对在配置拦截器时排除每一个接口,只用自定义注解去标记即可。

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface PassToken {    boolean required() default true;}

@Retention注解说明

  • RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。

  • RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。

  • RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。

  • @Document:说明该注解将被包含在javadoc中

  • @Inherited:说明子类可以继承父类中的该注解

2.3自定义全局统一返回值方法,异常类及相关枚举

定义全局枚举类

package com.yy.enums;import lombok.AllArgsConstructor;import lombok.Data;import lombok.Getter;import java.util.UUID;@AllArgsConstructor@Getterpublic enum ResponseEnum {      SUCCESS(200, "操作成功"),      FaiL(201,"获取数据失败"),      NO_TOKEN(400,"无token,请重新登录"),    TOKEN_EX(401,"token验证失败,请重新登录"),      USER_EX(402,"用户不存在,请重新登录"),         ERROR(400,"错误请求");      private final Integer code;         private  final String  resultMessage;      public static ResponseEnum getResultCode(Integer code){      for (ResponseEnum value : ResponseEnum.values()) {         if (code.equals(value.getCode())){            return value;         }      }      return ResponseEnum.ERROR;   }   public static void main(String[] args) {      ResponseEnum resultCode = ResponseEnum.getResultCode(100);      System.out.println(resultCode);   }}

定义全局异常类

package com.yy.util;import com.yy.Enums.ResultEnum;import io.swagger.annotations.apiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Data;@Data@ApiModel(value = "自定义全局异常类")public class MyException extends RuntimeException{    @ApiModelProperty(value = "异常状态码")    private final Integer code;        public MyException(Integer code,String message) {        super(message);        this.code = code;    }        public MyException(ResponseEnum responseEnum){        super(responseEnum.getMessage());        this.code = responseEnum.getCode();    }}
package com.yy.Config;import com.yy.utils.MyException;import com.yy.utils.R;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvicepublic class GlobalExceptionConfig{    @ExceptionHandler(MyException.class)    public R handle(MyException e){        e.printStackTrace();        return R.exception(e.getCode(),e.getMessage());    }}

定义统一返回结果类

package com.yy.utils;import com.yy.enums.ResponseEnum;import lombok.Data;import java.io.Serializable;import java.util.HashMap;import java.util.Map;@Datapublic class  R implements Serializable {        private static final long serialVersionUID = 56665257248936049L;        private Integer code;        private String message;        private T data;   private R(){}        public static  R ok(T data) {        R response = new R<>();     response.setCode(ResponseEnum.SUCCESS.getCode());   response.setMessage(ResponseEnum.SUCCESS.getResultMessage());        response.setData(data);        return response;    }        public static  R buildFailure(Integer errCode, String errMessage){        R response = new R<>();        response.setCode(errCode);        response.setMessage(errMessage);        return response;    }        public static  R exception(Integer errCode, String errMessage){        R response = new R<>();        response.setCode(errCode);        response.setMessage(errMessage);        return response;    }}

2.4编写JWT工具类,用于生成Token令牌

package com.yy.utils;import cn.hutool.core.date.DateUtil;import com.auth0.jwt.JWT;import com.auth0.jwt.algorithms.Algorithm;import java.util.Date;public class JwtTokenUtils {    private JwtTokenUtils() {        throw new IllegalStateException("Utility class");    }        public static String getToken(String userId,String sign){      return  JWT.create()              //签收者              .withAudience(userId)              //主题               .withSubject("token")                //2小时候token过期                .withExpiresAt(DateUtil.offsetHour(new Date(),2))                //以passWord作为token的密钥                .sign(Algorithm.HMAC256(sign));    }}

Algorithm.HMAC256():使用HS256生成token,密钥则是用户的密码,唯一密钥的话可以保存在服务端。

withAudience():存入需要保存在token的信息,这里我们把用户ID存入token中

2.5编写拦截器并注入容器

package com.yy.Config.inteceptor;import cn.hutool.core.text.CharSequenceUtil;import com.auth0.jwt.JWT;import com.auth0.jwt.JWTVerifier;import com.auth0.jwt.algorithms.Algorithm;import com.auth0.jwt.exceptions.JWTDecodeException;import com.auth0.jwt.exceptions.JWTVerificationException;import com.yy.enums.ResponseEnum;import com.yy.admin.pojo.Admin;import com.yy.admin.service.Impl.AdminServiceImpl;import com.yy.utils.MyException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.httpservletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;@Componentpublic class MyJwtInterceptor implements HandlerInterceptor {    @Autowired    private AdminServiceImpl adminService;    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        String token = request.getHeader("token");        if (!(handler instanceof HandlerMethod)) {            return true;        }        HandlerMethod handlerMethod = (HandlerMethod) handler;        Method method = handlerMethod.getMethod();        //检查是否通过有PassToken注解        if (method.isAnnotationPresent(PassToken.class)) {            //如果有则跳过认证检查            PassToken passToken = method.getAnnotation(PassToken.class);            if (passToken.required()) {                return true;            }        }        //否则进行token检查        if (CharSequenceUtil.isBlank(token)) {            throw new MyException(ResponseEnum.TOKEN_EX.getCode(), ResponseEnum.TOKEN_EX.getResultMessage());        }        //获取token中的用户id        String userId;        try {            userId = JWT.decode(token).getAudience().get(0);        } catch (JWTDecodeException j) {            throw new MyException(ResponseEnum.TOKEN_EX.getCode(), ResponseEnum.TOKEN_EX.getResultMessage());        }        //根据token中的userId查询数据库        Admin user = adminService.getById(userId);        if (user == null) {            throw new MyException(ResponseEnum.USER_EX.getCode(), ResponseEnum.USER_EX.getResultMessage());        }        //验证token        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPwd())).build();        try {            jwtVerifier.verify(token);        } catch (JWTVerificationException e) {            throw new MyException(406, "权限验证失败!");        }        return true;    }}

这里需要说明一下实现拦截器的方法,我们只需要实现HandlerInterceptor接口即可,它主要定义了三个方法:

boolean preHandle ()

预处理回调方法,实现处理器的预处理,第三个参数为响应的处理器,自定义Controller,返回值为true表示继续流程(如调用下一个拦截器或处理器)或者接着执行postHandle()和afterCompletion();false表示流程中断,不会继续调用其他的拦截器或处理器,中断执行。

void postHandle()

后处理回调方法,实现处理器的后处理(DispatcherServlet进行视图返回渲染之前进行调用),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。

void afterCompletion():

整个请求处理完毕回调方法,该方法也是需要当前对应的Interceptor的preHandle()的返回值为true时才会执行,也就是在DispatcherServlet渲染了对应的视图之后执行。用于进行资源清理。

整个请求处理完毕回调方法。如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中。

这里我们主要需要调用预处理回调方法即可,如果有其他业务需求,也可自行更改。

主要流程:

  1. 从 http 请求头中取出 token,

  2. 判断是否映射到方法

  3. 检查是否有passtoken注释,有则跳过认证

  4. 检查有没有需要用户登录的注解,有则需要取出并验证

  5. 认证通过则可以访问,不通过会报相关错误信息

然后通过配置类将我们自定义的拦截类注入到spring容器中,并进行拦截配置。

package com.yy.Config.inteceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorReGIStry;import org.springframework.web.servlet.config.annotation.WebmvcConfigurer;@Configurationpublic class InterceptorConfig implements WebMvcConfigurer {    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(jwtInterceptor()) //拦截所有请求,通过判断token来决定是否需要登陆                .addPathPatterns("    @GetMapping("/getOne/{id}")    @CostTime    @PassToken    public R selectOne(@PathVariable Integer id) {        TestUser user = testUserService.getById(id);        return R.ok(user);    }

进行接口测试后发现,该接口获取数据正常。

 其他没有加@PassToken的接口由于没有token进行验证,均会被拦截器拦截,并返回我们预期的异常信息"token验证失败,请重新登录"

因此,同理我们只需要在登陆注册或其他不需要token验证的接口上添加自定义注解即可实现拦截。

为了达到业务需求,我们需要在用户登录成功后获取到token,然后将token信息存放在每次的接口请求头(headers)上,这样就能实现对用户接口信息基本保护了。

3.2放行登录接口

在业务层处理token,将生成的token信息带到用户实体类中,这样登录获取用户信息时就能读取到token信息了

package com.yy.admin.service.Impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.yy.admin.pojo.Admin;import com.yy.admin.service.AdminService;import com.yy.admin.dao.AdminDao;import com.yy.utils.JwtTokenUtils;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.lang.Nullable;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.util.Optional;@Service@Slf4jpublic class AdminServiceImpl extends ServiceImpl    implements AdminService{  @Resource  private AdminDao adminDao;    @Override    public Admin getMsg(String username, String pwd){         Admin admin = adminDao.selectByUsernameAndPwd(username, pwd);         Optional.ofNullable(admin).ifPresent(u->{             //添加token信息设置到用户实体上             String token = JwtTokenUtils.getToken(String.valueOf(admin.getId()),pwd);             log.info("token的值为:{}",token);             admin.setToken(token);         });         return admin;     }}

编写用户登录的业务接口并放行Token

@PostMapping("/login")    @PassToken    public Object loginStatus(@RequestParam("username") String username, @RequestParam("password") String password){        JSONObject object = new JSONObject();        Admin admin = adminService.getMsg(username, password);        if (admin!=null){            object.put("code",1);            object.put("msg","登陆成功");            object.put("success",true);            object.put("type","success");            object.put("userMsg",admin);        }else {            object.put("code",0);            object.put("success",false);            object.put("msg","用户名或密码错误");            object.put("type","error");        }        return object;    }

进行接口测试

 这样就可以看到生成的Token信息了,然后我们将token信息设置在请求头上对其他接口进行测试。

此时就能看到,接口请求成功。

注意:这里的参数名token对应拦截器配置String token=request.getHeader("token")中的getHeader中设置的的参数名。

来源地址:https://blog.csdn.net/qq_42263280/article/details/128009297

--结束END--

本文标题: SpringBoot集成JWT实现Token登录验证

本文链接: https://www.lsjlt.com/news/397614.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • SpringBoot集成JWT实现Token登录验证
    目录 1.1 JWT是什么? 1.2 JWT主要使用场景 1.3 JWT请求流程 1.4 JWT结构 二,SpringBoot集成JWT具体实现过程 2.1添加相关依赖 2.2自定义跳出拦截器的注解 2.3自定义全局统一返回值方法,异常类...
    99+
    2023-09-06
    spring boot 后端 java
  • 实战SpringBoot集成JWT实现token验证
    目录环境搭建1、新建一个SpringBoot项目Jwt-Demo,引入项目后面需要用到的jar包2、数据库结构3、配置文件application.properties4、Entity...
    99+
    2022-11-12
  • SpringBoot集成JWT怎么实现token验证
    本篇内容主要讲解“SpringBoot集成JWT怎么实现token验证”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“SpringBoot集成JWT怎么实现token验证”吧!JWT可以理解为一个...
    99+
    2023-06-22
  • gogin+token(JWT)验证实现登陆验证
    1.准备 go get github.com/dgrijalva/jwt-go go get github.com/gin-gonic/gin  2.代码 package ...
    99+
    2022-11-12
  • ThinkPHP 集成 jwt 技术 token 验证
    ThinkPHP 集成 jwt 技术 token 验证 一、思路流程二、安装 firebase/php-jwt三、封装token类四、创建中间件,检验Token校验时效性五、配置路由中间件六、...
    99+
    2023-09-08
    ThiinkPHP PHP
  • SpringBoot集成JWT实现登陆验证的方法详解
    1:首先,我们需要在项目中导入两个依赖: <dependency> <groupId>com.auth0</gr...
    99+
    2022-11-13
  • PHP实现JWT的Token登录认证
    1、JWT简介 JSON Web Token(缩写 JWT),是目前最流行的跨域认证解决方案。 session登录认证方案:用户从客户端传递用户名、密码等信息,服务端认证后将信息存储在session中,将session_id放到cookie...
    99+
    2017-03-29
    PHP实现Token登录认证 PHP Token认证 Token登录认证
  • SpringBoot JWT实现token登录刷新功能
    目录1. 什么是JWT2. JWT组成部分3. JWT加密方式4.实战5.总结1. 什么是JWT Json web token (JWT) 是为了在网络应用环境间传递声明而执行的一种...
    99+
    2022-11-12
  • SpringBoot登录验证token拦截器的实现
    目录注解定义token生成与验证拦截器定义拦截器配置定义拦截器的方法执行类注解使用返回值-全局异常类定义各种测试不传token制造可行的假token伪造token测试拓展:从请求中获...
    99+
    2022-11-13
  • VUE实现token登录验证
    本文实例为大家分享了VUE实现token登录验证的具体代码,供大家参考,具体内容如下 实现这个登录功能的过程真是一波三折,中途出现了bug,整了两三天才解决了问题,心力交瘁,简直一个...
    99+
    2022-11-12
  • SpringBoot登录验证token拦截器如何实现
    这篇文章主要讲解了“SpringBoot登录验证token拦截器如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SpringBoot登录验证token拦截器如何实现”吧!用户访问接口验...
    99+
    2023-07-02
  • PHP如何实现JWT的Token登录认证
    本篇内容介绍了“PHP如何实现JWT的Token登录认证”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、JWT简介JSON Web Tok...
    99+
    2023-06-21
  • Java登录功能实现token生成与验证
    一、token与cookie相比较的优势 1、支持跨域访问,将token置于请求头中,而cookie是不支持跨域访问的; 2、无状态化,服务端无需存储token,只...
    99+
    2022-11-12
  • Java如何实现Token登录验证
    这篇文章主要介绍“Java如何实现Token登录验证”,在日常操作中,相信很多人在Java如何实现Token登录验证问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java如何实现Token登录验证”的疑惑有所...
    99+
    2023-07-05
  • SpringBoot+SpringSecurity+jwt实现验证
    目录环境目录结构信息记录一下使用springSecurity实现jwt的授权方法,这方法可以实现权限的基本认证。当然这个案例还有许多的问题,不过还是先记录一下。其他功能以后在补充。 ...
    99+
    2022-11-13
  • SpringBoot使用Sa-Token实现登录认证
    目录一、设计思路二、登录与注销三、会话查询四、Token 查询五、来个小测试,加深一下理解一、设计思路 对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加...
    99+
    2023-05-14
    SpringBoot Sa-Token登录认证 SpringBoot 登录认证
  • Vue实现用户登录及token验证
    在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下: 1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码 2、后端收到请求,验证用户名和密码,验证成功,就...
    99+
    2022-11-12
  • vue实现token登录验证的完整实例
    目录token可用于登录验证和权限管理。登录页 -----Login.vue路由守卫 ----- router/index.js封装axios 添加请求拦截器 在每次请求之前进行的操...
    99+
    2022-11-13
  • Java实现Token登录验证的项目实践
    目录一、JWT是什么?二、使用步骤1.项目结构2.相关依赖3.数据库4.相关代码三、测试结果一、JWT是什么? 在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程...
    99+
    2023-03-19
    Java Token登录验证 Java 登录验证
  • vue+springboot实现登录验证码
    本文实例为大家分享了vue+springboot实现登录验证码的具体代码,供大家参考,具体内容如下 先看效果图 在login页面添加验证码html 在后端pom文件添加kaptc...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作