Авторизуйтесь с помощью Spotify, используя Nginx - PullRequest
3 голосов
/ 23 марта 2019

У меня есть приложение-докер, запускающее три службы:

  • клиент -> реагировать на интерфейс
  • web ---> внутренняя часть колбы
  • nginx -> - обратный прокси для обоих

Это (упрощенная) структура проекта:

docker-compose-dev.yml
services/
        client/
              src/
                 app.jsx
                 components/ 
                           spotify-auth.js
                           Spotify.jsx
         nginx/
              dev.conf
         web/

Вот где я определил открытые порты во время сборки:

докер-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. Для моего решения у меня есть несколько вариантов:

enter image description here

Это мой файл 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 файлы, используемые для:

  1. Аутентификация с помощью Spotify -----> Неявный грант
  2. Перенаправить мое приложение обратно на 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>

ОШИБКА:

После того, как все это установлено и запущено, я получаю:

INVALID_CLIENT: Invalid redirect URI 

ЛОГИ:

Перед сборкой я экспортирую эту 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" "-"

Примечание

Если я попытаюсь использовать localhost:8888 на Spotify.jsx, приложению удастся пройти аутентификацию с помощью Spotify, но тогда все местоположения начнутся с localhost:8888/auth/login и т. Д., Что нежелательно.


ВОПРОС

Почему localhost:3000, мой client не будет работать как redirect uri? Что мне не хватает?

Это самый надежный способ аутентификации с помощью Spotify в docker проекте, подобном этому?

1 Ответ

4 голосов
/ 31 марта 2019

Ваша проблема:

В вашем docker-compose.yml контейнере client настроено отображение портов 3007:3000.Обратите внимание, что Docker-compose port mapping имеет значение host:container ( Ссылка Compose file ), это означает, что порт 3007 вашего хоста сопоставляется с портом 3000 контейнера.

Таким образом, вывы пытаетесь подключиться к порту вашего контейнера, который недоступен на вашем хосте, хотя nginx доволен им, потому что он находится в той же сети, что и ваш клиент, поэтому он может связаться с ним и перенаправить запрос.

Если это так:

  • http://localhost:3000 не работает, потому что он закрыт на стороне хоста.
  • http://localhost:3007 открывает ваш клиент, потому чтоон перенаправляется в ваш клиентский контейнер, но вы не сможете использовать там аутентификацию Spotify, пока не внесете этот URL в белый список и не измените свой redirect_uri.
  • http://localhost:8888, откроет ваш клиент, потому что у вас есть nginx настроен как обратный прокси-сервер, и он может получить доступ к клиентскому порту 3000, поскольку он находится в одной сети.

Ваше решение:

Ваше решение меняетсяdocker-compose, поэтому клиент сопоставлен с портами3000:3000.Тогда с Spotify auth должно быть все в порядке, поскольку порты открыты и URL-адреса настроены правильно.

Дополнительно:

Что касается вашего запроса мнения о дизайне, nginxчувствует себя немного недооцененным вашим дизайном.Если вы настроили обратный прокси-сервер, службы, на которые вы перенаправляете, скрыты в защищенной сети, к которой нет доступа извне.Таким образом, вы можете, например, настроить SSL на nginx и забыть об HTTP для остальных служб.Однако, если к таким службам можно получить доступ через другие порты, конфигурация такого типа бесполезна.

В производственной настройке вы захотите закрыть порты client и webиз вашего docker-compose (буквально удалите сопоставление портов. Nginx не будет иметь проблем с доступом к вашим контейнерам, так как он находится в той же сети, в отличие от вашего хоста), и оставит только nginx доступным для реального мира.

Возможно, вы также захотите установить правило rewrite, в котором клиент и сервер зависают на адресах http://localhost/client, http://localhost/server соответственно, но nginx переписывает запрос и передает его в соответствующий контейнер, чтобы контейнеры фактически видели запрос, поступающий к http://localhost:3000/.Вы можете увидеть пример того, как настроить все это на Stack Exchange - обратный прокси-сервер Nginx + перезапись URL .

...