icholy / digest

Go HTTP Digest Access Authentication

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cookies discarded from challenge response

mizhousoft opened this issue · comments

Thanks for your code, solved my proble.

But there is a problem,post the second url with Authorization header, it will be fail. because the server has given up the nonce paramter.

so I think post the second url with cookie header.

I modify the code:

  1. add cookiejar.go file
type HttpCookieJar struct {
	cookies []*http.Cookie
}

func (jar *HttpCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
	jar.cookies = cookies
}
func (jar *HttpCookieJar) Cookies(u *url.URL) []*http.Cookie {
	return jar.cookies
}
  1. modify transport.go file
func (t *Transport) clear(res *http.Response) error {
	host := res.Request.URL.Hostname()

	t.cacheMu.Lock()
	if t.cache == nil {
		t.cache = map[string]*cached{}
	}

	delete(t.cache, host)

	t.cacheMu.Unlock()

	return nil
}

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	// use the configured transport if there is one
	tr := t.Transport
	if tr == nil {
		tr = http.DefaultTransport
	}
	// don't modify the original request
	clone, err := cloner(req)
	if err != nil {
		return nil, err
	}
	// setup the first request
	first, err := clone()
	if err != nil {
		return nil, err
	}
	// try to authorize the request using a cached challenge
	if err := t.authorize(first); err != nil {
		return nil, err
	}
	// the first request will either succeed or return a 401
	res, err := tr.RoundTrip(first)
	if err != nil || res.StatusCode != http.StatusUnauthorized {
		return res, err
	}
	// close the first message body
	res.Body.Close()
	// save the challenge for future use
	if err := t.save(res); err != nil {
		return nil, err
	}
	// setup the second request
	second, err := clone()
	if err != nil {
		return nil, err
	}
	// authorise a second request based on the new challenge
	if err := t.authorize(second); err != nil {
		return nil, err
	}

	resp, error := tr.RoundTrip(second)

	t.clear(resp)

	return resp, error
}

3 Test demo

// Source code from https://github.com/icholy/digest

func TestHttpDigest(t *testing.T) {
	jar := new(HttpCookieJar)
	client := http.Client{
		Transport: &Transport{
			Username: "test",
			Password: "test",
		},
		Jar: jar,
	}

	res, _ := client.Get("http://localhost:8080/demo/user.action")
	println(res.Status)

	res, _ = client.Get("http://localhost:8080/demo/test.action")
	println(res.Status)
}

It's legal to re-use a nonce (this prevents 2 round-trips for every request). The server can reject it and provide a new challenge. What error are you seeing? The stdlib already implements a cookiejar https://golang.org/pkg/net/http/cookiejar/

微信图片_20200730150634

http client post the second url (http://localhost:8080/demo/test.action) with Authorization, the server will reject, because it is not safe. so must re-authenticate.

The cookiejar https://golang.org/pkg/net/http/cookiejar/ is very good.

That’s how digest auth works. Most servers will let you re-use a nonce for a period of time before invalidating it. Wireshark your browser and you’ll see the same thing. If you want something safe, don’t use digest auth.

Yes, I use spring security as server digest auth, I read spring securitysource code, the nonce validity second is 300 second.

Thanks.

I'm actually facing the same issue when the server is using Flask. Same thing happens from Postman. The 2nd request fails unless I clear the cookie.

But doing a request from Go using this lib fails on first try too.

Please read the thread. It's not an issue, it's how digest auth works.

So it's expected not to work with the flash-auth digest impl.? Or you're only talking about the 2nd req fail?

The first request should always fail assuming the client doesn't have a cached challenge from a previous failed request. Some servers don't support re-using the challenge, so every other request will fail (to get the new challenge). This should all happen behind the scenes automatically.

edit: is your request not going through at all? If so, open a separate issue, because it's unrelated.

The first request should always fail assuming the client doesn't have a cached challenge from a previous failed request. Some servers don't support re-using the challenge, so every other request will fail (to get the new challenge). This should all happen behind the scenes automatically.

edit: is your request not going through at all? If so, open a separate issue, because it's unrelated.

Yes, I'd expect the first request to fail, then after I get a challenge, I'd expect it to go through. But I can see 401 on both requests from the Go client.

I'm using a pretty basic setup of Flask. It looks like:

  1. https://flask.palletsprojects.com/en/2.0.x/quickstart/#a-minimal-application
  2. https://flask-httpauth.readthedocs.io/en/latest/ - using digest auth
  3. A simple Go client that uses this lib for auth (very much like your example

I can provide a more accurate explanation of what I do later on as I'm not at my workstation.

Copy/paste that into a new issue. This sounds like it might be a real bug.

@rolandjitsu the problem is that flask auth is trying to set cookies as part of the challenge response. That response never makes it out of the transport, so the http.Client doesn't have a chance to add it to its http.(*Client).Jar. I just pushed version v0.1.11 which allows you to specify an http.CookieJar in the digest.Transport.

package main

import (
	"net/http"
	"net/http/cookiejar"

	"github.com/icholy/digest"
)

func main() {
	jar, _ := cookiejar.New(nil)
	client := &http.Client{
		Jar: jar,
		Transport: &digest.Transport{
			Jar:      jar,
			Username: "foo",
			Password: "bar",
		},
	}
	res, err := client.Get("http://localhost:8080/some_outdated_service")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

@rolandjitsu the problem is that flask auth is trying to set cookies as part of the challenge response. That response never makes it out of the transport, so the http.Client doesn't have a chance to add it to its http.(*Client).Jar. I just pushed version v0.1.11 which allows you to specify an http.CookieJar in the digest.Transport.

@icholy thanks!

@icholy just tested the fix and I can confirm it works.