SpringSecurity入门到入土教程_2 Oauth教程
https://gitee.com/fakerlove/spring-security
文章目录
- SpringOauth 教程
- 1. 简介
- 1.1 oauth2 概念
- 架构图
- 验证流程
- spring security oauth2源码架构图
- 1.2 模式
- 2. 入门
- 2.1 授权码模式
- 新建pom 文件
- 准备UserDetalService
- 配置类
- 配置授权服务器--微信,qq 的授权服务器
- 配置资源服务器
- 配置Controller
- 输入网址
- 原理讲解
- 2.2 密码模式
- 配置类修改
- 授权服务器修改
- 输入网址
- 原理
- 2.3 简化模式
- 2.4 客户端模式
- 令牌的使用
- 更新令牌
- 2.5 Redis 存储令牌
- 创建配置类
- 授权服务器
- 输入网址
- 打开redis
SpringOauth 教程
1. 简介
1.1 oauth2 概念
首先声明oauth2是一种协议规范,spring-security-oauth2是对他的一种实现。其次,还有shiro实现,自己根据规范编写代码的实现方式。主流的qq,微信等第三方授权登录方式都是基于oauth2实现的。
oauth2的认证方式有授权码,简单,账户密码,客户端等方式,具体请自行百度不做过多的阐述。 本文基于授权码方式实现
oauth生态设计的范围很大,可以说是一种解决方案,它有“第三方客户端(web服务,APP服务)”、“用户”、“认证服务器”、“资源服务器”等部分。认证流程如下图:
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
架构图
验证流程
spring security oauth2源码架构图
1.2 模式
oauth2根据使用场景不同,分成了4种模式
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
2. 入门
2.1 授权码模式
项目结构如下
新建pom 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.8.BUILD-SNAPSHOT</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo1</artifactId><version>0.0.1-SNAPSHOT</version><name>demo1</name><description>Demo project for Spring Boot</description><properties><java.version>11</java.version><spring-cloud.version>Hoxton.BUILD-SNAPSHOT</spring-cloud.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>io.projectreactor</groupId><artifactId>reactor-test</artifactId><scope>test</scope></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><snapshots><enabled>true</enabled></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url></pluginRepository><pluginRepository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><snapshots><enabled>true</enabled></snapshots></pluginRepository></pluginRepositories></project>
准备UserDetalService
package com.example.demo1.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;@Service
public class LoginService implements UserDetailsService {@AutowiredPasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 自定义 用户登录名if(!username.equals("admin")){throw new UsernameNotFoundException("这是俺自己写的错误");}String password=passwordEncoder.encode("123");return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_abc"));}
}
配置类
package com.example.demo1.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().loginPage("/myLoginController") // 一定要和 Controller 中 返回 myLogin页面 一致,.loginProcessingUrl("/login");// 必须和表单提交 action 的名字 一样的,提交 username 和password// .successForwardUrl("/toSuccess");// 这个是 登录成功后返回的界面http.authorizeRequests().antMatchers("/myLoginController").permitAll()// 放行myLoginController.antMatchers("/oauth/**","/login/**","/logout/**").permitAll().anyRequest().authenticated();http.csrf().disable();}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}
配置授权服务器–微信,qq 的授权服务器
package com.example.demo1.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;/*** 这个是授权服务器* 功能类似于 微信,qq 等大场企业*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServiceConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate PasswordEncoder encoder;@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("wechat").secret(encoder.encode("112233")) // 秘钥.redirectUris("https://www.baidu.com") //重定向服务器配置.scopes("all")// 授权范围.authorizedGrantTypes("authorization_code");}
}
配置资源服务器
package com.example.demo1.config;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;/*** 这个是类似于 微信 qq 等大场的资源*/
@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable();http.authorizeRequests().antMatchers("/user/**").permitAll().anyRequest().authenticated();}
}
配置Controller
package com.example.demo1.controller;import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getCurrentUser")public Object kk(Authentication authentication){return authentication.getPrincipal();}
}
输入网址
http://localhost:8082/oauth/authorize?response_type=code&client_id=wechat&redirect_uri=https://www.baidu.com&scope=all
会登录 输入admin,123
会出现以下
允许以后
会跳转到百度。然后后面的code 很重要
打开postman
接下来输输入请求资源的地方
原理讲解
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站,授权用户数据给 A 网站使用。下面就是 A 网站跳转 B 网站的一个示意链接。
https://b.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
上面 URL 中,response_type
参数表示要求返回授权码(code
),client_id
参数让 B 知道是谁在请求,redirect_uri
参数是 B 接受或拒绝请求后的跳转网址,scope
参数表示要求的授权范围(这里是只读)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AUH52oeg-1609421689920)(picture/bg2019040902.jpg)]
第二步,用户跳转后,B 网站会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,这时 B 网站就会跳回redirect_uri
参数指定的网址。跳转时,会传回一个授权码,就像下面这样。
https://a.com/callback?code=AUTHORIZATION_CODE
上面 URL 中,code
参数就是授权码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jexiIrs3-1609421689921)(picture/bg2019040907.jpg)]
第三步,A 网站拿到授权码以后,就可以在后端,向 B 网站请求令牌。
https://b.com/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL
上面 URL 中,client_id
参数和client_secret
参数用来让 B 确认 A 的身份(client_secret
参数是保密的,因此只能在后端发请求),grant_type
参数的值是AUTHORIZATION_CODE
,表示采用的授权方式是授权码,code
参数是上一步拿到的授权码,redirect_uri
参数是令牌颁发后的回调网址。
第四步,B 网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri
指定的网址,发送一段 JSON 数据。
{ "access_token":"ACCESS_TOKEN","token_type":"bearer","expires_in":2592000,"refresh_token":"REFRESH_TOKEN","scope":"read","uid":100101,"info":{...}
}
上面 JSON 数据中,access_token
字段就是令牌,A 网站在后端拿到了。
2.2 密码模式
配置类修改
package com.example.demo1.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().loginPage("/myLoginController") // 一定要和 Controller 中 返回 myLogin页面 一致,.loginProcessingUrl("/login");// 必须和表单提交 action 的名字 一样的,提交 username 和password// .successForwardUrl("/toSuccess");// 这个是 登录成功后返回的界面http.authorizeRequests().antMatchers("/myLoginController").permitAll()// 放行myLoginController.antMatchers("/oauth/**","/login/**","/logout/**").permitAll().anyRequest().authenticated();http.csrf().disable();}@Override@Beanpublic AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}
授权服务器修改
package com.example.demo1.config;import com.example.demo1.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;/*** 这个是授权服务器* 功能类似于 微信,qq 等大场企业*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServiceConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate PasswordEncoder encoder;@Autowiredprivate AuthenticationManager authenticationManager;@AutowiredLoginService service;@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManager).userDetailsService(service);}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("wechat").secret(encoder.encode("112233")) // 秘钥.redirectUris("https://www.baidu.com") //重定向服务器配置.scopes("all")// 授权范围.authorizedGrantTypes("authorization_code","password");}
}
输入网址
http://localhost:8082/oauth/token
结果
原理
如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。
第一步,A 网站要求用户提供 B 网站的用户名和密码。拿到以后,A 就直接向 B 请求令牌。
https://oauth.b.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID
上面 URL 中,grant_type
参数是授权方式,这里的password
表示"密码式",username
和password
是 B 的用户名和密码。
第二步,B 网站验证身份通过后,直接给出令牌。注意,这时不需要跳转,而是把令牌放在 JSON 数据里面,作为 HTTP 回应,A 因此拿到令牌。
这种方式需要用户给出自己的用户名/密码,显然风险很大,因此只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。
2.3 简化模式
有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)“隐藏式”(implicit)。
第一步,A 网站提供一个链接,要求用户跳转到 B 网站,授权用户数据给 A 网站使用。
https://b.com/oauth/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
上面 URL 中,response_type
参数为token
,表示要求直接返回令牌。
第二步,用户跳转到 B 网站,登录后同意给予 A 网站授权。这时,B 网站就会跳回redirect_uri
参数指定的跳转网址,并且把令牌作为 URL 参数,传给 A 网站。
https://a.com/callback#token=ACCESS_TOKEN
上面 URL 中,token
参数就是令牌,A 网站因此直接在前端拿到令牌。
注意,令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。
这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。
2.4 客户端模式
最后一种方式是凭证式(client credentials),适用于没有前端的命令行应用,即在命令行下请求令牌。
第一步,A 应用在命令行向 B 发出请求。
https://oauth.b.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
上面 URL 中,grant_type
参数等于client_credentials
表示采用凭证式,client_id
和client_secret
用来让 B 确认 A 的身份。
第二步,B 网站验证通过以后,直接返回令牌。
这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。
令牌的使用
A 网站拿到令牌以后,就可以向 B 网站的 API 请求数据了。
此时,每个发到 API 的请求,都必须带有令牌。具体做法是在请求的头信息,加上一个Authorization
字段,令牌就放在这个字段里面。
curl -H "Authorization: Bearer ACCESS_TOKEN" \
"https://api.b.com"
上面命令中,ACCESS_TOKEN
就是拿到的令牌。
更新令牌
令牌的有效期到了,如果让用户重新走一遍上面的流程,再申请一个新的令牌,很可能体验不好,而且也没有必要。OAuth 2.0 允许用户自动更新令牌。
具体方法是,B 网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。
https://b.com/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN
上面 URL 中,grant_type
参数为refresh_token
表示要求更新令牌,client_id
参数和client_secret
参数用于确认身份,refresh_token
参数就是用于更新令牌的令牌。
B 网站验证通过以后,就会颁发新的令牌。
写到这里,颁发令牌的四种方式就介绍完了。下一篇文章会编写一个真实的 Demo,演示如何通过 OAuth 2.0 向 GitHub 的 API 申请令牌,然后再用令牌获取数据。
2.5 Redis 存储令牌
创建配置类
package com.example.demo1.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;@Configuration
public class RedisConfig {@AutowiredRedisConnectionFactory redisConnectionFactory;@Beanpublic TokenStore tokenStore(){return new RedisTokenStore(redisConnectionFactory);}
}
授权服务器
package com.example.demo1.config;import com.example.demo1.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;/*** 这个是授权服务器* 功能类似于 微信,qq 等大场企业*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServiceConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate PasswordEncoder encoder;@Autowiredprivate AuthenticationManager authenticationManager;@AutowiredTokenStore tokenStore;@AutowiredLoginService service;@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManager).userDetailsService(service).tokenStore(tokenStore);}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("wechat").secret(encoder.encode("112233")) // 秘钥.redirectUris("https://www.baidu.com") //重定向服务器配置.scopes("all")// 授权范围.authorizedGrantTypes("authorization_code","password");}
}
输入网址
打开redis
SpringSecurity入门到入土教程_2 Oauth教程相关推荐
- SpringSecurity入门到入土教程_1
https://gitee.com/fakerlove/spring-security 文章目录 SpringSecurity 教程 1. 简介 1.1 概念 1.2 入门案例 1.3 自定义登录逻辑 ...
- python人生的不同阶段_从入门到入土的Python自学教程,用改变你的人生轨迹
Python在近几年越来越受追捧,很多童鞋或者职场小伙伴想要提升技能-学习Python. 这是非常好的事情,但问题在于很多人不知道学Python做什么,所以什么零碎细末.艰难晦涩.长篇大论的都去看,很 ...
- 从入门到入土,Redis简明教程
作者:徐家三少 juejin.im/post/5880590d1b69e60058c72803 解压后的安装 [root@server1 redis-3.0.5]# make 指定安装目录: [roo ...
- Flink 教程 gitbook 从入门到入土(详细教程)
Flink从入门到入土(详细教程) 和其他所有的计算框架一样,flink也有一些基础的开发步骤以及基础,核心的API,从开发步骤的角度来讲,主要分为四大部分 1.Environment Flink J ...
- flowable画图教程_Flowable从入门到入土(1)-初始Flowable
Flowable从入门到入土 作者:独钓寒江雪 一剑一酒一江湖, 便是此生 心中有图,何必点灯.装载请注明出处 前文传送门: 简介 当前周围好多人都对flowable有所了解乃至投入到生产使用,此文章 ...
- PIXIJS超级详细教程【从入门到入土-下】
来自GitHub教程 GitHub - Zainking/LearningPixi: ⚡️Pixi教程中文版 PIXIJS教程[从入门到入土-上]地址[https://blog.csdn.net/qq ...
- PixiJS超级详细教程【从入门到入土-上】
PixiJS 来自GitHub教程 GitHub - Zainking/LearningPixi: ⚡️Pixi教程中文版 PixiJS超级详细教程[从入门到入土-下]地址[https://blog. ...
- java.securti_springboot集成springsecurity 使用OAUTH2做权限管理的教程
Spring Security OAuth2 主要配置,注意application.yml最后的配置resource filter顺序配置,不然会能获取token但是访问一直 没有权限 WebSecu ...
- C#游戏开发快速入门教程Unity5.5教程
C#游戏开发快速入门教程Unity5.5教程 试读文档下载地址:http://pan.baidu.com/s/1slwBHoD C#是微软发布的高级程序设计语言,这门语言和C语言一样,已经成为了大学计 ...
最新文章
- IOS 自定义相机, 使用 AVFoundation(附实现部分腾讯水印相机功能 demo)
- 代币转账_手把手教你从源代码开始搭建多节点以太坊私链(五)部署智能合约及代币发行...
- 腾讯技术直播间 | Apache IoTDB x Apache Pulsar Meetup
- 11.Wave Shader
- 7-6 区间覆盖 (10 分)(思路+详解)Come 宝!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- 解决高版本SpringBoot整合swagger时启动报错:Failed to start bean ‘documentationPluginsBootstrapper‘ 问题
- Eclipse开发Android程序如何在手机上运行
- linux6.4网络yum 源,CentOS 6.4使用本地yum源
- python编程例子-Python面向对象编程 - 类和实例
- Unity Plastic SCM (无法托管/当前仓库地址错误/创建新项目无法托管/由于目标计算机积极无法连接)
- ERP仓库管理系统主要功能
- Ubuntu 使用 xdg-open 命令
- 清华计算机专业作业,微计算机技术(清华)配套练习题及答案 作业2(答案)END
- 关于男女交往的换位思考
- 骁龙8+gen1和天玑9000+区别 骁龙8+和天玑9000+评测选哪个好
- html怎么实现展开,HTML使用DIV+css实现展开全文的功能
- DiskGenius 复制磁盘 提示 设备未就绪
- 毕业论文评审意见范例
- PHP导入(百万级)Excel表格数据
- HTMLday2旅途
热门文章
- 深度学习中所需的线性代数知识
- 如何去读Binder的源码
- Makefile之 .PHONY 作用
- Centos安装Clion
- Visual Assist X安装
- VALSE学习(十一):弱监督图像语义分割
- XP系统访问win7共享文件夹教程和提示没有权限的解决办法
- 2018linux市场份额数据,2018年7月Windows 10市场份额上涨,Linux仅占1.35%
- django配置邮件服务器,python – 使用Bluehost电子邮件服务器的Django电子邮件配置...
- python写sql语句_python3将变量写入SQL语句的实现方式