UAA搭建

the User Account and Authentication (UAA) Server,用户账户、鉴权服务。

The primary role of UAA is as an OAuth2 provider, issuing tokens for client apps.

OAuth defines four roles:

  • resource owner

    An entity capable of granting access to a protected resource. When the resource owner is a person, it is referred to as an end-user.

  • resource server

    The server hosting the protected resources, capable of accepting and responding to protected resource requests using access tokens.

  • client

    An application making protected resource requests on behalf of the resource owner and with its authorization. The term “client” does not imply any particular implementation characteristics (e.g., whether the application executes on a server, a desktop, or other devices).

  • authorization server

    The server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization.

一、环境

  • 操作系统:CentOS 7
  • Java Version:jdk-8u281-linux-i586.tar.gz
  • Tomcat Version:apache-tomcat-8.5.34.tar.gz
  • UAA WAR:cloudfoundry-identity-uaa-4.30.0.war

cloudfoundry uaa的官方指导文档,doc,主要介绍uaa的能力,以及搭建方式。doc中的搭建方式需要本地编译,问题有点多。推荐使用war包部署。

二、部署UAA

cloudfoudry是一个云平台,包含很多组件,这里仅使用它的UAA服务。

2.1 UAA.yml

UAA war有默认的配置文件uaa.yml,同时支持额外外部自定义配置文件。实现方式:

1.UAA_CONFIG_PATH

通过环境变量指定配置文件位置,UAA会去检查此目录下的uaa.yml文件

export UAA_CONFIG_PATH=/root/.uaa

2.yaa.yml

参考:

  • Sysadmin-Guide.rst 介绍uaa.yml的部分字段含义
  • uaa job from uaa/75.0.0 介绍uaa.yml的字段含义,参考意义大于实用意义

自定义配置:

