Ways to write authentication modules for Nginx servers using LUA

Source: Internet
Author: User
Tags auth constant header hmac lua sprintf valid nginx server

This article mainly introduces the method of using LUA to write the authentication module of the Nginx server, such as the popular social application access and other functions, the need of friends can refer to the

Over the past two days, I have solved a very interesting problem. I use a nginx server as a proxy, you need to be able to add a certification layer to enable it to use external authentication sources (such as a Web application) to authenticate, if the user has an account in the external authentication source, you can pass the certification agent.

Demand List

I have considered several solutions, listed below:

Use a simple Python/flask module to do the proxy and verify.

A nginx module that uses subrequests for verification (Nginx can do this now)

Writing a Nginxren authentication module with LUA

Obviously, adding additional requests to the entire system will not do very well, as it will increase latency (especially if adding a request to every paging file is annoying). This means that we have excluded the Subrequest module. Python/flash solution seems to support the nginx is not good, so we also exclude it. There is only LUA, of course, nginx to the original biochemical support is good.

Because I don't want to be authenticated on every request on a server that expands, I decided to generate some tokens so that people could save it and present it to the server, and then the server would let the request pass. However, because the LUA module does not have a way of maintaining state (which I have found), we cannot store the cards anywhere. When you don't have more memory, how do you verify what the user is saying?

Solve the problem

The way to encrypt your signature is our Savior! We can use the user's username and expiration time data to add the signed cookies to the user, so it is easy to verify who each user is, and we do not need to have a token.

In Nginx, all we have to do is to configure Access_by_lua_file/our/file.lua directly at the specified location so that the specified location will protect our script. Now, let's write the code together:

The code is as follows:

--Some variable declarations.

Local cookie = Ngx.var.cookie_MyToken

Local HMAC = ""

Local timestamp = ""

Local Timestamp_time = 0

--Check that cookie exists.

if cookie = = Nil or Cookie:find (":") = = Nil Then

--internally rewrite the URL so we serve

--/auth/if there ' s no cookie.

Ngx.exec ("/auth/")

Else

--If There ' s a cookie, split off the HMAC signature

--and timestamp.

Local divider = Cookie:find (":")

HMAC = Cookie:sub (divider+1)

timestamp = cookie:sub (0, Divider-1)

End

--Verify that's the signature is valid.

If HMAC_SHA1 ("some very secret string", timestamp) ~= HMAC or Tonumber (timestamp) < Os.time () then

--If invalid, send To/auth/again.

Ngx.exec ("/auth/")

End

The code above can be run directly. We use some clear text to sign (in this case is a timestamp, of course you can use whatever you want), then we use Milvensen into HMAC (hash information authentication code), and then a signature is generated, so that users can not tamper with invalid information.

When the user tries to load a resource, we check that the signature in the cookie is valid and, if so, pass his request. Instead, we redirect them to a password-issuing server that verifies and gives them a signed password in case they do not.

Sharp you may find that the above code has a time leak. If you don't find it, don't be sad. Well, it might be a little sad.

Here's a LUA code that compares the equivalent of two strings at a constant time (and thus can stop any attack at any time unless I ignore something that is most likely):

Copy code code as follows:

function Compare_strings (str1, STR2)

--Constant-time string comparison function.

Local same = True

For i = 1, #str1 do

--If the two strings ' lengths are different, sub ()

---would just return nil for the remaining length.

C1 = Str1:sub (i,i)

C2 = str2:sub (i,i)

If C1 ~= C2 Then

Same = False

End

End

Return same

End

I've applied the time on the function to differentiate, as I know, this is an equivalent string at constant time. Strings of different lengths change slightly in time, perhaps because the Sub procedure Sub applies a different branch. Moreover, the C1~=C2 branch is obviously not a constant time, but in practice it is fairly close to constant, so our example will not have an impact. I prefer to use an XOR operation to determine whether the XOR result of two strings is 0, but Lua does not seem to include bits XOR operations. If I am wrong in this judgment, I am grateful for any correction.

