我对使用刷新令牌服务的方式感到困惑。在我的应用程序中有一个包含许多播放列表的部分。当用户点击播放列表时,它会运行以下代码:
func checkAuth() {
print("checking auth")
let auth = SPTAuth.defaultInstance()
//print(auth!.session.isValid())
if auth!.session == nil {
print("no auth")
if auth!.hasTokenRefreshService {
print("refresh token if session == nil")
self.renewTokenAndShowPlayer()
return
} else {
self.performSegue(withIdentifier: "LoginControllerSegue", sender: nil)
}
return
}
if auth!.session.isValid() && firstLoad {
// It's still valid, show the player.
print("valid auth")
self.showPlayer()
return
}
if auth!.hasTokenRefreshService {
print("refresh token")
self.renewTokenAndShowPlayer()
return
}
}
func renewTokenAndShowPlayer() {
SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session) { error, session in
SPTAuth.defaultInstance().session = session
if error != nil {
print("Refreshing token failed.")
print("*** Error renewing session: \(error)")
self.performSegue(withIdentifier: "LoginControllerSegue", sender: nil)
return
}
self.showPlayer()
}
}
所以,让我们说用户还没有登录,他们转到登录播放器,然后进行身份验证。
稍后,当他们关闭播放器并点击其他播放列表时,会再次将其带到登录屏幕。这是为什么?
我相信我的刷新令牌服务有效,因为无论何时在有人登录后调用它,我的服务器都会成为/swap 200
。此外,只有在登录Spotify后有人回到应用程序(LoginViewController
)时才会调用此操作,为什么会这样?
以下是我的登录页面的代码:
import UIKit
import WebKit
class LoginViewController: UIViewController, SPTStoreControllerDelegate, WebViewControllerDelegate {
@IBOutlet weak var statusLabel: UILabel!
var authViewController: UIViewController?
var firstLoad: Bool!
var Information: [String:String]?
override func viewDidLoad() {
super.viewDidLoad()
self.statusLabel.text = ""
self.firstLoad = true
let auth = SPTAuth.defaultInstance()
NotificationCenter.default.addObserver(self, selector: #selector(self.sessionUpdatedNotification), name: NSNotification.Name(rawValue: "sessionUpdated"), object: nil)
// Check if we have a token at all
if auth!.session == nil {
self.statusLabel.text = ""
return
}
// Check if it's still valid
if auth!.session.isValid() && self.firstLoad {
// It's still valid, show the player.
print("View did load, still valid, showing player")
self.showPlayer()
return
}
// Oh noes, the token has expired, if we have a token refresh service set up, we'll call tat one.
self.statusLabel.text = "Token expired."
print("Does auth have refresh service? \(auth!.hasTokenRefreshService)")
if auth!.hasTokenRefreshService {
print("trying to renew")
self.renewTokenAndShowPlayer()
return
}
// Else, just show login dialog
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override var prefersStatusBarHidden: Bool {
return true
}
func getAuthViewController(withURL url: URL) -> UIViewController {
let webView = WebViewController(url: url)
webView.delegate = self
return UINavigationController(rootViewController: webView)
}
func sessionUpdatedNotification(_ notification: Notification) {
self.statusLabel.text = ""
let auth = SPTAuth.defaultInstance()
self.presentedViewController?.dismiss(animated: true, completion: { _ in })
if auth!.session != nil && auth!.session.isValid() {
self.statusLabel.text = ""
print("Session updated, showing player")
self.showPlayer()
}
else {
self.statusLabel.text = "Login failed."
print("*** Failed to log in")
}
}
func showPlayer() {
self.firstLoad = false
self.statusLabel.text = "Logged in."
self.Information?["SpotifyUsername"] = SPTAuth.defaultInstance().session.canonicalUsername
OperationQueue.main.addOperation {
[weak self] in
self?.performSegue(withIdentifier: "ShowPlayer", sender: self)
}
//self.performSegue(withIdentifier: "ShowPlayer", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowPlayer" {
if let destination = segue.destination as? PlayController {
destination.Information = self.Information
}
}
}
internal func productViewControllerDidFinish(_ viewController: SPTStoreViewController) {
self.statusLabel.text = "App Store Dismissed."
viewController.dismiss(animated: true, completion: { _ in })
}
func openLoginPage() {
self.statusLabel.text = "Logging in..."
let auth = SPTAuth.defaultInstance()
if SPTAuth.supportsApplicationAuthentication() {
self.open(url: auth!.spotifyAppAuthenticationURL())
} else {
// storyboard?.instantiateViewController(withIdentifier: <#T##String#>)
//
self.authViewController = self.getAuthViewController(withURL: SPTAuth.defaultInstance().spotifyWebAuthenticationURL())
self.definesPresentationContext = true
self.present(self.authViewController!, animated: true, completion: { _ in })
}
}
func open(url: URL) {
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:],
completionHandler: {
(success) in
print("Open \(url): \(success)")
})
} else {
let success = UIApplication.shared.openURL(url)
print("Open \(url): \(success)")
}
}
func renewTokenAndShowPlayer() {
self.statusLabel.text = "Refreshing token..."
print("trying to renew")
SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session) { error, session in
SPTAuth.defaultInstance().session = session
if error != nil {
self.statusLabel.text = "Refreshing token failed."
print("*** Error renewing session: \(error)")
return
}
print("refreshed token")
self.presentedViewController?.dismiss(animated: true, completion: { _ in })
self.showPlayer()
}
}
func webViewControllerDidFinish(_ controller: WebViewController) {
// User tapped the close button. Treat as auth error
print("UI Web view did finish")
let auth = SPTAuth.defaultInstance()
// Uncomment to turn off native/SSO/flip-flop login flow
//auth.allowNativeLogin = NO;
// Check if we have a token at all
if auth!.session == nil {
self.statusLabel.text = ""
return
}
// Check if it's still valid
if auth!.session.isValid() && self.firstLoad {
// It's still valid, show the player.
print("Still valid, showing player")
self.showPlayer()
return
}
}
@IBAction func loginButtonWasPressed(_ sender: SPTConnectButton) {
self.openLoginPage()
}
@IBAction func showSpotifyAppStoreClicked(_ sender: UIButton) {
self.statusLabel.text = "Presenting App Store..."
let storeVC = SPTStoreViewController(campaignToken: "your_campaign_token", store: self)
self.present(storeVC!, animated: true, completion: { _ in })
}
@IBAction func clearCookiesClicked(_ sender: UIButton) {
let storage = HTTPCookieStorage.shared
for cookie: HTTPCookie in storage.cookies! {
if (cookie.domain as NSString).range(of: "spotify.").length > 0 || (cookie.domain as NSString).range(of: "facebook.").length > 0 {
storage.deleteCookie(cookie)
}
}
UserDefaults.standard.synchronize()
self.statusLabel.text! = "Cookies cleared."
}
@IBAction func dismissViewController () {
self.dismiss(animated: true, completion: {})
}
}
这是我的node.js代码:
var spotifyEndpoint = 'https://accounts.spotify.com/api/token';
/**
* Swap endpoint
*
* Uses an authentication code on req.body to request access and
* refresh tokens. Refresh token is encrypted for safe storage.
*/
app.post('/swap', function (req, res, next) {
var formData = {
grant_type : 'authorization_code',
redirect_uri : clientCallback,
code : req.body.code
},
options = {
uri : url.parse(spotifyEndpoint),
headers : {
'Authorization' : authorizationHeader
},
form : formData,
method : 'POST',
json : true
};
console.log("Options" + options);
request(options, function (error, response, body) {
if (response.statusCode === 200) {
body.refresh_token = encrpytion.encrypt(body.refresh_token);
} else {
console.log("error swapping: " + error);
}
res.status(response.statusCode);
res.json(body);
});
});
app.post('/refresh', function (req, res, next) {
if (!req.body.refresh_token) {
res.status(400).json({ error : 'Refresh token is missing from body' });
return;
}
var refreshToken = encrpytion.decrypt(req.body.refresh_token),
formData = {
grant_type : 'refresh_token',
refresh_token : refreshToken
},
options = {
uri : url.parse(spotifyEndpoint),
headers : {
'Authorization' : authorizationHeader
},
form : formData,
method : 'POST',
json : true
};
request(options, function (error, response, body) {
if (response.statusCode === 200 && !!body.refresh_token) {
body.refresh_token = encrpytion.encrypt(body.refresh_token);
}
res.status(response.statusCode);
res.json(body);
});
});
{I}仅在我登录一次后被跳过,显示播放器,退出应用,然后再次启动应用并点击一首歌。这是为什么?
(注意,这是我从刷新令牌获得的错误:LoginViewController
)
我从[this]项目获得了刷新代码。 Heroku是否必要?