issuer:uri: http://localhost:8080/uaaencryption:active_key_label: CHANGE-THIS-KEYencryption_keys:- label: CHANGE-THIS-KEYpassphrase: CHANGEMElogin:serviceProviderKey: |-----BEGIN RSA PRIVATE KEY-----MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABAoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGulhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3XpubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYXkKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyLgu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWKvw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCeA2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHSN+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMBqy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/-----END RSA PRIVATE KEY-----serviceProviderKeyPassword: passwordserviceProviderCertificate: |-----BEGIN CERTIFICATE-----MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=-----END CERTIFICATE-----#The secret that an external login server will use to authenticate to the uaa using the id `login`
LOGIN_SECRET: loginsecretspring_profiles: postgresql,default
database:driverClassName: org.postgresql.Driverurl: jdbc:postgresql://localhost:5432/uaausername: postgrespassword: 123#jwt:
#  token:
#    signing-key: tokenKey
jwt:token:signing-key: |-----BEGIN RSA PRIVATE KEY-----MIIEpAIBAAKCAQEA7FfVYQay0MEWPD5y2bRo8rKzz5ngvg7PFTZz756pKA7Q1a9bnW5FtLQ5y2k+SFdOFKzEMdgAfyxMX2RfL9cChUJVkgagESlDrJ2U1BFEbEuTEXl5ZKGX5XRPt82Zk4A/gxDyAcQuqeQcDD7vWYCaQV2c1j8qsgr/ZuS1Bopq091x6VXliSX9P+4YZVjP1+Us5CF7iSJTwuABWCe6dEseugs8gqWYkPm6c8KzvV3m+lLFXixQr0UBgv552UHMZSJ+anybZB3uSpy6zRYANY8cEkIDjDK6fU8PG9/IvNP2HWScwUyfEkFw+dBqaYtu0Jp/DfE2jDHabRbQJqsQadANXQIDAQABAoIBADXueR+x8p4WYaePrI+nToeLZeLKv3E/WdwCWARnFTyx3M/WOza6kieBNOsI8hB587ReFEs3ei2LA0aFVf9JtiaIk5RF9MLVwr6iGvMlmZKI0F+dc6kWAt52YzaTMSdqjZOwCzmB9hAIoKnetqma4hhmb0KomWqVfeCR8gkzDtuXbXtj8Cv2tgpWNmYCq7mZCar9ZNJomhTJtWavIdunYuruP+pOI2G6BElHGofdqFfOCLczCCSeWjD/C4D2Htc7Ee2Gj8uyHSveqaxnCjvAuDne+Sh9+rS2RYnmI3QVHeMHFAuAVDpk6gVrs7WEQsg0kIxgSqRR2X3sruVVaU0N4qECgYEA+9A96ViKQgW3sUXI+NWa/EHNY7tXy3g4lBrSFgnGsU4qm30xwam9kFjEPx2c6+HTxp1lnNYhZxJI6miNYqwMsNtaqHrkI16RoNG/9eaiNhxrOD7rnYYY7+5YJQmGSPDzayZktqStqOQgmoWs2vEGxM29eQv8AXDSmpVSIJa+KIUCgYEA8EW/VqBFbBcH2KnOSnuGEHOlhtFPaURgE1icjYzRT9GkJ1Y/pugZ6T1/SgAtKcTD8AcMy4Lj14dNC9Y4laiwCJsof+QBnRSMJRt9F7t9GdFbbJAR6C3Rewwe+U3IpyDjWkSBPRk3nG2TTu+y0aCPR/RsYipsbE6VgXxjwvwDVPkCgYAC1+MbE2jcPfxJACS4ypCpcITFL4RaQ80/vt3IaevYbK2Ge+9n5GbDjn0IyWjQMQiXIYfYMYLHCynPm8ac6pxqEs//PwP8ckDqs/Oa7zO9sKx1QiCe8ritXN+Z63WctTvKZfCVL17WnVzQ4dmFz1roNfqBt2TtDz0RicYXoBwdkQKBgQCnJH3kLv3cIXFN4WImIiOy0iA11uldGzmSe7P8LBd3ZSjCTJde7lsIC8W+nrzML5r2IJFgCR+iUPbh4xXd1kkO05Cq1tvgf+i175dnqP9vtFna/aXXU/hDlrz9RITu7kv6AWm+LQqogPlWkhxdA0ppDblP2J8wAMK0HunvPAy9UQKBgQC6t/jAQd/lukod/fHozDjHmG5R+oA2L5OmNkRh95p/T2C2fpjJ2LYXMzkjT9jKSKZOwrioiFx2yS8TMqxt63V9icH+qxy92jyXyeew2TsaIAIwfMqfuE42LesyFJraT+yAYN3I1815JltFyZADx66EDjkrQ7N3843+MUyvInHpzg==-----END RSA PRIVATE KEY-----verification-key: |-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7FfVYQay0MEWPD5y2bRo8rKzz5ngvg7PFTZz756pKA7Q1a9bnW5FtLQ5y2k+SFdOFKzEMdgAfyxMX2RfL9cChUJVkgagESlDrJ2U1BFEbEuTEXl5ZKGX5XRPt82Zk4A/gxDyAcQuqeQcDD7vWYCaQV2c1j8qsgr/ZuS1Bopq091x6VXliSX9P+4YZVjP1+Us5CF7iSJTwuABWCe6dEseugs8gqWYkPm6c8KzvV3m+lLFXixQr0UBgv552UHMZSJ+anybZB3uSpy6zRYANY8cEkIDjDK6fU8PG9/IvNP2HWScwUyfEkFw+dBqaYtu0Jp/DfE2jDHabRbQJqsQadANXQIDAQAB-----END PUBLIC KEY-----smtp:host: testmail.virtual.comport: 25user: test@testmail.virtual.compassword: xxx

issuer

token的分发者

login

database

UAA will use an in-memory database that is torn down between runs unless you choose a spring profile or a specific database configuration as a toplevel setting in uaa.yml. An example connecting to a postgres database:

spring_profiles: postgresql,default
database:driverClassName: org.postgresql.Driverurl: jdbc:postgresql://localhost:5432/uaausername: postgrespassword: 123

jwt

UAA can use either symmetric key encryption (shared secrets) or public key encryption.

Generating new asymmetric key pairs

# jwt.token.signing-key
openssl genrsa -out privkey.pem 2048
# jwt.token.verification-key
openssl rsa -pubout -in privkey.pem -out pubkey.pem

smtp

邮件服务器配置,允许通过页面注册用户,需要配置邮箱服务器。

