finished full translation to chapter 6

This commit is contained in:
ma0
2016-12-28 19:40:55 -05:00
parent 8bc6fbbcb5
commit c54ffbb67c
4 changed files with 462 additions and 0 deletions

222
es/06.2.md Normal file
View File

@@ -0,0 +1,222 @@
# 6.2 ¿Cómo usar sesiones en Go?
En la sección 6.1, aprendimos que las sesiones son soluciones para la verificación de usuarios y que por ahora, la librería estándar de Go no tiene soporte para las sesiones o el manejo de ellas. Entonces, vamos a implementar nuestro propio manejador de sesiones en Go.
## Creando sesiones
El principio básico detrás de las sesiones es que el servidor mantiene la información de cada cliente , y los clientes tienen una única sesión para acceder a su información. Cuando los usuarios visitan la aplicación web, el servidor crea una sesión siguiendo los siguientes pasos en tanto sean necesarios:
- Crear un identificador de sesión único.
- Abrir un espacio de almacenamiento: normalmente tenemos las sesiones en memoria, pero las perderás si el sistema accidentalmente se interrumpe. Esto puede ser un serio problema si la aplicación web maneja datos sensibles, como de comercio electrónico por ejemplo. Para solucionar este problema, puedes guardar los datos de sesión en la base de datos o en el sistema de ficheros. Eso hace que los datos sean mas fiables y fácil de compartir con otras aplicaciones, sin embargo la negociaciónde información es mas del lado del servidor para leer y escribir sesiones.
- Enviar el identificador de sesión único al cliente.
El paso clave aquí es enviar un identificador único de sesión al cliente. En el contexto de una respuesta HTTP estándar, puedes usar el encabezado o el cuerpo que lo acompaña, por consiguiente, tenemos dos maneras de enviar identificadores de sesión a los clientes: por cookies o por URLs.
- Cookies: el servidor puede fácilmente usar `Set-cookie` dentro del encabezado de la respuesta para enviar el identificador de sesión del cliente, y el cliente puede usar esta cookie para las futuras peticiones; usualmente definimos el tiempo de expiración de cookies que contienen información de sesión a 0, lo que significa que la cookie será guardada en memoria y será eliminada cuando el usuario cierre el navegador.
- URLs: concatenar el identificador de sesión como argumento en todas las páginas. Esta opción puede sonar desordenada, pero es la mejor opción si el navegador tiene deshabilitadas las cookies.
## Usando Go para manejar sesiones
Hemos hablado sobre la construcción de sesiones, y deberías tener una idea de como funciona, pero ¿cómo podemos usar sesiones en páginas dinámicas? Miremos de cerca al ciclo de vida de una sesión y luego podremos continuar la implementación de nuestro manejador de sesiones en Go.
### Diseño del manejo de sesión
Aquí está una lista de algunas de las consideraciones claves para el diseño del manejo de sesión
- Manejador de sesiones Global.
- Mantener el identificador de sesión único.
- Tener una sesión por cada usuario.
- Almacenamiento de la sesión en memoria, archivos o base de datos.
- Manejar las sesiones expiradas.
A continuación vamos a examinar un ejemplo completo de un manejador de sesiones en Go y el porqué de las decisiones de diseño que se tomaron.
### Manejador de sesión
Definir un manejador de sesiones global:
```
type Manager struct {
cookieName string //private cookiename
lock sync.Mutex // protects session
provider Provider
maxlifetime int64
}
func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) {
provider, ok := provides[provideName]
if !ok {
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
}
return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
}
```
Crear un manejador de sesiones global en la función `main()`:
```
var globalSessions *session.Manager
// Then, initialize the session manager
func init() {
globalSessions = NewManager("memory","gosessionid",3600)
}
```
Como sabemos que podemos guardar las sesiones de muchas maneras, como en memoria, sistema de ficheros o directamente en la base de datos, necesitamos definir un interfaz `Provider` en orden de representar la estructura bajo nuestro manejador de sesiones:
```
type Provider interface {
SessionInit(sid string) (Session, error)
SessionRead(sid string) (Session, error)
SessionDestroy(sid string) error
SessionGC(maxLifeTime int64)
}
```
- `SessionInit` implementa la inicialización de una sesión y retorna una nueva sesión si es exitoso.
- `SessionRead` retorna una sesión representada con el identificador de sesión sid. Crea una nueva sesión y la retorna si no existe.
- `SessionDestroy` dado un sid, elimina la sesión correspondiente.
- `SessionGC` elimina las variables de sesión basado en el criterio `maxLifeTime`.
Entonces, ¿qué métodos debería tener nuestra interfaz de sesión? Si tienes alguna experiencia en desarrollo web deberías saber que solo hay cuatro operaciones para las sesiones: definir el valor, obtener el valor, eliminar el valor y obtener el identificador actual. Entonces en nuestra interfaz de sesión vamos a tener estos cuatro métodos para realizar estas operaciones.
```
type Session interface {
Set(key, value interface{}) error //set session value
Get(key interface{}) interface{} //get session value
Delete(key interface{}) error //delete session value
SessionID() string //back current sessionID
}
```
Este diseño está basado en `database/sql/driver`, que define la interface primero, luego registra la estructura específica que queremos usar. El siguiente código es la implementación interna de nuestra función de registro.
```
var provides = make(map[string]Provider)
// Register makes a session provider available by the provided name.
// If a Register is called twice with the same name or if the driver is nil,
// it panics.
func Register(name string, provider Provider) {
if provider == nil {
panic("session: Register provider is nil")
}
if _, dup := provides[name]; dup {
panic("session: Register called twice for provider " + name)
}
provides[name] = provider
}
```
### Identificacores de sesión únicos.
Los identificadores de sesión únics sirven para identificar usuarios en las aplicaciones web, por lo tanto deben ser únicos. El siguiente código muestra como lograr esto:
```
func (manager *Manager) sessionId() string {
b := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(b)
}
```
### Crear una sesión
Necesitamos localizar u obtener una sesión existente en orden de validar las operaciones de usuario. La función `SessionStart` es para verificar la existencia de cualquier sesión relacionada al usuario actual, y crearla si no ninguna sesión es encontrada.
```
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
manager.lock.Lock()
defer manager.lock.Unlock()
cookie, err := r.Cookie(manager.cookieName)
if err != nil || cookie.Value == "" {
sid := manager.sessionId()
session, _ = manager.provider.SessionInit(sid)
cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)}
http.SetCookie(w, &cookie)
} else {
sid, _ := url.QueryUnescape(cookie.Value)
session, _ = manager.provider.SessionRead(sid)
}
return
}
```
Aquí hay un ejemplo que usa la sesión de usuario para el ingreso.
```
func login(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
r.ParseForm()
if r.Method == "GET" {
t, _ := template.ParseFiles("login.gtpl")
w.Header().Set("Content-Type", "text/html")
t.Execute(w, sess.Get("username"))
} else {
sess.Set("username", r.Form["username"])
http.Redirect(w, r, "/", 302)
}
}
```
### Operaciones con valores: definir, obtener y eliminar.
La función `SessionStart` retorna una variable que implementa la interfaz de sesión. ¿Cómo la usamos?
Viste un `session.Get("uid")` en el ejemplo anterior para una operación básica. Ahora examinémolo en mejor detalle.
```
func count(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
createtime := sess.Get("createtime")
if createtime == nil {
sess.Set("createtime", time.Now().Unix())
} else if (createtime.(int64) + 360) < (time.Now().Unix()) {
globalSessions.SessionDestroy(w, r)
sess = globalSessions.SessionStart(w, r)
}
ct := sess.Get("countnum")
if ct == nil {
sess.Set("countnum", 1)
} else {
sess.Set("countnum", (ct.(int) + 1))
}
t, _ := template.ParseFiles("count.gtpl")
w.Header().Set("Content-Type", "text/html")
t.Execute(w, sess.Get("countnum"))
}
```
Como puedes ver, operar con sesiones simplemente es usar un patrón llave/valor en las operaciones de definición, obtención y eliminación.
Porque las sesiones tiene el concepto de tiempo de expiración definimos un Recolector de Basura para actualizar el último tiempo de modificación. De esta manera, el recolector de basura no eliminará sesiones que todavía están siendo usados.
### Definir sesiones.
Sabemos que las aplicaciones web tienen una operación de cerrar sesión. Cuando los usuarios cierran sesión, necesitamos eliminar la sesión correspondiente. Ya hemos usado la operación de reset en el ejemplo anterior, ahora vamos a mirar el cuerpo de la función.
```
//Destroy sessionid
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){
cookie, err := r.Cookie(manager.cookieName)
if err != nil || cookie.Value == "" {
return
} else {
manager.lock.Lock()
defer manager.lock.Unlock()
manager.provider.SessionDestroy(cookie.Value)
expiration := time.Now()
cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
http.SetCookie(w, &cookie)
}
}
```
### Eliminar sesiones
Vamos a ver como dejar al manejador de sesiones eliminar una sesión. Necesitamos iniciar el recolector de baseura en la función `main()`:
```
func init() {
go globalSessions.GC()
}
func (manager *Manager) GC() {
manager.lock.Lock()
defer manager.lock.Unlock()
manager.provider.SessionGC(manager.maxlifetime)
time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() })
}
```
Como podemos ver el recolector de basura hace un uso completo del paquete `time`. Automáticamente llama al recolector de basura cuando la sesión se termina, asegurando que todas las sesiones se puedan usar durante `maxLifeTime`. Una solución similar puede usarse para contar los usuarios activos.
## Resumen
Hemos implementado un manejador de sesión global para una aplicación web y definido la interfaz `Provider` como implementación de una `Session`. En la siguiente sección vamos a hablar sobre como implementar un `Provider` para una estructura de almacenamiento de sesiones, que podrás referencia en el futuro.
## Enlaces
- [Índice](preface.md)
- Sección anterior: [Sesiones y cookies](06.1.md)
- Siguiente sección: [Almacenamiento de sesiones](06.3.md)

