背景说明
最近做一个APP客户端图片直传阿里云OSS的服务,需要在后台开一个阿里云的OSSToken获取的接口。
阿里云官方文档地址:快速搭建移动应用直传服务。
略过移动端说明,直接看服务端的。
不是移动端直传吗,为什么需要服务端呢?原因如下:
Android和iOS应用不能直接存储AccessKey,这样会存在数据泄露的风险。所以应用必须向用户的应用服务器申请一个Token。
这个Token是有时效性的,如果Token的过期时间是30分钟(由应用服务器指定),那么在这30分钟里,该Android/iOS应用可以使用此Token从OSS上传和下载数据, 30分钟后需要重新获取Token。
正确返回的参数 Expiration 为该Token失效的时间。Android SDK会自动判断Token是否失效,如果失效,则自动获取Token。
代码实现
这里有个Java代码示例,可以下载来参考,把功能集成到项目中,没必要为一个接口单独部署一个项目到服务器。
项目环境是 SpringBoot2.0,JDK8(虽然官方说的是适用java1.7,但是JDK8兼容可用。)
添加依赖
首先在pom中添加相关依赖
<!--阿里云对象存储--> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-sts</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.0.0</version> </dependency>
配置属性文件
可以从下载的 AppTokenServerDemo 项目中找到 config.json 文件,将其中的五个参数都配置到我们自己的属性文件 application.properties 中。
#阿里云对象存储 ali.oss.AccessKeyID=xxxxxx ali.oss.AccessKeySecret=xxxxxxxxxxxxxxx ali.oss.RoleArn=xxxxxxxxxxxxxx ali.oss.TokenExpireTime=900 ali.oss.PolicyFile=policy/all_policy.txt
编写接口类
import com.alibaba.fastjson.JSONObject; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.http.MethodType; import com.aliyuncs.http.ProtocolType; import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.IClientProfile; import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest; import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; /** * @date 2019/4/7 20:18 * @auther wangbo */ @Api(tags = {"OSS接口类"}) @RestController @RequestMapping("/api/oss") public class ALiOSSController { //只有 RAM用户(子账号)才能调用 AssumeRole 接口 //阿里云主账号的AccessKeys不能用于发起AssumeRole请求 //请首先在RAM控制台创建一个RAM用户,并为这个用户创建AccessKeys @Value("${ali.oss.AccessKeyID}") private String accessKeyId; @Value("${ali.oss.AccessKeySecret}") private String accessKeySecret; //RoleArn 需要在 RAM 控制台上获取 @Value("${ali.oss.RoleArn}") private String roleArn; //Token 过期时间,默认 900s @Value("${ali.oss.TokenExpireTime}") private Long tokenExpireTime; //阿里云对象存储例子的 token 权限文件 //all_policy.txt:指定了该token拥有对该账号下创建Bucket、删除Bucket、上传文件、下载文件、删除文件的权限 。 //bucket_read_policy.txt:指定了该token拥有该账号下对指定Bucket的读权限。 //bucket_read_write_policy.txt:指定了该token拥有该账号下对指定Bucket的读写权限。 @Value("${ali.oss.PolicyFile}") private String policyFile; //目前只有"cn-hangzhou"这个region可用, 不要使用填写其他region的值 private static final String REGION_CN_HANGZHOU = "cn-hangzhou"; //这个是STS版本值 private static final String STS_API_VERSION = "2015-04-01"; @ApiOperation(value = "获取APPToken", notes = "获取阿里云对象存储的token") @GetMapping("/getAppToken") public void getAppToken(HttpServletRequest request, HttpServletResponse response) throws IOException { //RoleSessionName 是临时Token的会话名称,自己指定用于标识你的用户,主要用于审计,或者用于区分Token颁发给谁 //但是注意RoleSessionName的长度和规则,不要有空格,只能有\'-\' \'_\' 字母和数字等字符 //具体规则请参考API文档中的格式要求 String roleSessionName = "alice-001"; //此处必须为 HTTPS ProtocolType protocolType = ProtocolType.HTTPS; //获取文件的字符串 String policy = ReadJson(policyFile); //创建json对象 JSONObject respMap = new JSONObject(); try { final AssumeRoleResponse stsResponse = assumeRole(accessKeyId, accessKeySecret, roleArn, roleSessionName, policy, protocolType, tokenExpireTime); //设置获取阿里云对象存储成功的值 respMap.put("StatusCode", "200"); respMap.put("AccessKeyId", stsResponse.getCredentials().getAccessKeyId()); respMap.put("AccessKeySecret", stsResponse.getCredentials().getAccessKeySecret()); respMap.put("SecurityToken", stsResponse.getCredentials().getSecurityToken()); respMap.put("Expiration", stsResponse.getCredentials().getExpiration()); } catch (ClientException e) { //设置获取阿里云对象存储失败的值 respMap.put("StatusCode", "500"); respMap.put("ErrorCode", e.getErrCode()); respMap.put("ErrorMessage", e.getErrMsg()); } //response写回json数据 response(request, response,respMap.toJSONString()); } protected AssumeRoleResponse assumeRole(String accessKeyId, String accessKeySecret, String roleArn, String roleSessionName, String policy, ProtocolType protocolType, long durationSeconds) throws ClientException{ try { //创建一个 Aliyun Acs Client, 用于发起 OpenAPI 请求 IClientProfile profile = DefaultProfile.getProfile(REGION_CN_HANGZHOU, accessKeyId, accessKeySecret); DefaultAcsClient client = new DefaultAcsClient(profile); //创建一个 AssumeRoleRequest 并设置请求参数 final AssumeRoleRequest request = new AssumeRoleRequest(); request.setVersion(STS_API_VERSION); request.setMethod(MethodType.POST); request.setProtocol(protocolType); request.setRoleArn(roleArn); request.setRoleSessionName(roleSessionName); request.setPolicy(policy); request.setDurationSeconds(durationSeconds); //发起请求,并得到response final AssumeRoleResponse response = client.getAcsResponse(request); return response; }catch (ClientException e){ throw e; } } /** * 读取配置文件 * @param path * @return */ public static String ReadJson(String path){ BufferedReader reader = null; //返回值,使用StringBuffer StringBuffer data = new StringBuffer(); try { //从给定位置获取文件 ClassPathResource resource = new ClassPathResource(path); InputStream inputStream = resource.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream)); //每次读取文件的缓存 String temp = null; while((temp = reader.readLine()) != null){ data.append(temp); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { //关闭文件流 if (reader != null){ try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return data.toString(); } /** * 服务器响应结果 * @param request * @param response * @param results * @throws IOException */ private void response(HttpServletRequest request, HttpServletResponse response, String results) throws IOException { String callbackFunName = request.getParameter("callback"); if (callbackFunName==null || callbackFunName.equalsIgnoreCase("")) response.getWriter().println(results); else response.getWriter().println(callbackFunName + "( "+results+" )"); response.setStatus(HttpServletResponse.SC_OK); response.flushBuffer(); } }
此外需要把相关的资源文件(可以在下载的demo项目中获取)引入到resource目录下:
踩坑记录
代码中的文件读取部分在我本地 idea 中运行服务时,可以正常执行。但是将服务打包成可执行jar的包,以jar服务运行服务时报错。
java.io.FileNotFoundException: class path resource [policay/all_policay.txt] cannot be resolved to absolute file path because it does not reside in the file system
原因:打包后Spring无法使用resource.getFile()访问JAR中的路径的文件,必须使用resource.getInputStream()。
我尝试了好几种:
(1)使用demo项目中的方式,本地线上都找不到文件
File file = new File("policay/all_policay.txt"); BufferedReader reader = new BufferedReader(new FileReader(file));
(2)使用ResouceUtils类,线上版本找不到文件
File file = ResourceUtils.getFile("classpath:policay/all_policay.txt"); BufferedReader reader = new BufferedReader(new FileReader(file));
(3)最后改为使用InputStream才解决问题。
ClassPathResource resource = new ClassPathResource("policay/all_policay.txt");
InputStream inputStream = resource.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
另外注意,该接口需要定义为GET接口,并且响应值不能自定义的,需要按照demo中的形式进行响应,因为该接口是给阿里云OSS调用的,前端会在相应位置配置该接口地址。
参考博客: