การผสานการเข้าสู่ระบบ Facebook ในแอป AngularJS กับ Satellizer
เผยแพร่แล้ว: 2022-03-11ด้วยการมาถึงของเฟรมเวิร์กส่วนหน้าที่มีฟีเจอร์มากมาย เช่น AngularJS ตรรกะต่างๆ จะถูกนำไปใช้กับฟรอนต์เอนด์มากขึ้นเรื่อยๆ เช่น การจัดการ/การตรวจสอบข้อมูล การตรวจสอบสิทธิ์ และอื่นๆ Satellizer ซึ่งเป็นโมดูลการตรวจสอบสิทธิ์ที่ใช้โทเค็นที่ใช้งานง่ายสำหรับ AngularJS ช่วยลดความยุ่งยากในกระบวนการใช้กลไกการตรวจสอบสิทธิ์ใน AngularJS ไลบรารีมาพร้อมกับการสนับสนุนในตัวสำหรับ Google, Facebook, LinkedIn, Twitter, Instagram, GitHub, Bitbucket, Yahoo, Twitch และบัญชี Microsoft (Windows Live)
ในบทความนี้ เราจะสร้างเว็บแอปง่ายๆ ที่คล้ายกับที่นี่ ซึ่งช่วยให้คุณเข้าสู่ระบบและดูข้อมูลของผู้ใช้ปัจจุบันได้
การตรวจสอบและการอนุญาต
คำเหล่านี้เป็นคำที่น่ากลัว 2 คำที่คุณมักพบเมื่อแอปของคุณเริ่มรวมระบบผู้ใช้ ตามวิกิพีเดีย:
การ รับรองความถูกต้อง คือการยืนยันความจริงของแอตทริบิวต์ของข้อมูลชิ้นเดียว (ข้อมูลอ้างอิง) ที่อ้างว่าเป็นจริงโดยเอนทิตี
การอนุญาต เป็นหน้าที่ของการระบุสิทธิ์การเข้าถึงทรัพยากรที่เกี่ยวข้องกับความปลอดภัยของข้อมูลและความปลอดภัยของคอมพิวเตอร์โดยทั่วไป และเพื่อควบคุมการเข้าถึงโดยเฉพาะ
ในแง่คนธรรมดา เรามาดูตัวอย่างของเว็บไซต์บล็อกที่มีคนทำงานอยู่ บล็อกเกอร์เขียนบทความและผู้จัดการตรวจสอบเนื้อหา แต่ละคนสามารถตรวจสอบ (เข้าสู่ระบบ) เข้าสู่ระบบได้ แต่สิทธิ์ (การอนุญาต) ของพวกเขาแตกต่างกัน ดังนั้นบล็อกเกอร์จึงไม่สามารถตรวจสอบเนื้อหาในขณะที่ผู้จัดการสามารถทำได้
ทำไมต้องใช้ดาวเทียม
คุณสามารถสร้างระบบการตรวจสอบสิทธิ์ของคุณเองใน AngularJS ได้โดยทำตามบทช่วยสอน เช่น ระบบที่มีรายละเอียดมากนี้: JSON Web Token Tutorial: ตัวอย่างใน Laravel และ AngularJS ฉันแนะนำให้อ่านบทความนี้เนื่องจากอธิบาย JWT (JSON Web Token) ได้เป็นอย่างดี และแสดงวิธีง่ายๆ ในการปรับใช้การพิสูจน์ตัวตนใน AngularJS โดยใช้ที่จัดเก็บในเครื่องโดยตรงและตัวสกัดกั้น HTTP
ทำไมต้อง Satellizer? สาเหตุหลักคือ รองรับการเข้าสู่ระบบเครือข่ายสังคมออนไลน์จำนวนหนึ่ง เช่น Facebook, Twitter เป็นต้น ปัจจุบันนี้ โดยเฉพาะอย่างยิ่งสำหรับเว็บไซต์ที่ใช้บนมือถือ การพิมพ์ชื่อผู้ใช้และรหัสผ่านค่อนข้างยุ่งยาก และผู้ใช้คาดหวังว่าจะสามารถใช้เว็บไซต์ของคุณได้โดยมีอุปสรรคเล็กน้อย โดยใช้การเข้าสู่ระบบโซเชียล เนื่องจากการรวม SDK ของเครือข่ายโซเชียลแต่ละเครือข่ายและการปฏิบัติตามเอกสารประกอบนั้นค่อนข้างซ้ำซาก จะเป็นการดีที่จะสนับสนุนการเข้าสู่ระบบโซเชียลเหล่านี้โดยใช้ความพยายามเพียงเล็กน้อย
นอกจากนี้ Satellizer ยังเป็นโปรเจ็กต์ที่ ทำงานอยู่ บน Github แอ็คทีฟเป็นกุญแจสำคัญในที่นี้ เนื่องจาก SDK เหล่านี้มีการเปลี่ยนแปลงค่อนข้างบ่อย และคุณไม่ต้องการที่จะอ่านเอกสารของพวกเขาเป็นระยะๆ (ทุกคนที่ทำงานกับ Facebook SDK รู้ว่ามันน่ารำคาญแค่ไหน)
แอพ AngularJS พร้อม Facebook Login
นี่คือสิ่งที่เริ่มน่าสนใจ
เราจะสร้างเว็บแอปที่มีกลไกการเข้าสู่ระบบ/ลงทะเบียนเป็นประจำ (เช่น การใช้ชื่อผู้ใช้ รหัสผ่าน) และสนับสนุนการเข้าสู่ระบบโซเชียลด้วย เว็บแอปนี้เรียบง่ายมากเนื่องจากมีเพียง 3 หน้า:
- หน้าแรก ใครๆก็ดูได้
- หน้าเข้าสู่ระบบ: เพื่อป้อนชื่อผู้ใช้/รหัสผ่าน
- หน้าลับ: ที่เฉพาะผู้ใช้ที่เข้าสู่ระบบเท่านั้นที่สามารถเห็น
สำหรับแบ็กเอนด์ เราจะใช้ Python และ Flask Python และ Framework Flask ค่อนข้างจะสื่อความหมายได้ ดังนั้นฉันหวังว่าการพอร์ตโค้ดไปยังภาษา/เฟรมเวิร์กอื่นๆ จะไม่ยากมาก แน่นอน เราจะใช้ AngularJS สำหรับ front-end และสำหรับการเข้าสู่ระบบโซเชียล เราจะรวมเข้ากับ Facebook เท่านั้น เนื่องจากเป็นโซเชียลเน็ตเวิร์กที่ได้รับความนิยมสูงสุดในขณะนี้
เริ่มกันเลย!
ขั้นตอนที่ #1: Bootstrap Project
นี่คือวิธีที่เราจะจัดโครงสร้างโค้ดของเรา:
- app.py - static/ - index.html - app.js - bower.json - partials/ - login.tpl.html - home.tpl.html - secret.tpl.html
รหัสส่วนหลังทั้งหมดอยู่ใน app.py รหัสส่วนหน้าถูกวางในสแตติก / โฟลเดอร์ ตามค่าเริ่มต้น Flask จะให้บริการเนื้อหาของ static/ โฟลเดอร์โดยอัตโนมัติ มุมมองบางส่วนทั้งหมดเป็นแบบคงที่/บางส่วน/ และจัดการโดยโมดูล ui.router
ในการเริ่มต้นเขียนโค้ดแบ็คเอนด์ เราต้องใช้ Python 2.7.* และติดตั้งไลบรารีที่จำเป็นโดยใช้ pip คุณสามารถใช้ virtualenv เพื่อแยกสภาพแวดล้อม Python ได้ ด้านล่างนี้คือรายการของโมดูล Python ที่จำเป็นในการใส่ใน requirements.txt:
Flask==0.10.1 PyJWT==1.4.0 Flask-SQLAlchemy==1.0 requests==2.7.0
ในการติดตั้งการพึ่งพาเหล่านี้ทั้งหมด:
pip install -r requirements.txt
ใน app.py เรามีโค้ดเริ่มต้นสำหรับบูตสแตรป Flask (คำสั่งการนำเข้าจะถูกละเว้นเพื่อความกระชับ):
app = Flask(__name__) @app.route('/') def index(): return flask.redirect('/static/index.html') if __name__ == '__main__': app.run(debug=True)
ต่อไปเราจะ เริ่มต้น bower และติดตั้ง AngularJS และ ui.router:
bower init # here you will need to answer some question. when in doubt, just hit enter :) bower install angular angular-ui-router --save # install and save these dependencies into bower.json
เมื่อติดตั้งไลบรารีเหล่านี้แล้ว เราจำเป็นต้องรวม AngularJS และ ui-router ไว้ใน index.html และสร้างเส้นทางสำหรับ 3 หน้า ได้แก่ หน้าแรก การเข้าสู่ระบบ และความลับ
<body ng-app="DemoApp"> <a ui-sref="home">Home</a> <a ui-sref="login">Login</a> <a ui-sref="secret">Secret</a> <div ui-view></div> <script src="bower_components/angular/angular.min.js"></script> <script src="bower_components/angular-ui-router/release/angular-ui-router.min.js"></script> <script src="main.js"></script> </body>
ด้านล่างนี้คือรหัสที่เราต้องการใน main.js เพื่อกำหนดค่าการกำหนดเส้นทาง:
var app = angular.module('DemoApp', ['ui.router']); app.config(function ($stateProvider, $urlRouterProvider) { $stateProvider .state('home', { url: '/home', templateUrl: 'partials/home.tpl.html' }) .state('secret', { url: '/secret', templateUrl: 'partials/secret.tpl.html', }) .state('login', { url: '/login', templateUrl: 'partials/login.tpl.html' }); $urlRouterProvider.otherwise('/home'); });
ณ จุดนี้หากคุณเรียกใช้เซิร์ฟเวอร์ python app.py คุณควรมีอินเทอร์เฟซพื้นฐานที่ http://localhost:5000
ลิงก์หน้าแรก การเข้าสู่ระบบ และความลับควรใช้งานได้ ณ จุดนี้และแสดงเนื้อหาของเทมเพลตที่เกี่ยวข้อง
ยินดีด้วย คุณตั้งค่าโครงกระดูกเสร็จแล้ว! หากคุณพบข้อผิดพลาดใด ๆ โปรดตรวจสอบรหัสบน GitHub
ขั้นตอนที่ #2: เข้าสู่ระบบและลงทะเบียน
เมื่อสิ้นสุดขั้นตอนนี้ คุณจะมีเว็บแอปที่คุณสามารถลงทะเบียน/เข้าสู่ระบบโดยใช้อีเมลและรหัสผ่าน
ขั้นตอนแรกคือการกำหนดค่าแบ็กเอนด์ เราต้องการโมเดลผู้ใช้และวิธีสร้างโทเค็น JWT สำหรับผู้ใช้ที่กำหนด โมเดลผู้ใช้ที่แสดงด้านล่างนั้นเรียบง่ายมาก และไม่มีการตรวจสอบพื้นฐานใดๆ เลย เช่น หากฟิลด์ อีเมล มี “@” หรือหาก รหัสผ่าน ของฟิลด์มีอักขระอย่างน้อย 6 ตัว เป็นต้น
class User(db.Model): id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(100), nullable=False) password = db.Column(db.String(100)) def token(self): payload = { 'sub': self.id, 'iat': datetime.utcnow(), 'exp': datetime.utcnow() + timedelta(days=14) } token = jwt.encode(payload, app.config['TOKEN_SECRET']) return token.decode('unicode_escape')
เราใช้โมดูล jwt ใน python เพื่อสร้างส่วนของข้อมูลใน JWT ส่วน iat และ exp สอดคล้องกับการประทับเวลาที่โทเค็นถูกสร้างขึ้นและหมดอายุ ในรหัสนี้ โทเค็นจะหมดอายุใน 2 สัปดาห์
หลังจากสร้างโมเดล User แล้ว เราสามารถเพิ่มปลายทาง "เข้าสู่ระบบ" และ "ลงทะเบียน" รหัสสำหรับทั้งสองค่อนข้างคล้ายกัน ดังนั้นที่นี่ฉันจะแสดงเฉพาะส่วน "ลงทะเบียน" โปรดทราบว่าโดยค่าเริ่มต้น Satellizer จะเรียกปลายทาง /auth/login และ /auth/signup สำหรับ "เข้าสู่ระบบ" และ "ลงทะเบียน" ตามลำดับ
@app.route('/auth/signup', methods=['POST']) def signup(): data = request.json email = data["email"] password = data["password"] user = User(email=email, password=password) db.session.add(user) db.session.commit() return jsonify(token=user.token())
ตรวจสอบจุดปลายโดยใช้ curl ก่อน:
curl localhost:5000/auth/signup -H "Content-Type: application/json" -X POST -d '{"email":"[email protected]","password":"xyz"}'
ผลลัพธ์ควรมีลักษณะดังนี้:
{ "token": "very long string…." }
เมื่อส่วนหลังพร้อมแล้ว มาโจมตีส่วนหน้ากันเถอะ! อันดับแรก เราต้องติดตั้ง satellizer และเพิ่มเป็นการขึ้นต่อกันใน main.js:
bower install satellizer --save
เพิ่ม satellizer เป็นการพึ่งพา:
var app = angular.module('DemoApp', ['ui.router', 'satellizer']);
การเข้าสู่ระบบและลงทะเบียนใน satellizer นั้นค่อนข้างง่ายเมื่อเทียบกับการตั้งค่าทั้งหมดจนถึงตอนนี้:
$scope.signUp = function () { $auth .signup({email: $scope.email, password: $scope.password}) .then(function (response) { // set the token received from server $auth.setToken(response); // go to secret page $state.go('secret'); }) .catch(function (response) { console.log("error response", response); }) };
หากคุณมีปัญหาในการตั้งค่ารหัส คุณสามารถดูรหัสบน GitHub

ขั้นตอนที่ #3: แต่มุมมองลับไม่ใช่ความลับจริง ๆ เพราะใคร ๆ ก็สามารถเห็นได้!
ใช่ ถูกต้อง! จนถึงตอนนี้ ใครๆ ก็ไปที่หน้าลับได้โดยไม่ต้องเข้าสู่ระบบ
ได้เวลาเพิ่ม interceptor ใน AngularJS เพื่อให้แน่ใจว่าถ้ามีคนไปที่หน้าลับและถ้าผู้ใช้รายนี้ไม่ได้เข้าสู่ระบบ พวกเขาจะถูกเปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบ
ขั้นแรก เราควรเพิ่มแฟล็ก requiredLogin เพื่อแยกความแตกต่างของเพจลับออกจากเพจอื่น
.state('secret', { url: '/secret', templateUrl: 'partials/secret.tpl.html', controller: 'SecretCtrl', data: {requiredLogin: true} })
ส่วน "ข้อมูล" จะใช้ในเหตุการณ์ $stateChangeStart ซึ่งเริ่มทำงานทุกครั้งที่มีการเปลี่ยนแปลงเส้นทาง:
app.run(function ($rootScope, $state, $auth) { $rootScope.$on('$stateChangeStart', function (event, toState) { var requiredLogin = false; // check if this state need login if (toState.data && toState.data.requiredLogin) requiredLogin = true; // if yes and if this user is not logged in, redirect him to login page if (requiredLogin && !$auth.isAuthenticated()) { event.preventDefault(); $state.go('login'); } }); });
ตอนนี้ผู้ใช้ไม่สามารถไปที่หน้าลับได้โดยตรงโดยไม่ต้องเข้าสู่ระบบ ไชโย!
ตามปกติแล้ว รหัสของขั้นตอนนี้สามารถพบได้ที่นี่
ขั้นตอนที่ #4: ถึงเวลาที่จะได้รับบางสิ่งที่เป็นความลับจริงๆ!
ในขณะนี้ ไม่มีอะไรเป็นความลับจริงๆ ในหน้าความลับ มาใส่เรื่องส่วนตัวกันเถอะ
ขั้นตอนนี้เริ่มต้นด้วยการสร้างปลายทางในส่วนแบ็คเอนด์ซึ่งเข้าถึงได้เฉพาะผู้ใช้ที่ตรวจสอบสิทธิ์แล้วเท่านั้น เช่น การมีโทเค็นที่ถูกต้อง จุดสิ้นสุด /user ด้านล่างส่งคืน user_id และ อีเมล ของผู้ใช้ที่สอดคล้องกับโทเค็น
@app.route('/user') def user_info(): # the token is put in the Authorization header if not request.headers.get('Authorization'): return jsonify(error='Authorization header missing'), 401 # this header looks like this: “Authorization: Bearer {token}” token = request.headers.get('Authorization').split()[1] try: payload = jwt.decode(token, app.config['TOKEN_SECRET']) except DecodeError: return jsonify(error='Invalid token'), 401 except ExpiredSignature: return jsonify(error='Expired token'), 401 else: user_id = payload['sub'] user = User.query.filter_by(id=user_id).first() if user is None: return jsonify(error='Should not happen ...'), 500 return jsonify(id=user.id, email=user.email), 200 return jsonify(error="never reach here..."), 500
อีกครั้ง เราใช้โมดูล jwt เพื่อถอดรหัสโทเค็น JWT ที่รวมอยู่ในส่วนหัว 'การอนุญาต' และเพื่อจัดการกับกรณีที่โทเค็นหมดอายุหรือไม่ถูกต้อง
มาทดสอบจุดปลายนี้โดยใช้ curl อันดับแรก เราต้องได้รับโทเค็นที่ถูกต้อง:
curl localhost:5000/auth/signup -H "Content-Type: application/json" -X POST -d '{"email":"[email protected]","password":"xyz"}'
จากนั้นด้วยโทเค็นนี้:
curl localhost:5000/user -H "Authorization: Bearer {put the token here}"
ซึ่งให้ผลลัพธ์นี้:
{ "email": "[email protected]", "id": 1 }
ตอนนี้เราต้องรวมจุดสิ้นสุดนี้ไว้ใน Secret Controller มันค่อนข้างง่ายเพราะเราแค่ต้องเรียกปลายทางโดยใช้โมดูล $http ปกติ โทเค็นจะถูกแทรกลงในส่วนหัวโดยอัตโนมัติโดย Satellizer ดังนั้นเราจึงไม่จำเป็นต้องกังวลกับรายละเอียดทั้งหมดของการบันทึกโทเค็นแล้วใส่ลงในส่วนหัวที่ถูกต้อง
getUserInfo(); function getUserInfo() { $http.get('/user') .then(function (response) { $scope.user = response.data; }) .catch(function (response) { console.log("getUserInfo error", response); }) }
ในที่สุดเราก็มีบางอย่างที่เป็นส่วนตัวอย่างแท้จริงในหน้าความลับ!
รหัสของขั้นตอนนี้อยู่ใน GitHub
ขั้นตอนที่ #5: เข้าสู่ระบบ Facebook ด้วย Satellizer
สิ่งที่ดีเกี่ยวกับ Satellizer ตามที่กล่าวไว้ในตอนต้นคือทำให้การรวมการเข้าสู่ระบบโซเชียลทำได้ง่ายขึ้นมาก เมื่อสิ้นสุดขั้นตอนนี้ ผู้ใช้สามารถเข้าสู่ระบบโดยใช้บัญชี Facebook ของพวกเขา!
สิ่งแรกที่ต้องทำคือสร้างแอปพลิเคชันบนหน้านักพัฒนา Facebook เพื่อให้มี application_id และรหัสลับ โปรดติดตาม developer.facebook.com/docs/apps/register เพื่อสร้างบัญชีนักพัฒนา Facebook หากคุณยังไม่มีและสร้างแอปเว็บไซต์ หลังจากนั้น คุณจะมี ID แอปพลิเคชันและรหัสลับของแอปพลิเคชันดังในภาพหน้าจอด้านล่าง
เมื่อผู้ใช้เลือกเชื่อมต่อกับ Facebook แล้ว Satellizer จะส่งรหัสอนุญาตไปยังปลายทาง /auth/facebook ด้วยรหัสการให้สิทธิ์นี้ แบ็คเอนด์สามารถเรียกโทเค็นการเข้าถึงจากปลายทาง Facebook /oauth ที่อนุญาตให้เรียกไปยัง Facebook Graph API เพื่อรับข้อมูลผู้ใช้ เช่น ตำแหน่ง, user_friends, อีเมลผู้ใช้ ฯลฯ
นอกจากนี้เรายังต้องติดตามว่าบัญชีผู้ใช้ถูกสร้างขึ้นด้วย Facebook หรือผ่านการสมัครปกติ ในการดำเนินการดังกล่าว เราเพิ่ม facebook_id ให้กับโมเดลผู้ใช้ของเรา
facebook_id = db.Column(db.String(100))
ความลับของ Facebook ได้รับการกำหนดค่าผ่านตัวแปร env FACEBOOK_SECRET ที่เราเพิ่มลงใน app.config
app.config['FACEBOOK_SECRET'] = os.environ.get('FACEBOOK_SECRET')
ดังนั้นในการเปิด app.py คุณควรตั้งค่าตัวแปร env นี้:
FACEBOOK_SECRET={your secret} python app.py
นี่คือวิธีการจัดการกับการเข้าสู่ระบบ Facebook โดยค่าเริ่มต้น Satellizer จะเรียกปลายทาง /auth/facebook
@app.route('/auth/facebook', methods=['POST']) def auth_facebook(): access_token_url = 'https://graph.facebook.com/v2.3/oauth/access_token' graph_api_url = 'https://graph.facebook.com/v2.5/me?fields=id,email' params = { 'client_id': request.json['clientId'], 'redirect_uri': request.json['redirectUri'], 'client_secret': app.config['FACEBOOK_SECRET'], 'code': request.json['code'] } # Exchange authorization code for access token. r = requests.get(access_token_url, params=params) # use json.loads instead of urlparse.parse_qsl access_token = json.loads(r.text) # Step 2. Retrieve information about the current user. r = requests.get(graph_api_url, params=access_token) profile = json.loads(r.text) # Step 3. Create a new account or return an existing one. user = User.query.filter_by(facebook_id=profile['id']).first() if user: return jsonify(token=user.token()) u = User(facebook_id=profile['id'], email=profile['email']) db.session.add(u) db.session.commit() return jsonify(token=u.token())
ในการส่งคำขอไปยังเซิร์ฟเวอร์ Facebook เราใช้คำขอโมดูลที่สะดวก ตอนนี้ส่วนที่ยากของแบ็คเอนด์เสร็จแล้ว ที่ส่วนหน้า การเพิ่มการเข้าสู่ระบบ Facebook นั้นค่อนข้างง่าย อันดับแรก เราต้องบอก Satellizer facebook_id ของเราโดยเพิ่มโค้ดนี้ในฟังก์ชัน app.config :
$authProvider.facebook({ clientId: {your facebook app id}, // by default, the redirect URI is http://localhost:5000 redirectUri: 'http://localhost:5000/static/index.html' });
ในการเข้าสู่ระบบโดยใช้ Facebook เราสามารถโทร:
$auth.authenticate(“facebook”)
ตามปกติคุณสามารถตรวจสอบรหัสบน GitHub
ในเวลานี้ เว็บแอพสมบูรณ์ในแง่ของการทำงาน ผู้ใช้สามารถเข้าสู่ระบบ / ลงทะเบียนโดยใช้อีเมลและรหัสผ่านปกติหรือโดยใช้ Facebook เมื่อเข้าสู่ระบบแล้ว ผู้ใช้สามารถเห็นหน้าความลับของเขา
สร้างอินเทอร์เฟซที่สวยงาม
อินเทอร์เฟซไม่สวยมาก ณ จุดนี้ ให้เพิ่ม Bootstrap เล็กน้อยสำหรับเลย์เอาต์และโมดูลเครื่องปิ้งขนมปังเชิงมุมเพื่อจัดการกับข้อความแสดงข้อผิดพลาดอย่างดี เช่น เมื่อการเข้าสู่ระบบล้มเหลว
รหัสสำหรับส่วนที่สวยงามนี้สามารถพบได้ที่นี่
บทสรุป
บทความนี้แสดงการผสานการทำงานแบบทีละขั้นตอนของ Satellizer ในเว็บแอป AngularJS (แบบง่าย) ด้วย Satellizer เราสามารถเพิ่มการเข้าสู่ระบบโซเชียลอื่นๆ เช่น Twitter, Linkedin และอื่นๆ ได้อย่างง่ายดาย รหัสที่ส่วนหน้าค่อนข้างเหมือนกับในบทความ อย่างไรก็ตาม แบ็คเอนด์จะแตกต่างกันไปตาม SDK ของโซเชียลเน็ตเวิร์กมีปลายทางที่แตกต่างกันด้วยโปรโตคอลที่แตกต่างกัน คุณสามารถดูได้ที่ https://github.com/sahat/satellizer/blob/master/examples/server/python/app.py ซึ่งมีตัวอย่างสำหรับ Facebook, Github, Google, Linkedin, Twiter และ Bitbucket หากมีข้อสงสัย คุณควรดูเอกสารใน https://github.com/sahat/satellizer