Dosya indirmeleri JWT tabanlı kimlik doğrulama ile nasıl yönetilir?


116

Kimlik doğrulamasının bir JWT belirteci tarafından işlendiği Angular'da bir web uygulaması yazıyorum, bu da her isteğin gerekli tüm bilgileri içeren bir "Kimlik Doğrulama" başlığına sahip olduğu anlamına gelir.

Bu, REST çağrıları için iyi çalışıyor, ancak arka uçta barındırılan dosyalar için indirme bağlantılarını nasıl kullanmam gerektiğini anlamıyorum (dosyalar, web hizmetlerinin barındırıldığı aynı sunucuda bulunur).

<a href='...'/>Herhangi bir başlık taşımadıkları ve kimlik doğrulama başarısız olacağı için normal bağlantıları kullanamıyorum . Çeşitli büyüler için de aynı window.open(...).

Düşündüğüm bazı çözümler:

  1. Sunucuda geçici olarak güvenli olmayan bir indirme bağlantısı oluşturun
  2. Kimlik doğrulama bilgilerini bir url parametresi olarak iletin ve durumu manuel olarak işleyin
  3. Verileri XHR aracılığıyla alın ve dosya istemcisi tarafını kaydedin.

Yukarıdakilerin tümü tatmin edici olmaktan daha azdır.

Şu anda kullandığım çözüm 1. İki nedenden dolayı hoşuma gitmiyor: birincisi güvenlik açısından ideal değil, ikincisi çalışıyor ancak özellikle sunucuda epeyce çalışma gerektiriyor: bir şeyi indirmek için yeni bir "rastgele" üreten bir hizmeti aramam gerekiyor "url, bir yerde (muhtemelen DB'de) bir süre saklar ve istemciye geri gönderir. İstemci url'yi alır ve onunla window.open veya benzerini kullanır. İstendiğinde, yeni url hala geçerli olup olmadığını kontrol etmeli ve ardından verileri döndürmelidir.

2 en az iş kadar görünüyor.

3, mevcut kitaplıkları kullanmak ve birçok olası sorunu kullanmak bile çok çalışma gerektiriyor gibi görünüyor. (Kendi indirme durum çubuğumu sağlamam, tüm dosyayı belleğe yüklemem ve ardından kullanıcıdan dosyayı yerel olarak kaydetmesini istemem gerekir).

Görev oldukça basit görünüyor, bu yüzden kullanabileceğim çok daha basit bir şey olup olmadığını merak ediyorum.

"Açısal yol" için mutlaka bir çözüm aramıyorum. Normal Javascript iyi olur.


Uzaktan kumandayla, indirilebilir dosyaların Angular uygulamasından farklı bir etki alanında olduğunu mu kastediyorsunuz? Uzaktan kumandayı kontrol ediyor musunuz (arka ucunu değiştirme erişiminiz var) veya yok mu?
robertjd

Dosya verilerinin istemcide (tarayıcı) olmadığını söylüyorum; dosya aynı etki alanında barındırılıyor ve arka uç üzerinde denetim bende. Daha az belirsiz hale getirmek için soruyu güncelleyeceğim.
Marco Righele

2. seçeneğin zorluğu arka ucunuza bağlıdır. Arka ucunuza, kimlik doğrulama katmanından geçtiğinde JWT'nin yetkilendirme başlığına ek olarak sorgu dizesini de kontrol etmesini söylerseniz, işiniz bitmiştir. Hangi arka ucu kullanıyorsunuz?
Technetium

Yanıtlar:


47

İndirme özelliğini , getirme API'sini ve URL.createObjectURL'yi kullanarak istemciye indirmenin bir yolu . JWT'nizi kullanarak dosyayı alır, yükü bir blob'a dönüştürür, blob'u bir objectURL'ye yerleştirir, bağlantı etiketinin kaynağını bu objectURL'ye ayarlar ve javascript'te o objectURL'yi tıklatırsınız.

let anchor = document.createElement("a");
document.body.appendChild(anchor);
let file = 'https://www.example.com/some-file.pdf';

let headers = new Headers();
headers.append('Authorization', 'Bearer MY-TOKEN');

fetch(file, { headers })
    .then(response => response.blob())
    .then(blobby => {
        let objectUrl = window.URL.createObjectURL(blobby);

        anchor.href = objectUrl;
        anchor.download = 'some-file.pdf';
        anchor.click();

        window.URL.revokeObjectURL(objectUrl);
    });

downloadÖzniteliğin değeri , nihai dosya adı olacaktır. İsterseniz, diğer yanıtlarda açıklandığı gibi içerik düzenleme yanıt başlığından amaçlanan bir dosya adı çıkarabilirsiniz .


1
Neden kimsenin bu yanıtı dikkate almadığını merak ediyorum. Çok basit ve 2017'de yaşadığımız için platform desteği oldukça iyi.
Rafal Pastuszak