139
es/06.3.md Normal file
View File

@@ -0,0 +1,139 @@
# 6.3 Almacenamiento de sesiones
Introducimos los principios de un manejador de sesiones simple en la sección anterior, y entre otras cosas, definimos una interfaz de almacenamiento de sesión. En esta sección, voy a mostrarte un ejemplo de un motor de sesiones basado en memoria que implementa esa interfaz. Puedes mejorar este a otros mecanísmos de almacenamiento también.
```
package memory
import (
"container/list"
"github.com/astaxie/session"
"sync"
"time"
)
var pder = &Provider{list: list.New()}
type SessionStore struct {
sid string // identificador único de sesión
timeAccessed time.Time // último tiempo de acceso
value map[interface{}]interface{} // valor de la sesión accedida
}
func (st *SessionStore) Set(key, value interface{}) error {
st.value[key] = value
pder.SessionUpdate(st.sid)
return nil
}
func (st *SessionStore) Get(key interface{}) interface{} {
pder.SessionUpdate(st.sid)
if v, ok := st.value[key]; ok {
return v
} else {
return nil
}
return nil
}
func (st *SessionStore) Delete(key interface{}) error {
delete(st.value, key)
pder.SessionUpdate(st.sid)
return nil
}
func (st *SessionStore) SessionID() string {
return st.sid
}
type Provider struct {
lock sync.Mutex // bloqueo
sessions map[string]*list.Element // guardar en memoria
list *list.List // recikector de basura
}
func (pder *Provider) SessionInit(sid string) (session.Session, error) {
pder.lock.Lock()
defer pder.lock.Unlock()
v := make(map[interface{}]interface{}, 0)
newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v}
element := pder.list.PushBack(newsess)
pder.sessions[sid] = element
return newsess, nil
}
func (pder *Provider) SessionRead(sid string) (session.Session, error) {
if element, ok := pder.sessions[sid]; ok {
return element.Value.(*SessionStore), nil
} else {
sess, err := pder.SessionInit(sid)
return sess, err
}
return nil, nil
}
func (pder *Provider) SessionDestroy(sid string) error {
if element, ok := pder.sessions[sid]; ok {
delete(pder.sessions, sid)
pder.list.Remove(element)
return nil
}
return nil
}
func (pder *Provider) SessionGC(maxlifetime int64) {
pder.lock.Lock()
defer pder.lock.Unlock()
for {
element := pder.list.Back()
if element == nil {
break
}
if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() {
pder.list.Remove(element)
delete(pder.sessions, element.Value.(*SessionStore).sid)
} else {
break
}
}
}
func (pder *Provider) SessionUpdate(sid string) error {
pder.lock.Lock()
defer pder.lock.Unlock()
if element, ok := pder.sessions[sid]; ok {
element.Value.(*SessionStore).timeAccessed = time.Now()
pder.list.MoveToFront(element)
return nil
}
return nil
}
func init() {
pder.sessions = make(map[string]*list.Element, 0)
session.Register("memory", pder)
}
```
El ejmplo superior implementa un mecanismo de almacenamiento de sesiones en memoria. Usa la función `init()` para registrar este motor de almacenamiento al manejador de sesiones. ¿Cómo registramos este motor en nuestro programa principal?
```
import (
"github.com/astaxie/session"
_ "github.com/astaxie/session/providers/memory"
)
```
Usamos el mecanismo de importación del guión bajo (que invoca a la funcuón `init` del paquete automáticamente) para registrar este motor al manejador de sesiones. Luego usamos el siguiente código para inicializar el manejador de sesión.
```
var globalSessions *session.Manager
// initialize in init() function
func init() {
globalSessions, _ = session.NewManager("memory", "gosessionid", 3600)
go globalSessions.GC()
}
```
## Enlaces
- [Índice](preface.md)
- Sección previa: [Cómo usar sesiones en Go](06.2.md)
- Siguiente sección: [Prevenir el hijacking de sesión](06.4.md)

