在 Docker 中使用 React 和 Nginx 授权 Spotify

Posted

技术标签:

【中文标题】在 Docker 中使用 React 和 Nginx 授权 Spotify【英文标题】:Authorize Spotify using React and Nginx in Docker 【发布时间】:2019-08-05 13:25:48 【问题描述】:

我正在构建具有以下结构的 dockerized REST API 应用程序:

../
  web/
  nginx/
       dev.conf   
       Dockerfile-dev  
  client/
        build/
        conf/
        Dockerfile-dev
        node_modules/
        package_json
        public/
        src/
           App.jsx   
           components/ 
                     SpotifyRedirect.jsx                  
 spotify-client/ 
                Dockerfile-dev
                node_modules
                package-lock.json
                package.json
                authorization_code/
                                  app.js 

注意:在这个项目中,用户需要经过两个授权/认证过程:

    一个与我的应用程序,它生成一个原生的token(这个已经处理 已经)

    另一个Spotify(需要redirect URI,并为其API访问提供自己的token

    2a) 所以,在localhost,在localhost/auth/registerlocalhost/auth/login 提交之后,我会得到Spotify redirect URI (http://localhost:8888),带我到这个页面:

2b) 然后,点击登录按钮,系统会要求我将我的应用与 Spotify 连接,如下所示:

最后一个OK 在那里我将获得许可并交给我一个token,我可以在我的React 客户端上保存甚至刷新它。


本项目的构建块摘自本教程:

using-spotifys-awesome-api-with-react

但是,我已经配置了一个Reactclient,除了这个授权过程之外,它还有其他用途。

以下是尝试集成这两个服务的相关代码:更通用的clientspotify-client


相关代码:

所以我的第一个尝试是为spotify-client 创建一个特定的服务,在client 服务下面,将其暴露给端口8888,如下所示:

docker-compose-dev.yml

  nginx:
    build:
      context: ./services/nginx
      dockerfile: Dockerfile-dev
    restart: always
    ports:
      - 80:80
    depends_on:
      - web
      - client
      - spotify-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

  spotify-client: // NEW
    build:
      context: ./services/spotify-client
      dockerfile: Dockerfile-dev
    volumes:
      - './services/spotify-client:/usr/src/app'
      - '/usr/src/app/node_modules'
    ports:
      - 3000:8888
      - 8888:3000
    environment:
      - NODE_ENV=development
      - REACT_APP_WEB_SERVICE_URL=$REACT_APP_WEB_SERVICE_URL
    depends_on:
      - web
      - client

然后,我将每个节点进程设置为自己的Dockerfile,如下所示:

客户端/Dockerfile-dev

# base image
FROM node:11.6.0-alpine

# set working directory
WORKDIR /usr/src/app

# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH

# install and cache app dependencies
COPY package.json /usr/src/app/package.json
RUN npm install --silent
RUN npm install react-scripts@2.1.2 -g --silent

# start app
CMD ["npm", "start"]

spotify-client/Dockerfile-dev // 新

根据Spotify 网络文档的要求,运行不同的进程:

# base image
FROM node:11.6.0-alpine

# set working directory
WORKDIR /usr/src/app/authorization_code

# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH

# install and cache app dependencies
COPY package.json /usr/src/app/package.json
RUN npm install --silent
RUN npm install react-scripts@2.1.2 -g --silent

# start app <-- NOT npm start
CMD ["node", "app.js"] 

我的反向代理,我试过了:

nginx/dev.conf

server 

  listen 80;
  listen 8888; // NEW

  location / 
    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 /auth     // <-- app authorization, not Spotify's
    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;
  

在我的前端,我为我的重定向链接创建了一个component

client/src/components/SpofityRedirect.jsx

import React,  Component  from 'react';

class SpotifyRedirect extends Component
    render()
        return (
            <div className='SpotifyRedirect'>
                <a href='http://localhost:8888'> Log in with Spotify </a>
            </div>
        );
    


export default SpotifyRedirect;

这里我在“/”处显示这个重定向链接。

client/src/App.jsx

import SpotifyRedirect from './components/SpotifyRedirect';

 (...)
 <Switch
  <Route exact path='/' render=() => (
   <SpotifyRedirect/>
  ) />

 (...)
 </Switch>

更多:

spotify-client/authorization_code/app.js

