mirror of
https://github.com/labstack/echo.git
synced 2024-11-28 08:38:39 +02:00
Avoid redirect loop in HTTPSRedirect middleware (#1058)
In HTTPSRedirect and similar middlewares, determining if redirection is needed using `c.IsTLS()` causes redirect loop when an application is running behind a TLS termination proxy, e.g. AWS ELB. Instead, I believe, redirection should be determined by `c.Scheme() != "https"`. This works well even behind a TLS termination proxy.
This commit is contained in:
parent
ec048ea523
commit
ef82f3e034
@ -16,10 +16,10 @@ type RedirectConfig struct {
|
|||||||
Code int `yaml:"code"`
|
Code int `yaml:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirectLogic represents a function that given a tls flag, host and uri
|
// redirectLogic represents a function that given a scheme, host and uri
|
||||||
// can both: 1) determine if redirect is needed (will set ok accordingly) and
|
// can both: 1) determine if redirect is needed (will set ok accordingly) and
|
||||||
// 2) return the appropriate redirect url.
|
// 2) return the appropriate redirect url.
|
||||||
type redirectLogic func(tls bool, scheme, host, uri string) (ok bool, url string)
|
type redirectLogic func(scheme, host, uri string) (ok bool, url string)
|
||||||
|
|
||||||
const www = "www"
|
const www = "www"
|
||||||
|
|
||||||
@ -40,8 +40,8 @@ func HTTPSRedirect() echo.MiddlewareFunc {
|
|||||||
// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||||
// See `HTTPSRedirect()`.
|
// See `HTTPSRedirect()`.
|
||||||
func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||||
return redirect(config, func(tls bool, _, host, uri string) (ok bool, url string) {
|
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||||
if ok = !tls; ok {
|
if ok = scheme != "https"; ok {
|
||||||
url = "https://" + host + uri
|
url = "https://" + host + uri
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -59,8 +59,8 @@ func HTTPSWWWRedirect() echo.MiddlewareFunc {
|
|||||||
// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||||
// See `HTTPSWWWRedirect()`.
|
// See `HTTPSWWWRedirect()`.
|
||||||
func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||||
return redirect(config, func(tls bool, _, host, uri string) (ok bool, url string) {
|
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||||
if ok = !tls && host[:3] != www; ok {
|
if ok = scheme != "https" && host[:3] != www; ok {
|
||||||
url = "https://www." + host + uri
|
url = "https://www." + host + uri
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -78,8 +78,8 @@ func HTTPSNonWWWRedirect() echo.MiddlewareFunc {
|
|||||||
// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||||
// See `HTTPSNonWWWRedirect()`.
|
// See `HTTPSNonWWWRedirect()`.
|
||||||
func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||||
return redirect(config, func(tls bool, _, host, uri string) (ok bool, url string) {
|
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||||
if ok = !tls; ok {
|
if ok = scheme != "https"; ok {
|
||||||
if host[:3] == www {
|
if host[:3] == www {
|
||||||
host = host[4:]
|
host = host[4:]
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ func WWWRedirect() echo.MiddlewareFunc {
|
|||||||
// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||||
// See `WWWRedirect()`.
|
// See `WWWRedirect()`.
|
||||||
func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||||
return redirect(config, func(_ bool, scheme, host, uri string) (ok bool, url string) {
|
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||||
if ok = host[:3] != www; ok {
|
if ok = host[:3] != www; ok {
|
||||||
url = scheme + "://www." + host + uri
|
url = scheme + "://www." + host + uri
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ func NonWWWRedirect() echo.MiddlewareFunc {
|
|||||||
// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||||
// See `NonWWWRedirect()`.
|
// See `NonWWWRedirect()`.
|
||||||
func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||||
return redirect(config, func(tls bool, scheme, host, uri string) (ok bool, url string) {
|
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||||
if ok = host[:3] == www; ok {
|
if ok = host[:3] == www; ok {
|
||||||
url = scheme + "://" + host[4:] + uri
|
url = scheme + "://" + host[4:] + uri
|
||||||
}
|
}
|
||||||
@ -143,7 +143,7 @@ func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc {
|
|||||||
|
|
||||||
req, scheme := c.Request(), c.Scheme()
|
req, scheme := c.Request(), c.Scheme()
|
||||||
host := req.Host
|
host := req.Host
|
||||||
if ok, url := cb(c.IsTLS(), scheme, host, req.RequestURI); ok {
|
if ok, url := cb(scheme, host, req.RequestURI); ok {
|
||||||
return c.Redirect(config.Code, url)
|
return c.Redirect(config.Code, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,47 +12,74 @@ import (
|
|||||||
type middlewareGenerator func() echo.MiddlewareFunc
|
type middlewareGenerator func() echo.MiddlewareFunc
|
||||||
|
|
||||||
func TestRedirectHTTPSRedirect(t *testing.T) {
|
func TestRedirectHTTPSRedirect(t *testing.T) {
|
||||||
res := redirectTest(HTTPSRedirect, "labstack.com")
|
res := redirectTest(HTTPSRedirect, "labstack.com", nil)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
||||||
assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
|
assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHTTPSRedirectBehindTLSTerminationProxy(t *testing.T) {
|
||||||
|
header := http.Header{}
|
||||||
|
header.Set(echo.HeaderXForwardedProto, "https")
|
||||||
|
res := redirectTest(HTTPSRedirect, "labstack.com", header)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, res.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRedirectHTTPSWWWRedirect(t *testing.T) {
|
func TestRedirectHTTPSWWWRedirect(t *testing.T) {
|
||||||
res := redirectTest(HTTPSWWWRedirect, "labstack.com")
|
res := redirectTest(HTTPSWWWRedirect, "labstack.com", nil)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
||||||
assert.Equal(t, "https://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
|
assert.Equal(t, "https://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRedirectHTTPSWWWRedirectBehindTLSTerminationProxy(t *testing.T) {
|
||||||
|
header := http.Header{}
|
||||||
|
header.Set(echo.HeaderXForwardedProto, "https")
|
||||||
|
res := redirectTest(HTTPSWWWRedirect, "labstack.com", header)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, res.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRedirectHTTPSNonWWWRedirect(t *testing.T) {
|
func TestRedirectHTTPSNonWWWRedirect(t *testing.T) {
|
||||||
res := redirectTest(HTTPSNonWWWRedirect, "www.labstack.com")
|
res := redirectTest(HTTPSNonWWWRedirect, "www.labstack.com", nil)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
||||||
assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
|
assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRedirectHTTPSNonWWWRedirectBehindTLSTerminationProxy(t *testing.T) {
|
||||||
|
header := http.Header{}
|
||||||
|
header.Set(echo.HeaderXForwardedProto, "https")
|
||||||
|
res := redirectTest(HTTPSNonWWWRedirect, "www.labstack.com", header)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, res.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRedirectWWWRedirect(t *testing.T) {
|
func TestRedirectWWWRedirect(t *testing.T) {
|
||||||
res := redirectTest(WWWRedirect, "labstack.com")
|
res := redirectTest(WWWRedirect, "labstack.com", nil)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
||||||
assert.Equal(t, "http://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
|
assert.Equal(t, "http://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedirectNonWWWRedirect(t *testing.T) {
|
func TestRedirectNonWWWRedirect(t *testing.T) {
|
||||||
res := redirectTest(NonWWWRedirect, "www.labstack.com")
|
res := redirectTest(NonWWWRedirect, "www.labstack.com", nil)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
||||||
assert.Equal(t, "http://labstack.com/", res.Header().Get(echo.HeaderLocation))
|
assert.Equal(t, "http://labstack.com/", res.Header().Get(echo.HeaderLocation))
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirectTest(fn middlewareGenerator, host string) *httptest.ResponseRecorder {
|
func redirectTest(fn middlewareGenerator, host string, header http.Header) *httptest.ResponseRecorder {
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
next := func(c echo.Context) (err error) {
|
next := func(c echo.Context) (err error) {
|
||||||
return c.NoContent(http.StatusOK)
|
return c.NoContent(http.StatusOK)
|
||||||
}
|
}
|
||||||
req := httptest.NewRequest(echo.GET, "/", nil)
|
req := httptest.NewRequest(echo.GET, "/", nil)
|
||||||
req.Host = host
|
req.Host = host
|
||||||
|
if header != nil {
|
||||||
|
req.Header = header
|
||||||
|
}
|
||||||
res := httptest.NewRecorder()
|
res := httptest.NewRecorder()
|
||||||
c := e.NewContext(req, res)
|
c := e.NewContext(req, res)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user