1
Ancak indirme özniteliği için iosSafari desteği oldukça kırmızı görünüyor :(
Martin Cremer

1
Bu benim için kromda iyi çalıştı. Firefox için bağlantıyı belgeye ekledikten sonra çalıştı: document.body.appendChild (anchor); Edge için herhangi bir çözüm bulamadı ...
Tompi

12
Bu çözüm işe yarıyor ancak bu çözüm, büyük dosyalarla ilgili UX endişelerini ele alıyor mu? Bazen 300MB'lık bir dosya indirmem gerekirse, bağlantıyı tıklayıp tarayıcının indirme yöneticisine göndermeden önce indirmem biraz zaman alabilir. Çabayı getirme-ilerleme API'sini kullanarak harcayabilir ve kendi indirme ilerleme kullanıcı arayüzümüzü oluşturabiliriz .. ama aynı zamanda, sadece indirmeye devretmek için js-land'e (bellekte?) yönetici.
scvnc

1
@Tompi ben de bu işi Edge ve IE için yapamadım
zappa

34

teknik

Auth0'dan JWT evangelisti olarak tanınan Matias Woloski'nin bu tavsiyesine dayanarak, Hawk ile imzalı bir talep oluşturarak sorunu çözdüm .

Woloski'den alıntı:

Bunu çözmenin yolu, örneğin AWS'nin yaptığı gibi imzalı bir istek oluşturmaktır.

Burada , etkinleştirme bağlantıları için kullanılan bu tekniğin bir örneğini görüyorsunuz .

Arka uç

İndirme URL'lerimi imzalamak için bir API oluşturdum:

İstek:

POST /api/sign
Content-Type: application/json
Authorization: Bearer...
{"url": "https://path.to/protected.file"}

Tepki:

{"url": "https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c"}

İmzalanmış bir URL ile dosyayı alabiliriz

İstek:

GET https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c

Tepki:

Content-Type: multipart/mixed; charset="UTF-8"
Content-Disposition': attachment; filename=protected.file
{BLOB}

frontend ( jojoyuji tarafından )

Bu şekilde hepsini tek bir kullanıcı tıklamasıyla yapabilirsiniz:

function clickedOnDownloadButton() {

  postToSignWithAuthorizationHeader({
    url: 'https://path.to/protected.file'
  }).then(function(signed) {
    window.location = signed.url;
  });

}

2
Bu harika ama güvenlik açısından OP'nin 2 numaralı seçeneğinden (sorgu dizesi parametresi olarak belirteç) ne kadar farklı olduğunu anlamıyorum. Aslında, imzalanan talebin daha kısıtlayıcı olabileceğini, yani sadece belirli bir uç noktaya erişmesine izin verileceğini hayal edebiliyorum. Ancak OP'nin 2 numarası daha kolay görünüyor / daha az adım, bunda yanlış olan ne?
Tyler Collier

4
Web sunucunuza bağlı olarak tam URL, günlük dosyalarına kaydedilebilir. BT çalışanlarınızın tüm belirteçlere erişmesini istemeyebilirsiniz.
Ezequias Dinella

2
Ek olarak, sorgu dizesini içeren URL, kullanıcı geçmişinize kaydedilerek aynı makinedeki diğer kullanıcıların URL'ye erişmesine izin verir.
Ezequias Dinella

1
Son olarak ve bunu çok güvensiz kılan şey, URL'nin herhangi bir kaynağa, hatta üçüncü taraf kaynaklara yönelik tüm isteklerin Referer başlığında gönderilmesidir. Dolayısıyla, örneğin Google Analytics kullanıyorsanız, Google'a URL jetonunu ve tümünü göndereceksiniz.
Ezequias Dinella

1
Bu metin buradan alınmıştır: stackoverflow.com/questions/643355/…
Ezequias Dinella

10

Daha önce bahsedilen mevcut "fetch / createObjectURL" ve "download-token" yaklaşımlarına bir alternatif, yeni bir pencereyi hedefleyen standart bir Form POST'udur . Tarayıcı, sunucu yanıtındaki ek başlığını okuduğunda, yeni sekmeyi kapatır ve indirmeye başlar. Bu aynı yaklaşım, PDF gibi bir kaynağı yeni bir sekmede görüntülemek için de iyi çalışıyor.

Bu, eski tarayıcılar için daha iyi bir desteğe sahiptir ve yeni bir simge türünü yönetmek zorunda kalmaz. Bu ayrıca, URL'deki kullanıcı adı / şifre desteği tarayıcılar tarafından kaldırıldığından , URL'de temel kimlik doğrulamasından daha iyi uzun vadeli desteğe sahip olacaktır .

On istemci tarafında kullandığımız target="_blank"hatta İdarelerine (tek sayfa uygulamalar) için özellikle önemlidir başarısızlık durumlarda, önlemek navigasyona.

Önemli uyarı olmasıdır sunucu tarafı JWT'de doğrulama ile ilgili belirteç almak zorunda POST verileri ve başlıktan değildir . Çerçeveniz Kimlik Doğrulama üstbilgisini kullanarak yönlendiricilere erişimi otomatik olarak yönetiyorsa, doğru yetkilendirmeyi sağlamak için JWT'yi manuel olarak doğrulayabilmeniz için işleyicinizi kimlik doğrulaması yapılmamış / anonim olarak işaretlemeniz gerekebilir.

Form dinamik olarak oluşturulabilir ve düzgün bir şekilde temizlenebilmesi için hemen imha edilebilir (not: bu düz JS'de yapılabilir, ancak burada JQuery açıklık için kullanılır) -

function DownloadWithJwtViaFormPost(url, id, token) {
    var jwtInput = $('<input type="hidden" name="jwtToken">').val(token);
    var idInput = $('<input type="hidden" name="id">').val(id);
    $('<form method="post" target="_blank"></form>')
                .attr("action", url)
                .append(jwtInput)
                .append(idInput)
                .appendTo('body')
                .submit()
                .remove();
}

Gizli girişler olarak göndermeniz gereken ekstra verileri ekleyin ve bunların forma eklendiğinden emin olun.


1
Bu çözümün büyük ölçüde hak edilmediğine inanıyorum. Kolay, temiz ve mükemmel çalışıyor.
Yura Fedoriv

6

İndirmek için jeton üretecektim.

Açısal dahilinde, geçici bir belirteç (örneğin bir saat) elde etmek için kimliği doğrulanmış bir istekte bulunun ve ardından bunu bir get parametresi olarak url'ye ekleyin. Bu şekilde dosyaları istediğiniz şekilde indirebilirsiniz (window.open ...)


2
Şimdilik kullandığım çözüm bu, ancak memnun değilim çünkü oldukça fazla iş var ve
umarım

3
Bunun mevcut en temiz çözüm olduğunu düşünüyorum ve orada çok fazla iş göremiyorum. Ancak ya daha küçük bir geçerlilik süresi seçerdim (örneğin 3 dakika) ya da sunucuda belirteçlerin bir listesini tutarak ve kullanılan belirteçleri silerek (listemde olmayan belirteçleri kabul etmeyerek) bir kerelik belirteç yapardım. ).
nabinca

5

Ek bir çözüm: temel kimlik doğrulamasını kullanmak. Arka uçta biraz çalışma gerektirmesine rağmen, belirteçler günlüklerde görünmez ve URL imzalamanın uygulanması gerekmez.


İstemci Tarafı

Örnek bir URL şöyle olabilir:

http://jwt:<user jwt token>@some.url/file/35/download

Sahte jetonlu örnek:

http://jwt:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwIiwibmFtZSI6IiIsImlhdCI6MH0.KsKmQOZM-jcy4l_7NFsv1lWfpH8ofniVCv75ZRQrWno@some.url/file/35/download

Daha sonra bunu <a href="...">ya da window.open("...")- tarayıcı gerisini halleder.


Sunucu Tarafı

Buradaki uygulama size bağlıdır ve sunucu kurulumunuza bağlıdır - ?token=sorgu parametresini kullanmaktan çok da farklı değildir .

Laravel'i kullanarak kolay yoldan gittim ve temel kimlik doğrulama parolasını JWT Authorization: Bearer <...>başlığına dönüştürdüm ve normal kimlik doğrulama ara yazılımının geri kalanı halletmesine izin verdim :

class CarryBasic
{
    /**
     * @param Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle($request, \Closure $next)
    {
        // if no basic auth is passed,
        // or the user is not "jwt",
        // send a 401 and trigger the basic auth dialog
        if ($request->getUser() !== 'jwt') {
            return $this->failedBasicResponse();
        }

        // if there _is_ basic auth passed,
        // and the user is JWT,
        // shove the password into the "Authorization: Bearer <...>"
        // header and let the other middleware
        // handle it.
        $request->headers->set(
            'Authorization',
            'Bearer ' . $request->getPassword()
        );

        return $next($request);
    }

    /**
     * Get the response for basic authentication.
     *
     * @return void
     * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
     */
    protected function failedBasicResponse()
    {
        throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
    }
}

Bu yaklaşım umut verici görünüyor, ancak JWT jetonuna bu şekilde erişmenin bir yolunu görmüyorum. Sunucunun bu garip url'yi nasıl ayrıştırdığını ve jwt belirteci değerine nereden erişileceğini bana bazı kaynaklara yönlendirebilir misiniz?
Jiri Vetyska

1
@JiriVetyska LOL Umut verici? Token, başlıklarda geçirmekten daha nettir ahahahha
Liquid Core
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.