92
es/06.4.md Normal file
View File

@@ -0,0 +1,92 @@
# 6.4 Prevenir el hijacking de sesión
El hijacking de sesión es una amenaza conocida y seria de seguridad. Los clientes usan el identificador de sesión para validarse y otros propósitos cuando se comunican con el servidor. Desafortunadamente, paquetes deterceros pueden capturar estas comunicaciones y encontrar el identificador de sesión del cliente.
En esta sección vamos a mostrar como hijackear una sesión con propósitos educacionales.
## El proceso de hijacking de sesión
El siguiente código es un contador para la variable `count`:
```
func count(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
ct := sess.Get("countnum")
if ct == nil {
sess.Set("countnum", 1)
} else {
sess.Set("countnum", (ct.(int) + 1))
}
t, _ := template.ParseFiles("count.gtpl")
w.Header().Set("Content-Type", "text/html")
t.Execute(w, sess.Get("countnum"))
}
```
El contenido de `count.gtpl` es el siguiente:
```
Hi. Now count:{{.}}
```
Podemos ver el siguiente contenido en el navegador:
![](images/6.4.hijack.png?raw=true)
Figur 6.4 conteo en el navegador.
Manté refrescando hasta que el número sea 6, entonces abre el manejador de cookies (Uso chrome aquí). Podrás ver la siguiente información:
![](images/6.4.cookie.png?raw=true)
Figura 6.5 cookies guardadas en el navegador.
Este es un paso muy importante: abre otro navegador (Uso firefox aquí), copia la URL en el nuevo navegador, abre el simulador de cookies para crear una nueva cookie y escribe exactamente el mismo valor que la cookie que vimos en el primer navegador.
![](images/6.4.setcookie.png?raw=true)
Figura 6.6 Simular una cookie.
Refresca la página y verás lo siguiente:
![](images/6.4.hijacksuccess.png?raw=true)
Figura 6.7 hijacking de la sesión exitoso.
Aquí podemos que ver que podemos hijackear las sesiones entre diferentes navegadores y las acciones relizadas en uno pueden afectar el estado de la página del otro navegador. Porque HTTP es sin estado, no hay manera de saber si el identificador de sesión de firefox fue simulado, y chrome tampoco puede saber que su sesión fue hijackeada.
## prevenir el hijacking de sesión
### cookie solamente y token
A través de este simple ejemplo de hijacking de sesión, puedes ver que es muy peligroso porque permise a los atacantes hacer lo que quieran. ¿Cómo podemos prevenir el hijacking de sesión?
El primer paso es únicamente definir el identificador en las cookies, en vez de las URLs. También asignr la propiedad httponly cookie a verdadero. Esto restringe a los scripts del lado del cliente ganen acceso al identificar de sesión. Usando estas técnicas las cookie sno pueden ser accesadas por XSS y no será tan fácil como se demostró robar un identificador de sesión del manejador de cookies.
El segundo paso es agregar un token a cada petición. Similar a la manera en la que lidiamos con la repetición de subida de formularios en una sección anterior. Agregamos un campo oculto que contenga un token. Cuando una petición es enviada al servidor, podemos verificar el token para saber si es único.
```
h := md5.New()
salt:="astaxie%^7&8888"
io.WriteString(h,salt+time.Now().String())
token:=fmt.Sprintf("%x",h.Sum(nil))
if r.Form["token"]!=token{
// ask to log in
}
sess.Set("token",token)
```
### Tiempo de agotamiento del identificador de sesión
Otra solución es crear un tiempo para cada sesión, y reemplazar los identificadors de sesión con algunos nuevos. Esto puede prevenir las sesiones de ser hijackeadas bajo ciertas circunstancias, como cuando el hijacking se hace tarde.
```
createtime := sess.Get("createtime")
if createtime == nil {
sess.Set("createtime", time.Now().Unix())
} else if (createtime.(int64) + 60) < (time.Now().Unix()) {
globalSessions.SessionDestroy(w, r)
sess = globalSessions.SessionStart(w, r)
}
```
Cuando definimos el valor a guardar el tiempo de creación, si no ha expirado (60 segundos aquí). Este paso puede muy a menudo fristrar intentos de ataques de hijacking.
Al combinar las dos técnias de arriba podemos ser capaces de prevenir la mayoría de ataques de hijacking de que sean exitosos. Por otra parte, los identificadores de sesión que son reestablecidos frecuentemente resultan en que el atacante siempre obtiene identificadores de sesión expirados. También definiendo la configuración httponly en cookies y asegurarnos que la sesión solo pueda ser pasada por medio de cookies, todos los ataques por URL pueden ser mitigados. Finalmente definimos `MaxAge=0` en nuestras cookies, lo que significa que el identificador de sesión no será almacenado en el historial del navegador.
## Enlaces
- [Índice](preface.md)
- Sección previa: [Almacenamiento de sesiones](06.3.md)
- Siguiente sección: [Resumen](06.5.md)

9
es/06.5.md Normal file
View File

@@ -0,0 +1,9 @@
# 6.5 Resumen
En este capítulo, aprendimos la definición y propósito de las sesiones y la cookies y la relación entre ellas dos. Desde que Go no soporta las sesiones en su librería estándar, nosotros también diseñamos un manejador de sesión. Pasamos através de crear un cliente de sesiones hasta la eliminación. También definimos una interfaz llamada `Provider` que soporta todas las estructuras de almacenamiento de sesiones. En la sección 6.3 implementamos un manejador de sesiones para hacer persistir la información de usuario entre secciones. En la sección 6.4 mostramos una manera de hijackear una sesión. Luego miramos algunas herramientas para prevenir las sesiones de este ataque. Espero que ahora entiendas mas de los principio básicos sobre las sesiones y saber que las puedes usar con seguridad en tus aplicaciones.
## Enlaces
- [Índice](preface.md)
- Sección previa: [Prevenir el hijacking de sesión](06.4.md)
- Siguiente capítulo: [Archivos de texto](07.0.md)