使用 Nginx 与 Spotify 授权
Authorize with Spotify using Nginx
我有一个docker应用程序运行宁三个服务:
- client --> react frontend
- web --- > flask backend
- nginx ->- a reverse proxy for both
这是(简化的)项目结构:
docker-compose-dev.yml
services/
client/
src/
app.jsx
components/
spotify-auth.js
Spotify.jsx
nginx/
dev.conf
web/
这是我在构建时定义暴露端口的地方:
docker-compose-dev.yml
web:
build:
context: ./services/web
dockerfile: Dockerfile-dev
volumes:
- './services/web:/usr/src/app'
ports:
- 5001:5000 <----------------
environment:
- FLASK_ENV=development
- APP_SETTINGS=project.config.DevelopmentConfig
depends_on:
- web-db
nginx:
build:
context: ./services/nginx
dockerfile: Dockerfile-dev
restart: always
ports:
- 80:80 <----------------
- 8888:8888 <----------------
depends_on:
- web
- client
client:
build:
context: ./services/client
dockerfile: Dockerfile-dev
volumes:
- './services/client:/usr/src/app'
- '/usr/src/app/node_modules'
ports:
- 3007:3000 <----------------
environment:
- NODE_ENV=development
- REACT_APP_WEB_SERVICE_URL=${REACT_APP_WEB_SERVICE_URL}
depends_on:
- web
REDIRECT
Client
服务需要使用 Spotify
进行身份验证,这需要 Redirect URI
,已列入白名单 https://developer.spotify.com。对于我来说,我有几个选择:
这是我的 nginx 文件,我尝试在其中组织正确的端口:
dev.conf
server {
listen 80;
listen 8888;
location / { // frontend at localhost:3000
proxy_pass http://client:3000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location /users { // backend at localhost:5000
proxy_pass http://web:5000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location /auth { # this authentication is for the app, not spotify
proxy_pass http://web:5000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
最后,我的 js
和 jsx
文件用于:
- 使用 Spotify 进行身份验证 -----> Implicit Grant
- 将我的应用重定向回
localhost
,或 '/'
spotify-auth.js
export const stateKey = 'spotify_auth_state';
export const client_id = 'my_client_id'; // Your client id
export const redirect_uri = 'http://localhost:3000'; // my redirect uri
//export const redirect_uri = 'http://localhost:8888'; // my second try for uri
export const scope ='user-read-private user-read-email user-read-playback-state playlist-modify-public playlist-modify-private';
Spotify.jsx
class SpotifyAuth extends Component {
constructor (props) {
super(props);
this.state = {
isAuthenticatedWithSpotify: false
};
this.state.handleRedirect = this.handleRedirect.bind(this);
this.loginSpotifyUser = this.loginSpotifyUser.bind(this);
};
function generateRandomString(length) {
let text = '';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
getHashParams() {
const hashParams = {};
const r = /([^&;=]+)=?([^&;]*)/g;
const q = window.location.hash.substring(1);
let e = r.exec(q);
while (e) {
hashParams[e[1]] = decodeURIComponent(e[2]);
e = r.exec(q);
}
return hashParams;
}
componentDidMount() {
const params = this.getHashParams();
const access_token = params.access_token;
const state = params.state;
const storedState = localStorage.getItem(stateKey);
//localStorage.setItem('spotifyAuthToken', access_token);
//localStorage.getItem('spotifyAuthToken');
if (access_token && (state == null || state !== storedState)) {
alert('There was an error during the authentication');
} else {
localStorage.removeItem(stateKey);
}
// DO STUFF WITH ACCESS TOKEN HERE -- send ajax to backend routes
};
handleRedirect() {
const state = generateRandomString(16);
localStorage.setItem(stateKey, state);
let url = 'https://accounts.spotify.com/authorize';
url += '?response_type=token';
url += '&client_id=' + encodeURIComponent(client_id);
url += '&scope=' + encodeURIComponent(scope);
url += '&redirect_uri=' + encodeURIComponent(redirect_uri);
url += '&state=' + encodeURIComponent(state);
window.location = url;
// post data to backend
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/auth/spotify}`;
axios.post(url, data)
.then((res) => {
this.loginSpotifyUser(res.data.auth_token);
})
.catch((err) => { console.log(err); });
};
loginSpotifyUser(token) {
window.localStorage.setItem('spotifyAuthToken', token);
this.setState({ isAuthenticatedWithSpotify: true });
this.props.createMessage('Welcome to Spotify', 'success');
};
render() {
return (
<div className="button_container">
<button className="sp_button" onClick={this.handleRedirect}>
<strong>CONNECT YOUR SPOTIFY ACCOUNT</strong>
</button>
</div>
)
}
}
export default SpotifyAuth;
并渲染,像这样:
App.jsx
render() {
return (
<div>
<Switch>
<Route exact path='/' render={() => (
<SpotifyAuth/>
)}
/>
</Switch>
</div>
ERROR:
所有这些设置完成后 运行,我得到:
INVALID_CLIENT: Invalid redirect URI
LOGS:
在构建之前,我导出这个 env
变量:
$ export REACT_APP_WEB_SERVICE_URL=http://localhost
构建服务后,我得到 logs
:
client_1 | You can now view client in the browser.
client_1 |
client_1 | Local: http://localhost:3000/
web_1 | * Environment: development
web_1 | * Debug mode: on
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET /static/js/0.chunk.js HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET /static/js/bundle.js HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET /static/js/main.chunk.js HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
注意:
如果我尝试在 Spotify.jsx
处使用 localhost:8888
,应用程序会设法使用 Spotify
进行身份验证,但随后所有位置都以 localhost:8888/auth/login
开头,依此类推,这不需要。
问题:
为什么我的 client
localhost:3000
不能像 redirect uri
一样工作?我错过了什么?
在像这样的 docker
项目中,这是使用 Spotify
进行身份验证的最可靠方法吗?
您的问题:
您的 docker-compose.yml
的 client
容器配置了端口映射 3007:3000
。注意Docker-compose端口映射是host:container
(Compose file reference),这意味着你主机的3007端口映射到容器的3000端口。
这样,您尝试连接到主机中不可用的容器端口,尽管 nginx
对此很满意,因为它与您的客户端位于同一网络上,所以它可以到达并重定向请求。
如果是这样的话:
http://localhost:3000
不行,因为在host端关闭了
http://localhost:3007
打开您的客户端,因为它被重定向到您的客户端容器,但您将无法在那里使用 Spotify 身份验证,除非您将此 URL 列入白名单并更改您的 redirect_uri
.
http://localhost:8888
打开您的客户端,因为您 nginx
设置为反向代理,并且它可以访问客户端端口 3000,因为它在同一网络上。
您的解决方案:
您的解决方案是更改 docker-compose
,以便客户端映射到端口 3000:3000
。然后 Spotify 身份验证应该没问题,因为端口已打开并且 URLs 已正确配置。
额外:
关于您对设计的意见请求,nginx
感觉您的设计有点未充分利用。如果您设置反向代理,那么您要重定向到的服务将隐藏在无法从外部访问的安全网络中。例如,您可以通过这种方式在 nginx
上配置 SSL,而在其余服务上忘记 HTTPs。但是,如果这些服务可以从其他端口访问,那么你做这样的配置是没有用的。
在生产设置中,您需要关闭 docker-compose 中的 client
和 web
端口(字面意思是删除端口映射。Nginx 不会有问题访问你的容器,因为它在同一个网络上,不像你的主机)并且只让 nginx
暴露在现实世界中。
您可能还想设置一个 rewrite
规则,客户端和服务器分别挂在 http://localhost/client
、http://localhost/server
地址,但 nginx 重写请求并将其代理到适当的地址容器,因此容器实际上看到了到达 http://localhost:3000/
的请求。您可以在 Stack Exchange - Nginx reverse proxy + URL rewrite.
查看有关如何配置所有这些的示例
我有一个docker应用程序运行宁三个服务:
- client --> react frontend
- web --- > flask backend
- nginx ->- a reverse proxy for both
这是(简化的)项目结构:
docker-compose-dev.yml
services/
client/
src/
app.jsx
components/
spotify-auth.js
Spotify.jsx
nginx/
dev.conf
web/
这是我在构建时定义暴露端口的地方:
docker-compose-dev.yml
web:
build:
context: ./services/web
dockerfile: Dockerfile-dev
volumes:
- './services/web:/usr/src/app'
ports:
- 5001:5000 <----------------
environment:
- FLASK_ENV=development
- APP_SETTINGS=project.config.DevelopmentConfig
depends_on:
- web-db
nginx:
build:
context: ./services/nginx
dockerfile: Dockerfile-dev
restart: always
ports:
- 80:80 <----------------
- 8888:8888 <----------------
depends_on:
- web
- client
client:
build:
context: ./services/client
dockerfile: Dockerfile-dev
volumes:
- './services/client:/usr/src/app'
- '/usr/src/app/node_modules'
ports:
- 3007:3000 <----------------
environment:
- NODE_ENV=development
- REACT_APP_WEB_SERVICE_URL=${REACT_APP_WEB_SERVICE_URL}
depends_on:
- web
REDIRECT
Client
服务需要使用 Spotify
进行身份验证,这需要 Redirect URI
,已列入白名单 https://developer.spotify.com。对于我来说,我有几个选择:
这是我的 nginx 文件,我尝试在其中组织正确的端口:
dev.conf
server {
listen 80;
listen 8888;
location / { // frontend at localhost:3000
proxy_pass http://client:3000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location /users { // backend at localhost:5000
proxy_pass http://web:5000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location /auth { # this authentication is for the app, not spotify
proxy_pass http://web:5000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
最后,我的 js
和 jsx
文件用于:
- 使用 Spotify 进行身份验证 -----> Implicit Grant
- 将我的应用重定向回
localhost
,或'/'
spotify-auth.js
export const stateKey = 'spotify_auth_state';
export const client_id = 'my_client_id'; // Your client id
export const redirect_uri = 'http://localhost:3000'; // my redirect uri
//export const redirect_uri = 'http://localhost:8888'; // my second try for uri
export const scope ='user-read-private user-read-email user-read-playback-state playlist-modify-public playlist-modify-private';
Spotify.jsx
class SpotifyAuth extends Component {
constructor (props) {
super(props);
this.state = {
isAuthenticatedWithSpotify: false
};
this.state.handleRedirect = this.handleRedirect.bind(this);
this.loginSpotifyUser = this.loginSpotifyUser.bind(this);
};
function generateRandomString(length) {
let text = '';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
getHashParams() {
const hashParams = {};
const r = /([^&;=]+)=?([^&;]*)/g;
const q = window.location.hash.substring(1);
let e = r.exec(q);
while (e) {
hashParams[e[1]] = decodeURIComponent(e[2]);
e = r.exec(q);
}
return hashParams;
}
componentDidMount() {
const params = this.getHashParams();
const access_token = params.access_token;
const state = params.state;
const storedState = localStorage.getItem(stateKey);
//localStorage.setItem('spotifyAuthToken', access_token);
//localStorage.getItem('spotifyAuthToken');
if (access_token && (state == null || state !== storedState)) {
alert('There was an error during the authentication');
} else {
localStorage.removeItem(stateKey);
}
// DO STUFF WITH ACCESS TOKEN HERE -- send ajax to backend routes
};
handleRedirect() {
const state = generateRandomString(16);
localStorage.setItem(stateKey, state);
let url = 'https://accounts.spotify.com/authorize';
url += '?response_type=token';
url += '&client_id=' + encodeURIComponent(client_id);
url += '&scope=' + encodeURIComponent(scope);
url += '&redirect_uri=' + encodeURIComponent(redirect_uri);
url += '&state=' + encodeURIComponent(state);
window.location = url;
// post data to backend
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/auth/spotify}`;
axios.post(url, data)
.then((res) => {
this.loginSpotifyUser(res.data.auth_token);
})
.catch((err) => { console.log(err); });
};
loginSpotifyUser(token) {
window.localStorage.setItem('spotifyAuthToken', token);
this.setState({ isAuthenticatedWithSpotify: true });
this.props.createMessage('Welcome to Spotify', 'success');
};
render() {
return (
<div className="button_container">
<button className="sp_button" onClick={this.handleRedirect}>
<strong>CONNECT YOUR SPOTIFY ACCOUNT</strong>
</button>
</div>
)
}
}
export default SpotifyAuth;
并渲染,像这样:
App.jsx
render() {
return (
<div>
<Switch>
<Route exact path='/' render={() => (
<SpotifyAuth/>
)}
/>
</Switch>
</div>
ERROR:
所有这些设置完成后 运行,我得到:
INVALID_CLIENT: Invalid redirect URI
LOGS:
在构建之前,我导出这个 env
变量:
$ export REACT_APP_WEB_SERVICE_URL=http://localhost
构建服务后,我得到 logs
:
client_1 | You can now view client in the browser.
client_1 |
client_1 | Local: http://localhost:3000/
web_1 | * Environment: development
web_1 | * Debug mode: on
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET /static/js/0.chunk.js HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET /static/js/bundle.js HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET /static/js/main.chunk.js HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
注意:
如果我尝试在 Spotify.jsx
处使用 localhost:8888
,应用程序会设法使用 Spotify
进行身份验证,但随后所有位置都以 localhost:8888/auth/login
开头,依此类推,这不需要。
问题:
为什么我的 client
localhost:3000
不能像 redirect uri
一样工作?我错过了什么?
在像这样的 docker
项目中,这是使用 Spotify
进行身份验证的最可靠方法吗?
您的问题:
您的 docker-compose.yml
的 client
容器配置了端口映射 3007:3000
。注意Docker-compose端口映射是host:container
(Compose file reference),这意味着你主机的3007端口映射到容器的3000端口。
这样,您尝试连接到主机中不可用的容器端口,尽管 nginx
对此很满意,因为它与您的客户端位于同一网络上,所以它可以到达并重定向请求。
如果是这样的话:
http://localhost:3000
不行,因为在host端关闭了http://localhost:3007
打开您的客户端,因为它被重定向到您的客户端容器,但您将无法在那里使用 Spotify 身份验证,除非您将此 URL 列入白名单并更改您的redirect_uri
.http://localhost:8888
打开您的客户端,因为您nginx
设置为反向代理,并且它可以访问客户端端口 3000,因为它在同一网络上。
您的解决方案:
您的解决方案是更改 docker-compose
,以便客户端映射到端口 3000:3000
。然后 Spotify 身份验证应该没问题,因为端口已打开并且 URLs 已正确配置。
额外:
关于您对设计的意见请求,nginx
感觉您的设计有点未充分利用。如果您设置反向代理,那么您要重定向到的服务将隐藏在无法从外部访问的安全网络中。例如,您可以通过这种方式在 nginx
上配置 SSL,而在其余服务上忘记 HTTPs。但是,如果这些服务可以从其他端口访问,那么你做这样的配置是没有用的。
在生产设置中,您需要关闭 docker-compose 中的 client
和 web
端口(字面意思是删除端口映射。Nginx 不会有问题访问你的容器,因为它在同一个网络上,不像你的主机)并且只让 nginx
暴露在现实世界中。
您可能还想设置一个 rewrite
规则,客户端和服务器分别挂在 http://localhost/client
、http://localhost/server
地址,但 nginx 重写请求并将其代理到适当的地址容器,因此容器实际上看到了到达 http://localhost:3000/
的请求。您可以在 Stack Exchange - Nginx reverse proxy + URL rewrite.