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表示"密码式",usernamepassword是 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_idclient_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教程相关推荐

  1. SpringSecurity入门到入土教程_1

    https://gitee.com/fakerlove/spring-security 文章目录 SpringSecurity 教程 1. 简介 1.1 概念 1.2 入门案例 1.3 自定义登录逻辑 ...

  2. python人生的不同阶段_从入门到入土的Python自学教程,用改变你的人生轨迹

    Python在近几年越来越受追捧,很多童鞋或者职场小伙伴想要提升技能-学习Python. 这是非常好的事情,但问题在于很多人不知道学Python做什么,所以什么零碎细末.艰难晦涩.长篇大论的都去看,很 ...

  3. 从入门到入土,Redis简明教程

    作者:徐家三少 juejin.im/post/5880590d1b69e60058c72803 解压后的安装 [root@server1 redis-3.0.5]# make 指定安装目录: [roo ...

  4. Flink 教程 gitbook 从入门到入土(详细教程)

    Flink从入门到入土(详细教程) 和其他所有的计算框架一样,flink也有一些基础的开发步骤以及基础,核心的API,从开发步骤的角度来讲,主要分为四大部分 1.Environment Flink J ...

  5. flowable画图教程_Flowable从入门到入土(1)-初始Flowable

    Flowable从入门到入土 作者:独钓寒江雪 一剑一酒一江湖, 便是此生 心中有图,何必点灯.装载请注明出处 前文传送门: 简介 当前周围好多人都对flowable有所了解乃至投入到生产使用,此文章 ...

  6. PIXIJS超级详细教程【从入门到入土-下】

    来自GitHub教程 GitHub - Zainking/LearningPixi: ⚡️Pixi教程中文版 PIXIJS教程[从入门到入土-上]地址[https://blog.csdn.net/qq ...

  7. PixiJS超级详细教程【从入门到入土-上】

    PixiJS 来自GitHub教程 GitHub - Zainking/LearningPixi: ⚡️Pixi教程中文版 PixiJS超级详细教程[从入门到入土-下]地址[https://blog. ...

  8. java.securti_springboot集成springsecurity 使用OAUTH2做权限管理的教程

    Spring Security OAuth2 主要配置,注意application.yml最后的配置resource filter顺序配置,不然会能获取token但是访问一直 没有权限 WebSecu ...

  9. C#游戏开发快速入门教程Unity5.5教程

    C#游戏开发快速入门教程Unity5.5教程 试读文档下载地址:http://pan.baidu.com/s/1slwBHoD C#是微软发布的高级程序设计语言,这门语言和C语言一样,已经成为了大学计 ...

最新文章

  1. IOS 自定义相机, 使用 AVFoundation(附实现部分腾讯水印相机功能 demo)
  2. 代币转账_手把手教你从源代码开始搭建多节点以太坊私链(五)部署智能合约及代币发行...
  3. 腾讯技术直播间 | Apache IoTDB x Apache Pulsar Meetup
  4. 11.Wave Shader
  5. 7-6 区间覆盖 (10 分)(思路+详解)Come 宝!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  6. 解决高版本SpringBoot整合swagger时启动报错:Failed to start bean ‘documentationPluginsBootstrapper‘ 问题
  7. Eclipse开发Android程序如何在手机上运行
  8. linux6.4网络yum 源,CentOS 6.4使用本地yum源
  9. python编程例子-Python面向对象编程 - 类和实例
  10. Unity Plastic SCM (无法托管/当前仓库地址错误/创建新项目无法托管/由于目标计算机积极无法连接)
  11. ERP仓库管理系统主要功能
  12. Ubuntu 使用 xdg-open 命令
  13. 清华计算机专业作业,微计算机技术(清华)配套练习题及答案 作业2(答案)END
  14. 关于男女交往的换位思考
  15. 骁龙8+gen1和天玑9000+区别 骁龙8+和天玑9000+评测选哪个好
  16. html怎么实现展开,HTML使用DIV+css实现展开全文的功能
  17. DiskGenius 复制磁盘 提示 设备未就绪
  18. 毕业论文评审意见范例
  19. PHP导入(百万级)Excel表格数据
  20. HTMLday2旅途

热门文章

  1. 深度学习中所需的线性代数知识
  2. 如何去读Binder的源码
  3. Makefile之 .PHONY 作用
  4. Centos安装Clion
  5. Visual Assist X安装
  6. VALSE学习(十一):弱监督图像语义分割
  7. XP系统访问win7共享文件夹教程和提示没有权限的解决办法
  8. 2018linux市场份额数据,2018年7月Windows 10市场份额上涨,Linux仅占1.35%
  9. django配置邮件服务器,python – 使用Bluehost电子邮件服务器的Django电子邮件配置...
  10. python写sql语句_python3将变量写入SQL语句的实现方式