进入IAM控制台后点击角色,找到上面创建身份池时命名的两个角色名。
点击给经过验证的auth_xx的这个角色,点击附加策略。
测试可以点击附加策略后选择AWSIoTFullAccess全部权限,也可以点击创建内联策略自定义一个,json格式串可以网上搜,自定义主要可以缩小一些权限范围,后面再讲权限策略设置。
至此可以简单理解下:一个用户下载了APP,刚使用时是一个游客,当用户使用了Amazon账户联合登录或者服务器端对他进行了验证合法后用户就获得了auth_xx这个角色的权限,就能和IOT互动了。
3)创建IOT Policy
为什么还需要创建Iot Policy呢,因为官方文档里介绍这种接入AWS IOT的方式是由IAM角色的权限策略和这个identity身份上的权限策略组合而定的,至于两者的权限是如何合并界定参考官方,我也没细致研究。(题外话:如果给unauth_xx身份直接赋予了AWSIoTFullAccess权限是后不用附加IOT策略也是可以连接IOT平台的)
点击安全性->策略,创建一个新策略,内容也可以先设置全部权限,稍后和上面IAM一样再进行细分。
三 、编码
下面介绍以Login with Amzon为身份提供商接入AWS IOT
已准备好的工作:
1>创建了一个身份池,关联定义了两个IAM角色,一个是未经过认证的角色,权限默认不需要更改,另一个是经过认证的角色,附加上了AWSIoTFullAccess权限。该身份提供商Amazon的ID已填写。
2>Iot的后台创建了一个策略,策略先定义的全部权限(参照上图)
3>注意区域选择一致
Android App中接入Login with Amazon不做详解,网上资料齐全没什么难度。
在oncreat方法里先初始化Cognito认证构造方法
credentialsProvider = new CognitoCachingCredentialsProvider(
getApplicationContext(), // Context
"XXXXXXXXXXXX", //Cognito控值台创建的身份池ID
Regions.US\_EAST\_1 // 身份池所属区域
);
题外话:如果是使用的自定义身份验证的方式,就不能使用这个构造方法了,要使用下面这个
DeveloperAuthenticationProvider developerProvider = new DeveloperAuthenticationProvider
(null, Cognito\_Pool\_ID, Regions.US\_EAST\_1,baseApplication);
credentialsProvider = new CognitoCachingCredentialsProvider( context, developerProvider, Regions.US\_EAST\_1 );
developerProvider是需要自己实现的一个方法,继承自AWSAbstractCognitoDeveloperIdentityProvider
public class DeveloperAuthenticationProvider extends AWSAbstractCognitoDeveloperIdentityProvider {
private static final String developerProvider = "xxxxxx";//这是自定义的身份提供商名
private BaseApplication baseApplication;
//构造方法
public DeveloperAuthenticationProvider(String accountId, String identityPoolId, Regions region, BaseApplication baseApplication) {
super(accountId, identityPoolId, region);
this.baseApplication = baseApplication;
}
.............
这个自定义类中主要是要实现refresh方法
@Override
public String refresh() {
String result = "";
String getIdenttid = "";
String token="";
try {
//这里要调用后台服务器方法来获取权限,这里就简单传了存在sp中的用户ID到后台服务器
String appToken = (String)baseApplication.get("token","");
result = AcceletNetWork.synGet(HttpUrls.GetCognitoToken+"&appToken="+appToken);//这里采用的是同步
Gson gson = new Gson();
TokenDataBean dataBean = gson.fromJson(result, TokenDataBean.class);
getIdenttid = dataBean.getIdentityId();
token = dataBean.getToken();
} catch (Exception e) {
e.printStackTrace();
}
//服务器返回两个参数,update一下
update(getIdenttid,token);
return token;
}
服务器也是java编写,代码简单提一下:
服务器使用的AWS java版的SDK,且有V1、V2的版本,请注意区别。
try {
Map<String, String> logins = Collections.singletonMap(developerIdentityProviderName, "single" + ":" + user_sn);//developerIdentityProviderName是在身份池里填写的自定义标识,第二个参数是唯一标识,这里采用的是用户user\_sn
Builder builder = GetOpenIdTokenForDeveloperIdentityRequest.builder()
.identityPoolId(identityPoolId)//身份池ID
.logins(logins)
.tokenDuration(3600\*24L);//有效期
GetOpenIdTokenForDeveloperIdentityRequest request = builder.build();
GetOpenIdTokenForDeveloperIdentityResponse response = getClient().getOpenIdTokenForDeveloperIdentity(request);
//最主要的就是getOpenIdTokenForDeveloperIdentity方法了,官网文档介绍一定要从后台调用
if(response != null) {
JSONObject identityJson = new JSONObject();
identityJson.put("identityId", response.identityId());
identityJson.put("token", response.token());
return identityJson;
}
} catch (Exception e) {
e.printStackTrace();
}
题外话结束,回到LWA版。在使用亚马逊账户登录完成后会返回一个token,更新登录映射
public void onSuccess(AuthorizeResult result) {
String token = result.getAccessToken();
if (null != token) {
/\* The user is signed in \*/
Map<String, String> logins = new HashMap<String, String>();
logins.put("www.amazon.com", token);//标识名固定为www.amazon.com
credentialsProvider.setLogins(logins);//调用setLogins
attachPolicy();
} else {
/\* The user is not signed in \*/
}
}
然后附加IOT策略
private void attachPolicy() {
try{
AWSIotClient awsIotClient = new AWSIotClient(credentialsProvider);
awsIotClient.setRegion(Region.getRegion(Regions.US\_EAST\_1));
AttachPolicyRequest attachPolicyRequest = new AttachPolicyRequest()
.withPolicyName("xxxxx")//IOT控制台里创建的策略名称
.withTarget(credentialsProvider.getIdentityId());//身份池的唯一ID
awsIotClient.attachPolicy(attachPolicyRequest);
}catch (Exception e){
e.printStackTrace();
Log.d(Constants.TAG,"附加策略异常");
}
}
附加IOT策略说明:网上搜有不少介绍使用AWS CLI给identityId加上策略,更有甚者说没有API可调用的。
还是得参考官方文档啊,文档里介绍了核心代码但就是不提供完整的PubSub示例,无语。
这里附加IOT策略需要注意:
1>构造的awsIotClient需要有attachPolicy的权限,之前IAM里经过身份验证的角色赋予了AWSIOTFull全部的权限,所以没问题,但是你如果改了那就要加上AttachPolicy。
2>IOT控制台里创建的策略名称也要正确,如果区域不一致会报找不到策略的错误。如果是控制台里复制时注意左右不要有空格。
3>identityId身份,IOT策略附加到这个角色身上,这个身份对应的一个唯一ID,在Cognito管理后台可以查看。
这个身份ID很有用处,后面要用它连接AWS IOT CORE以及解决缓存问题。
4>由于attachPolicy方法没有回调函数之类的反应是否策略附加成功,各个管理后台也没有查看的地方。但我试了修改策略名称或者随便填一个身份ID,都会报错,所以只要程序没有报错那么策略就附加成功了。
接下来就是准备连接AWS IOT进行MQTT测试了,初始化awsIotMqttManager并进行连接
String tIdentityId= credentialsProvider.getIdentityId();
awsIotMqttManager = new AWSIotMqttManager(tIdentityId, "xxxxxxxxxx");
awsIotMqttManager.setCleanSession(true);//默认为true,类似于一个连接如果断线后服务器不再保留其相关的信息
awsIotMqttManager.setKeepAlive(10);// 将keepalive的时间设置小于服务端的超时时间,则客户端每隔 keepalive的时间就会给服务端发一个心跳包,默认是300s,单位是s
awsIotMqttManager.setAutoReconnect(true);//断开后自动重连
awsIotMqttManager.setMaxAutoReconnectAttempts(1);//设置断网后重新尝试连接的次数,不设的话默认值为-1 ,一直试着连接
try {
awsIotMqttManager.connect(credentialsProvider, (status, throwable) -> {
if(throwable!=null){
Log.d(Constants.TAG,throwable.toString());
}
if (status == AWSIotMqttClientStatusCallback.AWSIotMqttClientStatus.Connecting) {
Log.d(Constants.TAG,"正在连接中");
}else if(status == AWSIotMqttClientStatusCallback.AWSIotMqttClientStatus.Connected){
Log.d(Constants.TAG,"连上了");
}else if (status == AWSIotMqttClientStatusCallback.AWSIotMqttClientStatus.Reconnecting) {
Log.d(Constants.TAG,"重连");
}else if (status == AWSIotMqttClientStatusCallback.AWSIotMqttClientStatus.ConnectionLost) {
Log.d(Constants.TAG,"失去连接");
}else{
Log.d(Constants.TAG,"未知状态");
}
});
}
catch (Exception e)
{
e.printStackTrace();
Log.d(Constants.TAG, "还没有权限操作" );
}
AWSIotMqttManager构造方法传入两个参数,第一个client_id,这个可以是任意的string类型。我这里使用的唯一身份ID,之所以这样使用是因为我配置了权限细分策略里规定我只能使用这个唯一ID才能连接,其他都不可以。之前的示例里使用的UUID.randomUUID().toString()产生随机值作为唯一client_id也是可以的,但也存在两个使用APP的client_id一致的风险,只不过几乎不可能罢了。实际生产环境中这个可不能写成固定值,同一个clientid的话后连的会把先连的给踢掉。第二个参数在IOT控制台里的设置菜单里终端节点。
如果不出意外就可以连接上了,需要注意的是若连不上报下面错误(不得不吐槽AWS各种诡异错误异常都难以定位到底是哪里出错了)
MqttException (0) - java.io.IOException: Already connected
不要参照这篇博客里介绍的直接给IAM中的Auth_xx身份加上个AdministratorAccess权限。根本上虽然都是权限的问题但是需要了解怎么改,这里直接加上AdministratorAccess是不可取的。
awsIotMqttManager有一些可以的属性可以修改,比较有印象的就是setMaxAutoReconnectAttempts方法,刚开始连接失败后一直就不断的重连,后来找到这个方法,设置重连次数后程序就会按照规定的值进行尝试,一直失败直接退出。
然后便是awsIotMqttManager.connect(credentialsProvider…这个方法,这里选择了定义的cognitor联合认证身份的方法,而非采用本地创建存储keystore文件的方法,随取随用。
最后便是订阅和发布了,没有什么难度,要注意的是需要先连接上AWS IOT。
订阅:
try{
awsIotMqttManager.subscribeToTopic("esp32/pub", AWSIotMqttQos.QOS0, new AWSIotMqttSubscriptionStatusCallback() {
@Override
public void onSuccess() {
Log.d(Constants.TAG,"订阅esp32/pub成功!!");
}
@Override
public void onFailure(Throwable exception) {
Log.d(Constants.TAG,"订阅esp32/pub失败!!");
Log.d(Constants.TAG,exception.toString());
}
}, (topic, data) -> {
String str = new String(data);
Log.d(Constants.TAG,"esp32/pub:"+str);
});
}catch (Exception e){
e.printStackTrace();
}
发布:
try{
awsIotMqttManager.publishData("avs".getBytes(), "esp32/pub", AWSIotMqttQos.QOS0, new AWSIotMqttMessageDeliveryCallback() {
@Override
public void statusChanged(MessageDeliveryStatus status, Object userData) {
if(status == MessageDeliveryStatus.Fail){
Log.d(Constants.TAG,"发布esp32/pub失败!!");
}else if(status == MessageDeliveryStatus.Success){
Log.d(Constants.TAG,"发布esp32/pub成功!!");
Log.d(Constants.TAG,userData.toString());
}
}
},"success");
}catch (Exception e){
e.printStackTrace();
}
至此一个完整的手机端APP连接AWS IOT并且发布订阅示例结束。
四、踩坑
过程中遇到了不少问题,由于网上资料实在是匮乏,只能靠自己想办法,Android原生开发调试又特麻烦。
1>身份池和iot资源位置要统一,犯过创建资源成功但位置搞错了。
2>这种使用Cognito联合身份连接AWS IOT一定是IAM的角色策略和IOT策略结合在一起才能使用
3>权限细分,这一部分困扰了很长一段时间
像上述例子中如果IAM的Auth_xx角色赋予了AWSIoTFullAccess权限,IOT策略中又赋予了iot*的全部权限,虽然可以正常连接AWS IOT 且可以向任何topic发布消息,也可以订阅和接收任何topic的消息,这明显与实际使用场景不符。
关于实际使用场景中的权限细分可以阅读这篇文章,从文章中介绍可以看出通常做法是IAM的Auth_xx角色给个宽松的权限,iot策略中再做进一步缩小限制。
我所测试细分的权限
1)谁能连,app端不应该使用UUID.randomUUID().toString()这种随机值进行标识,而是使用了identityId,即使用户换个手机登录也能识别到是谁。可能涉及到保留消息等相关知识,没有细致研究。
这个ID在策略里会转化成一个变量,${cognito-identity.amazonaws.com:sub},AWS会把他自动转化成实际的ID
2)能订阅和发布哪些topic
注意订阅的权限通常要和接收权限一起设置,不然订阅成功了但却收不到消息
3)要正确配置各个权限的策略资源(就是arn开头的那一串)
我提供我做权限测试细分所配置的两个策略
IAM中的Auth_xx角色策略:
这里我给的相对比较宽松的策略,注意AttachPolicy一定要加,不然Android程序里attachPolicy会报没有权限错误。
AWS IOT策略:
这里就做进一步权限限制了,必须使用credentialsProvider.getIdentityId()才能连,使用UUID.randomUUID().toString()被拒绝。
只能订阅esp32/pub和esp32/pub/demo两个topic(实际生产环境中应参考iot中的示例使用通配符啥的),但只能接收esp32/pub/demo这个topic的消息。
发布消息的同样只允许向esp32/pub和esp32/pub/demo这两个topic发送
实际demo效果是向这两个topic都能发送成功消息,但只有esp32/pub/demo这个topic接收到了发布的消息。
需要注意的是:
先要订阅成功后才能接收发布消息
箭头所指的各个标识要写正确,该是哪个就是哪个,控制台拷贝别处的json过来也不会出错* 我这里的资源位置以及账号全部用*代替,可替换实际的值
4>其他
我当初在做限制只能以IdentityId进行连接AWS IOT时,有可能由于IAM策略 IOT策略频繁更新,明明配置正确了但还是连接不上,试了好久都不行。后台无意间将Cognito控制台下的那个身份ID给删除了,然后就可以正常连接了。这个方案暂时不确定其是否有效,猜测可能与AWS 的缓存机制有关。
订阅和发布报错,明明我的配置是按照官方的示例写的,但却没法订阅也没法发布,也没法精准定位究竟什么原因。折腾后来发现其实是自己的topic写错了,比如订阅的topic应该是esp32/pub/demo,但我写成了/esp32/pub/demo
总结
这里仅介绍了初步的Android APP使用AWS IOT的例子,代码就不上传了,各位自行测试。
另外这只是初步的研究,实际生产环节中还涉及到比如策略变更、权限时效性、分享等等等。
权限探索
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数嵌入式工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以+V:Vip1104z获取!!! (备注:嵌入式)
![img](https://i-blog.csdnimg.cn/blog_migrate/e8fc80f99798a7cde53c4ac835b0aee9.jpeg)
最后
资料整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!
片转存中…(img-6thFwdCT-1712228374705)]
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以+V:Vip1104z获取!!! (备注:嵌入式)
![img](https://i-blog.csdnimg.cn/blog_migrate/e8fc80f99798a7cde53c4ac835b0aee9.jpeg)
最后
资料整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!