-
Notifications
You must be signed in to change notification settings - Fork 7
/
websspi_windows.go
685 lines (620 loc) · 20.9 KB
/
websspi_windows.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
package websspi
import (
"context"
"encoding/base64"
"encoding/gob"
"errors"
"fmt"
"log"
"net/http"
"os/user"
"reflect"
"strings"
"sync"
"syscall"
"time"
"unsafe"
"github.com/quasoft/websspi/secctx"
)
// The Config object determines the behaviour of the Authenticator.
//
// To resolve group membership of authenticated principals, set EnumerateGroups to true.
// Currently there are two options to resolve group membership - both return different results.
// To resolve the "static" local or AD group membership, additionally set "ServerName" to a Windows server or Active Directory.
//
type Config struct {
contextStore secctx.Store
authAPI API
KrbPrincipal string // Name of Kerberos principle used by the service (optional).
AuthUserKey string // Key of header to fill with authenticated username, eg. "X-Authenticated-User" or "REMOTE_USER" (optional).
EnumerateGroups bool // If true, groups the user is a member of are enumerated and stored in request context (default false)
ServerName string // Specifies the DNS or NetBIOS name of the remote server which to query about user groups. Use an empty value to query the groups granted on a real login. Ignored if EnumerateGroups is false.
ResolveLinked bool // Resolve a linked token.
}
// NewConfig creates a configuration object with default values.
func NewConfig() *Config {
return &Config{
contextStore: secctx.NewCookieStore(),
authAPI: &Win32{},
}
}
// Validate makes basic validation of configuration to make sure that important and required fields
// have been set with values in expected format.
func (c *Config) Validate() error {
if c.contextStore == nil {
return errors.New("Store for context handles not specified in Config")
}
if c.authAPI == nil {
return errors.New("Authentication API not specified in Config")
}
return nil
}
// contextKey represents a custom key for values stored in context.Context
type contextKey string
func (c contextKey) String() string {
return "websspi-key-" + string(c)
}
var (
UserInfoKey = contextKey("UserInfo")
)
// The Authenticator type provides middleware methods for authentication of http requests.
// A single authenticator object can be shared by concurrent goroutines.
type Authenticator struct {
Config Config
serverCred *CredHandle
credExpiry *time.Time
ctxList []CtxtHandle
ctxListMux *sync.Mutex
}
// New creates a new Authenticator object with the given configuration options.
func New(config *Config) (*Authenticator, error) {
err := config.Validate()
if err != nil {
return nil, fmt.Errorf("invalid config: %v", err)
}
var auth = &Authenticator{
Config: *config,
ctxListMux: &sync.Mutex{},
}
err = auth.PrepareCredentials(config.KrbPrincipal)
if err != nil {
return nil, fmt.Errorf("could not acquire credentials handle for the service: %v", err)
}
log.Printf("Credential handle expiry: %v\n", *auth.credExpiry)
return auth, nil
}
// PrepareCredentials method acquires a credentials handle for the specified principal
// for use during the live of the application.
// On success stores the handle in the serverCred field and its expiry time in the
// credExpiry field.
// This method must be called once - when the application is starting or when the first
// request from a client is received.
func (a *Authenticator) PrepareCredentials(principal string) error {
var principalPtr *uint16
if principal != "" {
var err error
principalPtr, err = syscall.UTF16PtrFromString(principal)
if err != nil {
return err
}
}
credentialUsePtr, err := syscall.UTF16PtrFromString(NEGOSSP_NAME)
if err != nil {
return err
}
var handle CredHandle
var expiry syscall.Filetime
status := a.Config.authAPI.AcquireCredentialsHandle(
principalPtr,
credentialUsePtr,
SECPKG_CRED_INBOUND,
nil, // logonId
nil, // authData
0, // getKeyFn
0, // getKeyArgument
&handle,
&expiry,
)
if status != SEC_E_OK {
return fmt.Errorf("call to AcquireCredentialsHandle failed with code 0x%x", status)
}
expiryTime := time.Unix(0, expiry.Nanoseconds())
a.credExpiry = &expiryTime
a.serverCred = &handle
return nil
}
// Free method should be called before shutting down the server to let
// it release allocated Win32 resources
func (a *Authenticator) Free() error {
var status SECURITY_STATUS
a.ctxListMux.Lock()
for _, ctx := range a.ctxList {
// TODO: Also check for stale security contexts and delete them periodically
status = a.Config.authAPI.DeleteSecurityContext(&ctx)
if status != SEC_E_OK {
return fmt.Errorf("call to DeleteSecurityContext failed with code 0x%x", status)
}
}
a.ctxList = nil
a.ctxListMux.Unlock()
if a.serverCred != nil {
status = a.Config.authAPI.FreeCredentialsHandle(a.serverCred)
if status != SEC_E_OK {
return fmt.Errorf("call to FreeCredentialsHandle failed with code 0x%x", status)
}
a.serverCred = nil
}
return nil
}
// StoreCtxHandle stores the specified context to the internal list (ctxList)
func (a *Authenticator) StoreCtxHandle(handle *CtxtHandle) {
if handle == nil || *handle == (CtxtHandle{}) {
// Should not add nil or empty handle
return
}
a.ctxListMux.Lock()
defer a.ctxListMux.Unlock()
a.ctxList = append(a.ctxList, *handle)
}
// ReleaseCtxHandle deletes a context handle and removes it from the internal list (ctxList)
func (a *Authenticator) ReleaseCtxHandle(handle *CtxtHandle) error {
if handle == nil || *handle == (CtxtHandle{}) {
// Removing a nil or empty handle is not an error condition
return nil
}
a.ctxListMux.Lock()
defer a.ctxListMux.Unlock()
// First, try to delete the handle
status := a.Config.authAPI.DeleteSecurityContext(handle)
if status != SEC_E_OK {
return fmt.Errorf("call to DeleteSecurityContext failed with code 0x%x", status)
}
// Then remove it from the internal list
foundAt := -1
for i, ctx := range a.ctxList {
if ctx == *handle {
foundAt = i
break
}
}
if foundAt > -1 {
a.ctxList[foundAt] = a.ctxList[len(a.ctxList)-1]
a.ctxList = a.ctxList[:len(a.ctxList)-1]
}
return nil
}
// AcceptOrContinue tries to validate the auth-data token by calling the AcceptSecurityContext
// function and returns and error if validation failed or continuation of the negotiation is needed.
// No error is returned if the token was validated (user was authenticated).
func (a *Authenticator) AcceptOrContinue(context *CtxtHandle, authData []byte) (newCtx *CtxtHandle, out []byte, exp *time.Time, err error) {
if authData == nil {
err = errors.New("input token cannot be nil")
return
}
var inputDesc SecBufferDesc
var inputBuf SecBuffer
inputDesc.BuffersCount = 1
inputDesc.Version = SECBUFFER_VERSION
inputDesc.Buffers = &inputBuf
inputBuf.BufferSize = uint32(len(authData))
inputBuf.BufferType = SECBUFFER_TOKEN
inputBuf.Buffer = &authData[0]
var outputDesc SecBufferDesc
var outputBuf SecBuffer
outputDesc.BuffersCount = 1
outputDesc.Version = SECBUFFER_VERSION
outputDesc.Buffers = &outputBuf
outputBuf.BufferSize = 0
outputBuf.BufferType = SECBUFFER_TOKEN
outputBuf.Buffer = nil
var expiry syscall.Filetime
var contextAttr uint32
var newContextHandle CtxtHandle
var status = a.Config.authAPI.AcceptSecurityContext(
a.serverCred,
context,
&inputDesc,
ASC_REQ_ALLOCATE_MEMORY|ASC_REQ_MUTUAL_AUTH|ASC_REQ_CONFIDENTIALITY|
ASC_REQ_INTEGRITY|ASC_REQ_REPLAY_DETECT|ASC_REQ_SEQUENCE_DETECT, // contextReq uint32,
SECURITY_NATIVE_DREP, // targDataRep uint32,
&newContextHandle,
&outputDesc, // *SecBufferDesc
&contextAttr, // contextAttr *uint32,
&expiry, // *syscall.Filetime
)
if newContextHandle.Lower != 0 || newContextHandle.Upper != 0 {
newCtx = &newContextHandle
}
tm := time.Unix(0, expiry.Nanoseconds())
exp = &tm
if status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED {
// Copy outputBuf.Buffer to out and free the outputBuf.Buffer
out = make([]byte, outputBuf.BufferSize)
var bufPtr = unsafe.Pointer(outputBuf.Buffer)
for i := 0; i < len(out); i++ {
out[i] = *(*byte)(bufPtr)
bufPtr = unsafe.Pointer(uintptr(bufPtr) + 1)
}
}
if outputBuf.Buffer != nil {
freeStatus := a.Config.authAPI.FreeContextBuffer(outputBuf.Buffer)
if freeStatus != SEC_E_OK {
status = freeStatus
err = fmt.Errorf("could not free output buffer; FreeContextBuffer() failed with code: 0x%x", freeStatus)
return
}
}
if status == SEC_I_CONTINUE_NEEDED {
err = errors.New("Negotiation should continue")
return
} else if status != SEC_E_OK {
err = fmt.Errorf("call to AcceptSecurityContext failed with code 0x%x", status)
return
}
// TODO: Check contextAttr?
return
}
// GetCtxHandle retrieves the context handle for this client from request's cookies
func (a *Authenticator) GetCtxHandle(r *http.Request) (*CtxtHandle, error) {
sessionHandle, err := a.Config.contextStore.GetHandle(r)
if err != nil {
return nil, fmt.Errorf("could not get context handle from session: %s", err)
}
if contextHandle, ok := sessionHandle.(*CtxtHandle); ok {
log.Printf("CtxHandle: 0x%x\n", *contextHandle)
if contextHandle.Lower == 0 && contextHandle.Upper == 0 {
return nil, nil
}
return contextHandle, nil
}
log.Printf("CtxHandle: nil\n")
return nil, nil
}
// SetCtxHandle stores the context handle for this client to cookie of response
func (a *Authenticator) SetCtxHandle(r *http.Request, w http.ResponseWriter, newContext *CtxtHandle) error {
// Store can't store nil value, so if newContext is nil, store an empty CtxHandle
ctx := &CtxtHandle{}
if newContext != nil {
ctx = newContext
}
err := a.Config.contextStore.SetHandle(r, w, ctx)
if err != nil {
return fmt.Errorf("could not save context to cookie: %s", err)
}
log.Printf("New context: 0x%x\n", *ctx)
return nil
}
// GetFlags returns the negotiated context flags
func (a *Authenticator) GetFlags(context *CtxtHandle) (uint32, error) {
var flags SecPkgContext_Flags
status := a.Config.authAPI.QueryContextAttributes(context, SECPKG_ATTR_FLAGS, (*byte)(unsafe.Pointer(&flags)))
if status != SEC_E_OK {
return 0, fmt.Errorf("QueryContextAttributes failed with status 0x%x", status)
}
return flags.Flags, nil
}
// GetUsername returns the name of the user associated with the specified security context
func (a *Authenticator) GetUsername(context *CtxtHandle) (username string, err error) {
var names SecPkgContext_Names
status := a.Config.authAPI.QueryContextAttributes(context, SECPKG_ATTR_NAMES, (*byte)(unsafe.Pointer(&names)))
if status != SEC_E_OK {
err = fmt.Errorf("QueryContextAttributes failed with status 0x%x", status)
return
}
if names.UserName != nil {
username = UTF16PtrToString(names.UserName, 2048)
status = a.Config.authAPI.FreeContextBuffer((*byte)(unsafe.Pointer(names.UserName)))
if status != SEC_E_OK {
err = fmt.Errorf("FreeContextBuffer failed with status 0x%x", status)
}
return
}
err = errors.New("QueryContextAttributes returned empty name")
return
}
// GetGroups returns the groups assosiated with the specified security context
func (a *Authenticator) GetGroups(context *CtxtHandle) (groups []string, err error) {
var token SecPkgContext_AccessToken
status := a.Config.authAPI.QueryContextAttributes(context, SECPKG_ATTR_ACCESS_TOKEN, (*byte)(unsafe.Pointer(&token)))
if status != SEC_E_OK {
err = fmt.Errorf("QueryContextAttributes failed with status 0x%x", status)
return
}
var requiredMemory uint32
// 1. Get buffer size
ec := a.Config.authAPI.GetTokenInformation(
syscall.Token(token.AccessToken),
syscall.TokenGroups,
nil, 0, &requiredMemory,
)
if ec != syscall.ERROR_INSUFFICIENT_BUFFER {
err = fmt.Errorf("GetTokenInformation failed with %+v (while getting required memory)", ec)
return
}
tokenInformation := make([]byte, requiredMemory)
// 2. Get data
ec = a.Config.authAPI.GetTokenInformation(
syscall.Token(token.AccessToken),
syscall.TokenGroups,
&tokenInformation[0], uint32(len(tokenInformation)), &requiredMemory,
)
if ec != nil {
err = fmt.Errorf("GetTokenInformation failed with %+v (when looking up group membership)", ec)
return
}
// The struct ends with a variable amount of SIDAndAttributes structs.
var tokens *TokenGroups = (*TokenGroups)(unsafe.Pointer(&tokenInformation[0]))
var allSidAndAttributes []syscall.SIDAndAttributes
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&allSidAndAttributes))
hdr.Data = uintptr(unsafe.Pointer(&tokens.Groups))
hdr.Len = int(tokens.GroupCount)
hdr.Cap = int(tokens.GroupCount)
for _, sidAndAttributes := range allSidAndAttributes {
// SE_GROUP_ENABLED
if sidAndAttributes.Attributes&4 == 4 {
str, _ := sidAndAttributes.Sid.String()
group, err := user.LookupGroupId(str)
if err != nil { // Non-group SIDs - can happen sometimes.
continue
}
groups = append(groups, group.Name)
}
}
return
}
// GetUserGroups returns the groups the user is a member of
func (a *Authenticator) GetUserGroups(userName string) (groups []string, err error) {
var serverNamePtr *uint16
if a.Config.ServerName != "" {
serverNamePtr, err = syscall.UTF16PtrFromString(a.Config.ServerName)
if err != nil {
return
}
}
userNamePtr, err := syscall.UTF16PtrFromString(userName)
if err != nil {
return
}
var buf *byte
var entriesRead uint32
var totalEntries uint32
err = a.Config.authAPI.NetUserGetGroups(
serverNamePtr,
userNamePtr,
0,
&buf,
MAX_PREFERRED_LENGTH,
&entriesRead,
&totalEntries,
)
if buf == nil {
err = fmt.Errorf("NetUserGetGroups(): returned nil buffer, error: %s", err)
return
}
defer func() {
freeErr := a.Config.authAPI.NetApiBufferFree(buf)
if freeErr != nil {
err = freeErr
}
}()
if err != nil {
return
}
if entriesRead < totalEntries {
err = fmt.Errorf("NetUserGetGroups(): could not read all entries, read only %d entries of %d", entriesRead, totalEntries)
return
}
ptr := unsafe.Pointer(buf)
for i := uint32(0); i < entriesRead; i++ {
groupInfo := (*GroupUsersInfo0)(ptr)
groupName := UTF16PtrToString(groupInfo.Grui0_name, MAX_GROUP_NAME_LENGTH)
if groupName != "" {
groups = append(groups, groupName)
}
ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(GroupUsersInfo0{}))
}
return
}
// GetUserInfo returns a structure containing the name of the user associated with the
// specified security context and the groups to which they are a member of (if Config.EnumerateGroups)
// is enabled
func (a *Authenticator) GetUserInfo(context *CtxtHandle) (*UserInfo, error) {
// Get username
username, err := a.GetUsername(context)
if err != nil {
return nil, err
}
info := UserInfo{
Username: username,
}
// Get groups
if a.Config.EnumerateGroups {
if a.Config.ServerName != "" {
info.Groups, err = a.GetUserGroups(username)
} else {
info.Groups, err = a.GetGroups(context)
}
if err != nil {
return nil, err
}
}
return &info, nil
}
// GetAuthData parses the "Authorization" header received from the client,
// extracts the auth-data token (input token) and decodes it to []byte
func (a *Authenticator) GetAuthData(r *http.Request, w http.ResponseWriter) (authData []byte, err error) {
// 1. Check if Authorization header is present
headers := r.Header["Authorization"]
if len(headers) == 0 {
err = errors.New("the Authorization header is not provided")
return
}
if len(headers) > 1 {
err = errors.New("received multiple Authorization headers, but expected only one")
return
}
authzHeader := strings.TrimSpace(headers[0])
if authzHeader == "" {
err = errors.New("the Authorization header is empty")
return
}
// 1.1. Make sure header starts with "Negotiate"
if !strings.HasPrefix(strings.ToLower(authzHeader), "negotiate") {
err = errors.New("the Authorization header does not start with 'Negotiate'")
return
}
// 2. Extract token from Authorization header
authzParts := strings.Split(authzHeader, " ")
if len(authzParts) < 2 {
err = errors.New("the Authorization header does not contain token (gssapi-data)")
return
}
token := authzParts[len(authzParts)-1]
if token == "" {
err = errors.New("the token (gssapi-data) in the Authorization header is empty")
return
}
// 3. Decode token
authData, err = base64.StdEncoding.DecodeString(token)
if err != nil {
err = errors.New("could not decode token as base64 string")
return
}
return
}
// Authenticate tries to authenticate the HTTP request and returns nil
// if authentication was successful.
// Returns error and data for continuation if authentication was not successful.
func (a *Authenticator) Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *UserInfo, outToken string, err error) {
// 1. Extract auth-data from Authorization header
authData, err := a.GetAuthData(r, w)
if err != nil {
err = fmt.Errorf("could not get auth data: %s", err)
return
}
// 2. Authenticate user with provided token
contextHandle, err := a.GetCtxHandle(r)
if err != nil {
return
}
newCtx, output, _, err := a.AcceptOrContinue(contextHandle, authData)
// If a new context was created, make sure to delete it or store it
// both in internal list and response Cookie
defer func() {
// Negotiation is ending if we don't expect further responses from the client
// (authentication was successful or no output token is going to be sent back),
// clear client cookie
endOfNegotiation := err == nil || len(output) == 0
// Current context (contextHandle) is not needed anymore and should be deleted if:
// - we don't expect further responses from the client
// - a new context has been returned by AcceptSecurityContext
currCtxNotNeeded := endOfNegotiation || newCtx != nil
if !currCtxNotNeeded {
// Release current context only if its different than the new context
if contextHandle != nil && *contextHandle != *newCtx {
remErr := a.ReleaseCtxHandle(contextHandle)
if remErr != nil {
err = remErr
return
}
}
}
if endOfNegotiation {
// Clear client cookie
setErr := a.SetCtxHandle(r, w, nil)
if setErr != nil {
err = fmt.Errorf("could not clear context, error: %s", setErr)
return
}
// Delete any new context handle
remErr := a.ReleaseCtxHandle(newCtx)
if remErr != nil {
err = remErr
return
}
// Exit defer func
return
}
if newCtx != nil {
// Store new context handle to internal list and response Cookie
a.StoreCtxHandle(newCtx)
setErr := a.SetCtxHandle(r, w, newCtx)
if setErr != nil {
err = setErr
return
}
}
}()
outToken = base64.StdEncoding.EncodeToString(output)
if err != nil {
err = fmt.Errorf("AcceptOrContinue failed: %s", err)
return
}
// 3. Get username and user groups
currentCtx := newCtx
if currentCtx == nil {
currentCtx = contextHandle
}
userInfo, err = a.GetUserInfo(currentCtx)
if err != nil {
err = fmt.Errorf("could not get username, error: %s", err)
return
}
return
}
// AppendAuthenticateHeader populates WWW-Authenticate header,
// indicating to client that authentication is required and returns a 401 (Unauthorized)
// response code.
// The data parameter can be empty for the first 401 response from the server.
// For subsequent 401 responses the data parameter should contain the gssapi-data,
// which is required for continuation of the negotiation.
func (a *Authenticator) AppendAuthenticateHeader(w http.ResponseWriter, data string) {
value := "Negotiate"
if data != "" {
value += " " + data
}
w.Header().Set("WWW-Authenticate", value)
}
// Return401 populates WWW-Authenticate header, indicating to client that authentication
// is required and returns a 401 (Unauthorized) response code.
// The data parameter can be empty for the first 401 response from the server.
// For subsequent 401 responses the data parameter should contain the gssapi-data,
// which is required for continuation of the negotiation.
func (a *Authenticator) Return401(w http.ResponseWriter, data string) {
a.AppendAuthenticateHeader(w, data)
http.Error(w, "Error!", http.StatusUnauthorized)
}
// WithAuth authenticates the request. On successful authentication the request
// is passed down to the next http handler. The next handler can access information
// about the authenticated user via the GetUserName method.
// If authentication was not successful, the server returns 401 response code with
// a WWW-Authenticate, indicating that authentication is required.
func (a *Authenticator) WithAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Authenticating request to %s\n", r.RequestURI)
user, data, err := a.Authenticate(r, w)
if err != nil {
log.Printf("Authentication failed with error: %v\n", err)
a.Return401(w, data)
return
}
log.Print("Authenticated\n")
// Add the UserInfo value to the reqest's context
r = r.WithContext(context.WithValue(r.Context(), UserInfoKey, user))
// and to the request header with key Config.AuthUserKey
if a.Config.AuthUserKey != "" {
r.Header.Set(a.Config.AuthUserKey, user.Username)
}
// The WWW-Authenticate header might need to be sent back even
// on successful authentication (eg. in order to let the client complete
// mutual authentication).
if data != "" {
a.AppendAuthenticateHeader(w, data)
}
next.ServeHTTP(w, r)
})
}
func init() {
gob.Register(&CtxtHandle{})
gob.Register(&UserInfo{})
}