この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、田中孝明です。
このエントリは Serverless Advent Calendar 2017 4日目の記事です。
いよいよ2017年も終わりが近づき、忘年会、クリスマス、年越しとイベントが目白押しではないでしょうか?
思い出のシェアとなるとInstagramやfacebook、Google Photoなど、様々な写真共有サービスが候補にあがるでしょう。
だた、中には限られた人たちにだけ公開したい、特別な思い出もあるかもしれません。
そんなあなたにとって、 Amazon Cognito と Amazon S3 は、希望を叶えてくれる力強い存在となるでしょう。
やりたいこと
- Amazon Cognito でユーザー認証
- 認証したユーザーでiOS端末から写真を送信
- 写真を Amazon S3 に保存
Amazon Cognitoを使った認証
Google認証
ユーザーの認証にGoogleアカウントを利用することにしました。
Google をセットアップするという項目を参考にGoogleアカウントで認証ができる状態にします。
Google 開発者コンソールに移動し、任意のGoogleアカウントでログインし、新しいプロジェクトを作成します。
「Social」 を検索し、「Google+ API」 を有効にします。
iOS 向け OAuth 2.0 Client ID を作成します。
[Credentials] > [Add Credentials] で、サービスアカウントを作成します。コンソールで、新しいパブリック/プライベートキーが作成されたことが警告されます。
Client ID の作成まで終了したら次はAmazon Cognitoにidentity poolの作成しましょう
identity poolの作成
AWSのコンソールにログインし、「Cognito」を選択します。
「Manage Federated Identities」をクリックします。
「Identity pool name」 に任意のアプリ名を入力します。
「Authentication providers」で「Google+」を選択し、Google 開発者コンソールから入手した「Client ID」を入力します。
「create pool」クリック時にAuthとUnAuthのIAMロールの作成も行なってくれます。
用意しているものがなければここで作成し、「Allow」をクリックしましょう。
S3 Bucketへの権限作成
IAM設定
AWS Management ConsoleからIAMを選択し、「Roles」をクリックします。 ここでCognitoの「create pool」時に作成されたAuthのRoleを選択します。
ポリシーにS3のPutの権限を付与しておきましょう。
こうすることで、Cognitoの認証がとったユーザーのみ、S3のPut権限を与えることができます。
iOSの実装
Googleサインインの設定
Google Sign-In for iOSに従って設定を行います。
GoogleServices-Info.plist
を作成するところから始めましょう。
任意のAppNameとiOSのBundle IDを入力し、「Choose and configure service」を選択します。
「ENABLE GOOGLE SIGN-IN」でサインインを有効にします。
GoogleServices-Info.plist
をダウンロードします。
サインイン処理の作成
サインイン画面の作成に従って作業を行います。
サインインを行うために CocoaPods
で Google/SignIn
SDKを組み込みます。
pod 'Google/SignIn'
GoogleServices-Info.plist
をXcodeに組み込みます。
Xcodeの 「Target」 > 「Info」 > 「URL Types」 に GoogleServices-Info.plist
の REVERSED_CLIENT_ID
の値を入力します。
AppDelegateの protocol
に GIDSignInDelegate
を追加します。
import GoogleSignIn
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {
didFinishLaunchingWithOptions
にsign-inの初期化処理を記載します。
YOUR_CLIENT_ID
には発行されたClient IDを入力します。
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// sign-inの初期化処理
GIDSignIn.sharedInstance().clientID = "YOUR_CLIENT_ID"
GIDSignIn.sharedInstance().delegate = self
return true
}
URLスキーマでアプリに遷移した際の処理をopenURLで行うようにします。
public func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance().handle(url as URL!,
sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as? String,
annotation: options[UIApplicationOpenURLOptionsKey.annotation])
}
AppDelegateに signIn
を実装します。
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if (error == nil) {
// Perform any operations on signed in user here.
let userId = user.userID // For client-side use only!
let idToken = user.authentication.idToken // Safe to send to the server
let fullName = user.profile.name
let givenName = user.profile.givenName
let familyName = user.profile.familyName
let email = user.profile.email
// ...
} else {
print("\(error.localizedDescription)")
}
}
サインイン画面の作成
ログイン画面に相当するUIViewControllerに GIDSignInUIDelegate
を適用します。
import GoogleSignIn
class LoginViewController: UIViewController, GIDSignInUIDelegate {
...
}
viewDidLoad
で初期化処理とサインインボタンの配置を行います。
GIDSignInButton
でボタンを配置するとテーマがGoogleのログインボタンになります。
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance().uiDelegate = self
...
// Signin Buttonの配置
let signInButton = GIDSignInButton()
signInButton.center = view.center
view.addSubview(_signInButton)
}
GIDSignInUIDelegate
のメソッドを実装します。
// Stop the UIActivityIndicatorView animation that was started when the user
// pressed the Sign In button
func signInWillDispatch(signIn: GIDSignIn!, error: NSError!) {
// stop indicator
}
// Present a view that prompts the user to sign in with Google
func signIn(signIn: GIDSignIn!,
presentViewController viewController: UIViewController!) {
// signin
self.present(viewController, animated: true, completion: nil)
}
// Dismiss the "Sign in with Google" view
func signIn(signIn: GIDSignIn!,
dismissViewController viewController: UIViewController!) {
self.dismiss(animated: true, completion: nil)
}
ここまでの作業で、ログイン画面を作ることができました。
Googleログイン後の認証情報の登録
「identity poolの作成」の項目で作成したidentity poolから「Identity pool ID」を確認します。
iOSで必要なAWS SDKを取得します。
pod 'AWSS3'
pod 'AWSCognito'
pod 'AWSCognitoIdentityProvider'
AWSIdentityProviderManagerのサブクラスを作成し、 logins
処理を仲介させるようにします。
// AWSIdentityProviderManagerのサブクラス
class GoogleProvider: NSObject, AWSIdentityProviderManager {
var tokens : [NSString : NSString]?
init(tokens: [NSString : NSString]) {
self.tokens = tokens
}
public func logins() -> AWSTask<NSDictionary> {
let token: NSDictionary = NSDictionary(dictionary: tokens!)
return AWSTask(result: token)
}
}
先ほどログイン画面を作る際に編集した AppDelegate
に認証成功後の処理を追加します。
YOUR_IDENTITY_POOL_ID
には先ほど確認した「Identity pool ID」を入力します。
import AWSCognito
import AWSCognitoIdentityProvider
import AWSCore
import GoogleSignIn
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if (error == nil) {
// authentication token取得
let idToken = user.authentication.idToken
// Initialize the Amazon Cognito credentials provider
let provider = GoogleProvider(tokens: [AWSIdentityProviderGoogle as NSString: idToken as! NSString])
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .APNortheast1,
identityPoolId:"ap-northeast-1:32e6c955-1859-4222-aa09-2f15a7ddbedc",
identityProviderManager: provider)
let configuration = AWSServiceConfiguration(region: .APNortheast1,
credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
} else {
print("\(error.localizedDescription)")
}
}
...
AWSCognitoCredentialsProvider
の getIdentityId
をコールします。
credentialsProvider.getIdentityId().continueOnSuccessWith(block: { [weak self] task -> Any? in
if credentialsProvider.identityId != nil {
// login!
}
return nil
})
AWS Management ConsoleからAWS CognitoのIdentity browserを確認すると、認証済みの端末が登録されていることが確認できます。
写真の送信
カメラの画像取得処理については割愛します。
画像を保存したURLから AWSS3 を使い、指定したS3のBucketへPutする処理を作成します。
func upload(imageURL: URL, createAt: Date) -> AWSTask<AnyObject> {
let request = AWSS3TransferManagerUploadRequest()
request?.bucket = "report-camera"
request?.key = "images/original/\(createAt.timeIntervalSince1970)-\(imageURL.lastPathComponent)"
request?.body = imageURL
return AWSS3TransferManager.default().upload(request!)
}
これをカメラの画像取得時に呼べば、S3に写真がPutされるようになります。
let data = UIImagePNGRepresentation(image)
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
let filename = documentsDirectory.appendingPathComponent("copy.png")
try? data?.write(to: filename)
upload(imageURL: filename, createAt: Date()).continueWith(block: { task -> Any? in
print(task)
})
まとめ
「Firebase SDK」を使った方がいいなど、色々ツッコミどころはあると思いますが、 Amazon CognitoはTwitter、facebookなどの認証にも対応しているため、S3へのPut以外にも応用はききます。
面倒な認証の実装を代理してもらうことで、アプリの実装により集中できるのではないでしょうか。