smtp:host: testmail.virtual.comport: 25user: test@testmail.virtual.compassword: xxx

2.2 Tomcat部署

正常tomcat部署即可。

浏览器访问: http://localhost:8080/uaa,有登陆界面即可

三、UAAC

管理员用户的命令行管理工具,可以对client、group、user、scope进行管理。

参考:

  • User Account and Authentication (UAA) Server 介绍UAA验证服务的搭建
  • Creating and Managing Users with the UAA CLI (UAAC) 介绍UAAC的使用方式
  • UAA Concepts 介绍UAA中zone、client、group、client的部分概念

2.1 安装cf-uaac

uaac依赖ruby环境,先安装ruby的环境,yum默认的是2.0.0版本,不可用,需要安装高版本的ruby。安装方式如下:

升级gcc环境

yum install -y gcc gcc-c++

scl安装

yum install centos-release-scl-rh //会在/etc/yum.repos.d/目录下多出一个CentOS-SCLo-scl-rh.repo源

yum install rh-ruby27 -y     //直接yum安装即可

yum install rh-ruby27-ruby-devel

scl enable rh-ruby27 bash    //必要一步

ruby -v    //查看安装版本 ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]

gem -v //查看安装版本 3.1.2

cf-uaac

gem install cf-uaac // 安装uaac

uaac -v // 查看版本 UAA client 4.2.0

2.2 设置uaac指向

将uaac指向正在运行中的UAA服务实例

uaac target http://localhost:8080/uaa

2.3 注册client、user、group

./WEB-INF/spring/oauth-clients.xml 有默认的client,其中admin具有管理员权限,可以注册、变更client、group、user信息

<entry key="admin"><map><entry key="authorized-grant-types" value="client_credentials" /><entry key="scope" value="uaa.none" /><entry key="authorities" value="uaa.admin,clients.read,clients.write,clients.secret,scim.read,scim.write,clients.admin" /><entry key="secret" value="adminsecret" /></map>
</entry>

client、group、user的操作都属于管理员操作,操作前,需要验证自身具备这些权限:

uaac token client get admin -s adminsecret

使用uaac contexts可以查看当前的连接信息,这部分信息默认存储在~/.uaac.yml

uaac contexts

查看当前已有的client

uaac clients

新建client

UAA是一个OAuth2服务,在服务启动后,开发者必须第一时间创建一个client。

client采用XHR的简单验证。

uaac client add webappclient -s webappclientsecret
–name WebAppClient
–scope resource.read,resource.write,openid,profile,email,address,phone
–authorized_grant_types authorization_code,refresh_token,client_credentials,password
–authorities uaa.resource
–redirect_uri http://localhost:8081/login/oauth2/code/uaa

  • name 客户端名称
  • scope 客户端支持的权限范围。默认scope解释
  • authorized_grant_types 客户端支持的验证类型。参考select-type
  • authorities
  • redirect_uri 用户验证成功后,默认的跳转路径

此时执行uaac clients可以看到新建的client WebAppClient。

新建用户

uaac user add appuser -p appusersecret --emails appuser@acme.com

此时执行 uaac users -a username可以看到新建的用户

新建权限组

UAA有默认的权限组,不同的权限组代表具有不能的能力。另,支持自定义权限组。

uaac group add resource.read

uaac group add resource.write

此时执行uaac groups -a displayname可以看到新建的用户

关联权限组

uaac member add resource.read appuser

uaac member add resource.write appuser

四、Rest API

UAA服务提供REST API的访问,参考文档:

  • 理解OAuth2.0
  • The OAuth 2.0 Authorization Framework
  • UAA-APIs.rst
  • UAA API DOC

1. 获取Admin凭证

xhr: doc

客户端admin账户是UAA服务的内置账户,存在配置文件./WEB-INF/spring/oauth-clients.xml,有UAA服务中client、group、user的读写权限。

curl 'http://localhost:8080/uaa/oauth/token' -i -u 'admin:adminsecret' -X POST -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: application/json' -d 'grant_type=client_credentials&token_format=opaque'

参数

  • token_format token格式化方式。opaque和jwt,前者为不透明的token

Response

{"access_token": "d6eb1382943c4f819bc143c8912c655b","token_type": "bearer","expires_in": 43199,"scope": "clients.read clients.secret clients.write uaa.admin clients.admin scim.write scim.read","jti": "d6eb1382943c4f819bc143c8912c655b"
}

备注:

下面出现的${ADMIN_TOKEN}为:

d6eb1382943c4f819bc143c8912c655b

2. Client API

支持对客户端的单个、批量操作。

2.1 Create One Client

xhr:doc

 curl 'http://192.168.10.56:8080/uaa/oauth/clients' -i -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer ${ADMIN_TOKEN}' -H 'Accept: application/json' -d '{ "scope" : [ "clients.read", "clients.write", "openid", "profile", "resource.read", "resource.write" ], "client_id" : "uiclient", "client_secret" : "secret", "resource_ids" : [ ], "authorized_grant_types" : [ "client_credentials" ], "authorities" : [ "clients.read", "clients.write", "openid", "profile","resource.read", "resource.write" ], "token_salt" : "cdGXbD", "autoapprove" : true, "name" : "uiclient web client" }'

请求头:

  • authorization:值为第一步(获取Admin凭证)请求的Response的access_token

参数

  • scope:客户端具备的范围
  • authorities:创建一个客户端时,标识这个客户端能给予USER的授权范围
  • redirect_uri:用户在当前客户端鉴权成功后,重定向的路由

2.2 Delete One Client

xhr:doc

curl 'http://192.168.10.56:8080/uaa/oauth/clients/uiclient' -i -X DELETE -H 'Authorization: Bearer ${ADMIN_TOKEN}' -H 'Accept: application/json'

Response:

{"scope": ["clients.read","clients.write","openid"],"client_id": "uiclient","resource_ids": ["none"],"authorized_grant_types": ["client_credentials"],"redirect_uri": ["https://www.baidu.com"],"autoapprove": ["true"],"authorities": ["clients.read","clients.write"],"token_salt": "cdGXbD","name": "uiclient web client","lastModified": 1617778508969,"required_user_groups": []
}

2.3 Update One Client

xhr:doc

curl 'http://localhost:8080/uaa/oauth/clients/uiclient' -i -X PUT -H 'Content-Type: application/json' -H 'Authorization: Bearer ${ADMIN_TOKEN}' -H 'Accept: application/json' -d '{"client_id": "uiclient", "scope" : [ "swl.test" ] }'

2.4 Retrieve One Client Info

xhr:doc

curl 'http://192.168.10.56:8080/uaa/oauth/clients/uiclient' -i -X GET -H 'Authorization: Bearer ${ADMIN_TOKEN}' -H 'Accept: application/json'

Response:

{"scope": ["clients.read","clients.write"],"client_id": "uiclient","resource_ids": ["none"],"authorized_grant_types": ["client_credentials"],"redirect_uri": ["https://www.baidu.com"],"autoapprove": ["true"],"authorities": ["clients.read","clients.write"],"token_salt": "cdGXbD","name": "uiclient web client","lastModified": 1617778508969,"required_user_groups": []
}

3. Group API

doc

4. User API

doc

五、uaa-client-side工具库

The table below describes the client-side tools and libraries UAA uses:

Name Language
UAAC CF-UAA-LIB Ruby
Spring Security OAuth Java
CF Java Client Java
UAA Javascript SDK (Singular) JS

