Android端结合Cognito的Amazon S3预签名URL报错400优化咨询
问题根因
你遇到的预签名URL提前过期触发400错误,核心原因是Cognito返回的临时凭证有效期短于你设置的预签名URL有效期:
Cognito身份池默认给未认证用户发放的临时凭证有效期仅为1小时,就算你给预签名URL设置了12小时过期,用来生成签名的临时凭证先过期后,URL自然会提前失效,返回400错误。
改进推荐
- 对齐有效期配置
预签名URL的有效期不能超过生成它所用的凭证的有效期:- 可以直接把预签名URL的过期时间改为小于3600秒(1小时),比如设置
System.currentTimeMillis() + 3500000,留一定缓冲时间 - 如果确实需要更长有效期的URL,登录AWS控制台进入对应Cognito身份池,在「身份验证提供者」-「未验证」选项下,把凭证有效期调整到最长12小时,再对应设置预签名URL的有效期不超过该值
- 可以直接把预签名URL的过期时间改为小于3600秒(1小时),比如设置
- 调整预签名URL生成时机
不要在onCreate阶段一次性生成URL长期持有,改成每次需要请求S3资源前再当场生成预签名URL,避免APP退后台长时间挂起后复用过期URL - 新增错误重试逻辑
给Volley请求添加错误拦截逻辑,捕获到400错误后,先销毁旧的S3客户端实例,重新生成Cognito凭证、新的预签名URL后再重试请求 - 修复主线程阻塞问题
你当前在onCreate中调用thread.join()会阻塞主线程,极易触发ANR,改为异步回调的方式获取预签名URL后再发起网络请求
改进代码示例
调整后的RunnableAWSS3.java
public class RunnableAWSS3 implements Runnable { private final Context context; private final OnURLGeneratedListener listener; public interface OnURLGeneratedListener { void onSuccess(String presignedUrl); void onError(Exception e); } public RunnableAWSS3(@NonNull Context context, OnURLGeneratedListener listener) { this.context = context.getApplicationContext(); this.listener = listener; } @Override public void run() { try { CognitoCachingCredentialsProvider cognitoProvider = new CognitoCachingCredentialsProvider( context, "us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx", Regions.US_EAST_1 ); // 每次生成URL前清空旧的缓存凭证,强制拿最新有效凭证 cognitoProvider.clearCredentials(); AmazonS3 amazonS3 = new AmazonS3Client(cognitoProvider, Region.getRegion(Regions.US_EAST_1)); // 有效期设置为59分钟,小于默认1小时的凭证有效期 URL presignedURL = amazonS3.generatePresignedUrl( "xxxxxxxxxxxxxxxxxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxxxxxxxx", new Date(System.currentTimeMillis() + 3540000) ); listener.onSuccess(presignedURL.toString()); } catch (Exception e) { listener.onError(e); } } }
调整后的MainActivity.java调用逻辑
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 异步获取预签名URL后再发起请求,不阻塞主线程 new Thread(new RunnableAWSS3(this, new RunnableAWSS3.OnURLGeneratedListener() { @Override public void onSuccess(String presignedUrl) { JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, presignedUrl, null, response -> { // 处理返回结果 }, error -> { // 400错误时重新生成URL重试 if (error.networkResponse != null && error.networkResponse.statusCode == 400) { new Thread(new RunnableAWSS3(MainActivity.this, this)).start(); } }); // 把请求加入Volley队列 Volley.newRequestQueue(MainActivity.this).add(jsonObjectRequest); } @Override public void onError(Exception e) { // 处理初始化错误 } })).start(); }
内容的提问来源于stack exchange,提问作者Miha M
相关产品推荐
相关产品推荐