Password Publishing Server

Now, we've written some great password-checking code, all we need to do is write a server to actually issue these passwords. I could have used Python and flask to write this server, but I still wanted to try it with go because I was a computer language boomer and the go looked "cool". It might be quicker to use python, but I'm happy to use go.

This server will pop up an HTTP Basic authentication form, check the account you entered, and if it is correct, it will give you a signed password that is suitable for one hours of proxy server access. In this way, you only need to verify the external service once, and subsequent authentication checks will be at the nginx level, and will be fairly fast.

Request processor

Writing a processor to eject a basic validation form is not difficult, but go has no perfect documentation, so I have to hunt myself a little. In fact, it's very simple, and finally, here is the HTTP Basic authentication Go code:

The code is as follows:

Func Handler (w http. Responsewriter, R *http. Request) {

If username: = Checkauth (R); Username = = "" {

W.header (). Set ("Www-authenticate", "Basic realm=" the Kingdom of Stavros "")

W.writeheader (401)

W.write ([]byte ("401 Unauthorizedn"))

} else {

Fmt. Printf ("Authenticated user%V.N", username)

Token: = GetToken ()

Settokencookie (W, token)

Fmt. fprintf (W, "")

}

}

Setting passwords and Cookies

Once we have verified a user, we need to set a cookie for their password. We just need to do the same thing we did with Lua, as above, just simpler, because go has a real encryption package inside the standard library. This code is as straightforward as it is, even if it is not fully documented:

The code is as follows:

Func GetToken () string {

Expiration: = Int (time. Now (). Unix ()) + 3600

Mac: = HMAC. New (SHA1. New, []byte ("some very secret string")

Mac. Write ([]byte (FMT). Sprintf ("%v", expiration))

Expectedmac: = Fmt. Sprintf ("%x", Mac.) Sum (nil))

Return FMT. Sprintf ("%v:%s", expiration, Expectedmac)

}

Func Settokencookie (w http. Responsewriter, token string) {

Rawcookie: = Fmt. Sprintf ("mytoken=%s", token)

Expire: = time. Now (). ADD (time. Hour)

Cookie: = http. cookie{"MyToken",

Token

"/",

". example.com",

Expire

Expire. Format (time. Unixdate),

3600,

False

True

Rawcookie,

[]string{rawcookie}}

http. Setcookie (W, &cookie)

}

Try to put them together

To complete this great segment of our portfolio, we only need a function to check the authentication information provided by the user, and we did it! Here is the code I draw from some libraries, currently it just checks a specific username/password combination, So integration with third party services is a job for the reader:

The code is as follows:

Func Checkauth (R *http. Request) String {

S: = Strings. SPLITN (R.header.get ("Authorization"), "", 2)

If Len (s)!= 2 | | S[0]!= "Basic" {

Return ""

}

B, err: = base64. Stdencoding.decodestring (S[1])

If Err!= nil {

Return ""

}

Pair: = strings. SPLITN (string (b), ":", 2)

If Len (pair)!= 2 {

Return ""

}

If pair[0]!= "username" | | PAIR[1]!= "password" {

Return ""

}

return pair[0]

}

Conclusion

I'm still quite fond of Nginx's LUA modules. It allows you to do some simple work on the Web server's request/response cycle, and it makes sense for some operations, such as checking the proxy server for validation. These things have been difficult for a Web server that is not programmable, so it is very likely that we will need to write our own HTTP proxy service.

The code above is fairly short and elegant, so I'm happy with everything above. I'm not sure how much extra time it adds to the response, but it's good to do a validation and I think it's worth it (and it should be fast enough, so it's not a problem).

Another advantage is that you can only open it with a single directive in Nginxlocationblock, so there is no configuration item to keep track of. I found that, overall, it was a very elegant solution, and I'm happy to learn that nginx can make me do something like this, maybe I need to do it in the future.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.