【UAA】从部署到接口调用相关推荐

  1. SpringBoot集成Axis2,部署webservice接口并调用

    SpringBoot集成Axis2,部署webservice接口并调用 一.省略新建springboot项目过程 1.pom.xml 2.application.yml 3.准备工作做完,编写serv ...

  2. 微服务实战之春云与刀客(三)—— 面向接口调用代码结构实例

    2019独角兽企业重金招聘Python工程师标准>>> 概述 在上一篇中提到了spring cloud 面向接口调用的开发风格,这一篇会举一个简单的但完整的例子来说明整个代码结构. ...

  3. EasyNVR摄像机网页无插件直播方案H5前端构建之:关于接口调用常见的一些问题(401 Unauthorized)...

    背景分析 最近在使用EasyNVR的过程中,很多小伙伴咨询关于接口调用的问题,初步判断应该是遇到权限问题(401 Unauthorized).EasyNVR为第三方系统和应用提供了标准的API接口,方 ...

  4. vue和Java做数据交互_基于vue和springmvc前后端分离,json类接口调用介绍

    基于vue和springmvc前后端分离,json类接口调用介绍 版本要求:spring-3.2.9.RELEASE.vue-2.9.2.axios-0.17.1,其中axios作为http clie ...

  5. 海康威视接口调用报错处理

    1.批量添加人员 处理方法: 注:身份证有些服务器配置的时候可能会必填,且不能重复 2.人脸添加接口问题 图片压缩为base64后需要在10KB-200KB之间,上传的base64需要丢掉头,从/9开 ...

  6. 打造个人版微信小程序(1)——本地开发api接口调用

    如果觉得这篇文章对您有所启发,欢迎关注我的公众号,我会尽可能积极和大家交流,谢谢.   从今天开始,开始打造一个个人版的微信小程序,尽早上线,方便大家使用以及技术讨论.这套小程序包括前台.后台.数据库 ...

  7. 基于Django实现Linux运维管理平台的整个实现过程和各种API接口调用以及Echarts绘图项目介绍(一)记录点滴生活

    基于Django实现Linux运维管理平台整个实现过程和各种API接口调用以及Echarts绘图的使用介绍 项目内容涉及技术直通车: 我的项目仓库:MyGitHub https://github.co ...

  8. EasyCVR平台基于萤石云SDK接入的设备播放流程及接口调用

    EasyCVR视频融合云服务支持海量视频汇聚与管理.处理与分发.智能分析等视频能力,在功能上,可支持视频监控直播.云端录像.云存储.录像检索与回看.智能告警.平台级联.服务器集群等.EasyCVR平台 ...

  9. 营业执照识别api接口调用OCR识别

    营业执照识别 营业执照云识别 营业执照识别api产品描述 营业执照识别api开发的一款基于服务器平台的营业执照OCR识别服务程序,支持主流Windows.Linux 服务器平台.上传营业执照图像在服务 ...

最新文章

  1. HTML5 drag drop 拖拽与拖放简介
  2. 关于SAP物流和供应链模块发展的一点思考
  3. Android 使用Picasso加载网络图片等比例缩放
  4. 在linux中安装Qt4.8,在linux 如何安装qt 4.8.1
  5. javascript 懒加载技术(lazyload)简单实现
  6. python 上传excel_简历批量合并Python+VBA小工具
  7. 为什么拙劣的软件也会成功?
  8. Django使用消息提示简单的弹出个对话框
  9. CDN > 域名管理 > 访问控制 > URL鉴权配置 > URL鉴权
  10. c lambda表达式 select 改变字段名称_C博客作业01--分支、顺序结构 - 吖黑大帅
  11. Android RxJava
  12. java jxl 复制单元格_如何用JAVA(如poi、jxl等)读取excel文件中的下拉框单元格的值。...
  13. python机器学习生物信息学
  14. Universal Radio Hacker(URH):一个用于逆向解析和攻击无线通信协议的开源工具
  15. Tensorflow2.0之FGSM
  16. 安装VMware15.5+安装win10虚拟机操作系统
  17. 项目1 :家庭记账系统
  18. RGB与16进制颜色值的相互转换
  19. 【签约】ManageEngine卓豪签约中国大饭店 | 智能解析日志、洞察威胁,提升数据安全合规性
  20. 神舟Z8-SP7D1驱动安装

热门文章

  1. 什么是CPC,CPL,CPM,CPL,CPS,PPC,广告?
  2. 精准设置GPU占用率
  3. Ka-CHOCO国产士力架的测评
  4. 如何用ICA去除脑电信号中的干扰?
  5. 计算机接口与应用ppt,微型计算机接口技术及应用.ppt
  6. 使用Jsoup抓取京东图书分类页面图书信息
  7. win7怎样关闭开机自检扫描硬盘
  8. ios post上传文件到服务器,iOS post请求上传文件(OC)
  9. 章鱼网络进展月报 | 2022.4.1-4.30
  10. 联想平板刷机机器人_小机器人跌跌撞撞走向未来:Android平板发展艰难