(这是Spofity提供的,我只插入了http://localhost:3000

var express = require('express'); // Express web server framework
var request = require('request'); // "Request" library
var cors = require('cors');
var querystring = require('querystring');
var cookieParser = require('cookie-parser');

var client_id = 'is'; // Your client id
var client_secret = 'secret'; // Your secret
var redirect_uri = 'http://localhost:8888'; // Your redirect uri

/**
 * Generates a random string containing numbers and letters
 * @param  number length The length of the string
 * @return string The generated string
 */
var generateRandomString = function(length) 
  var text = '';
  var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

  for (var i = 0; i < length; i++) 
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  
  return text;
;

var stateKey = 'spotify_auth_state';

var app = express();

app.use(express.static(__dirname + '/public'))
   .use(cors())
   .use(cookieParser());

app.get('/login', function(req, res) 

  var state = generateRandomString(16);
  res.cookie(stateKey, state);

  // your application requests authorization
  var scope = 'user-read-private user-read-email user-read-playback-state playlist-modify-public playlist-modify-private';
  res.redirect('https://accounts.spotify.com/authorize?' +
    querystring.stringify(
      response_type: 'code',
      client_id: client_id,
      scope: scope,
      redirect_uri: redirect_uri,
      state: state
    ));
);

app.get('/callback', function(req, res) 

  // your application requests refresh and access tokens
  // after checking the state parameter

  var code = req.query.code || null;
  var state = req.query.state || null;
  var storedState = req.cookies ? req.cookies[stateKey] : null;

  if (state === null || state !== storedState) 
    res.redirect('/#' +
      querystring.stringify(
        error: 'state_mismatch'
      ));
   else 
    res.clearCookie(stateKey);
    var authOptions = 
      url: 'https://accounts.spotify.com/api/token',
      form: 
        code: code,
        redirect_uri: redirect_uri,
        grant_type: 'authorization_code'
      ,
      headers: 
        'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))
      ,
      json: true
    ;

    request.post(authOptions, function(error, response, body) 
      if (!error && response.statusCode === 200) 

        var access_token = body.access_token,
            refresh_token = body.refresh_token;

        var options = 
          url: 'https://api.spotify.com/v1/me',
          headers:  'Authorization': 'Bearer ' + access_token ,
          json: true
        ;

        // use the access token to access the Spotify Web API
        request.get(options, function(error, response, body) 
          console.log(body);
        );

        // we can also pass the token to the browser to make requests from there
        res.redirect('http://localhost:3000/#' + //NEW
          querystring.stringify(
            access_token: access_token,
            refresh_token: refresh_token
          ));
       else 
        res.redirect('/#' +
          querystring.stringify(
            error: 'invalid_token'
          ));
      
    );
  
);

app.get('/refresh_token', function(req, res) 

  // requesting access token from refresh token
  var refresh_token = req.query.refresh_token;
  var authOptions = 
    url: 'https://accounts.spotify.com/api/token',
    headers:  'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) ,
    form: 
      grant_type: 'refresh_token',
      refresh_token: refresh_token
    ,
    json: true
  ;

  request.post(authOptions, function(error, response, body) 
    if (!error && response.statusCode === 200) 
      var access_token = body.access_token;
      res.send(
        'access_token': access_token
      );
    
  );
);

console.log('Listening on 8888');
app.listen(8888);

______

Docker 命令行服务:

CONTAINER ID        IMAGE                      COMMAND                  CREATED             STATUS              PORTS                            NAMES
0e9870a7412c        dev3_nginx                 "nginx -g 'daemon of…"   9 seconds ago       Up 6 seconds        0.0.0.0:80->80/tcp               dev3_nginx_1
e6bc5bbff630        dev3_spotify-client        "node app.js"            26 minutes ago      Up 26 minutes       0.0.0.0:3000->8888/tcp           dev3_spotify-auth-server_1
a6b9e84953a3        dev3_client                "npm start"              25 hours              "/start.sh"              25 hours ago        Up 25 hours         80/tcp, 0.0.0.0:3008->8080/tcp  
16fb623ca2b3        dev3_web                   "/usr/src/app/entryp…"   25 hours 

最后,在构建之前,我运行:

$ export REACT_APP_WEB_SERVICE_URL=http://localhost


到目前为止,通过配置 aboce,当我点击 Log in with Soptify 时,我得到:


问题:

如何将上述配置与我的nginx reverse proxy 一起使用,以便:

    为位置 / 提供我的 Spotify's 重定向 uri http://localhost:8888 的链接 使用 Spotify 授权应用程序 授权完成后返回位置 '/'

【问题讨论】:

【参考方案1】:

问题:8888端口没有容器监听,可以直接在8888端口发布spotify-client:8888(不用nginx)。更新docker-compose-dev.yml

 spotify-client:
    ports:
      - 8888:8888

如果你真的需要 nginx,那么你将需要使用 nginx 配置 + 你还需要在 8888 端口上发布 nginx。 nginx/dev.conf 例子:


  server 
    listen 80;
    location / 
      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 /auth 
      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;
    
  

  # reverse proxy on the port 8888 for spotify-client
  server 
    listen  8888;
    location / 
      proxy_pass http://<spotify-client service/ip>:<port>/;
      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;
    
  

docker-compose-dev.yml 和 nginx 发布的端口:

  nginx:
    ports:
      - 80:80
      - 8888:8888

一般情况下需要正确配置nginx:8888->spotify-client:port

恕我直言:您根本不需要spotify-client 服务。只需在您的应用中使用implicit flow 即可获取 Spotify 令牌。它是 React/Angular(浏览器 JS 代码)更好的选择。请记住,此流程中不存在刷新令牌,因此您还需要实现静默刷新。

【讨论】:

谢谢。是的,我真的需要 nginx。您能否扩展您的答案以包含nginx publishing? @data_garden nginx.conf 示例包括 谢谢! nginx 和 spotify-client 都分配到了 8888 端口?我不会为 nginx 获得 Bind for 0.0.0.0:8888 failed: port is already allocated 吗? 另外,当我重建它时,我得到:nginx_1 | nginx: [emerg] "events" directive is not allowed here in /etc/nginx/conf.d/dev.conf:1 我给了你 2 个选项:1.) 没有反向代理,所以 spotify-client 在 8888 上发布 2.) 有反向代理,nginx 在 8888 上发布。不要期望完全复制/粘贴溶液。您仍然可能需要根据需要对其进行自定义。我已经更新了答案 - 现在有 dev.conf,而不是 nginx.conf【参考方案2】:

当您决定使用 nginx 时,您需要编辑配置并在端口 8888 上发布 nginx。

【讨论】:

以上是关于在 Docker 中使用 React 和 Nginx 授权 Spotify的主要内容,如果未能解决你的问题,请参考以下文章

docker nginx没有加载css样式

使用 Nginx 和 Docker 部署 React 和 Django

Docker搭建Nginx

05-docker系列-使用dockerfile构建镜像

使用 react 和 express 建立 web-socket 通信(nginx、docker)

Docker 使用啥来托管/运行 Web 应用程序?