MicroServices in Golang-part 4th-Certification with JWT

Source: Internet
Author: User
Tags key string md5 hash docker run asymmetric encryption
This is a creation in Article, where the information may have evolved or changed. In the previous article, we created a user service that saved some users. Now we look at how to safely save the user password in the user service, and write several functions through the microservices to authenticate the user and release the security token. Note that I have now split our service into several different warehouses. I think it's easier to deploy. At first I was going to make a separate warehouse, but I found it was a bit of a hassle and a lot of conflict with Go DEP management. I will also explain how to run and test the microservices independently. Unfortunately, we can't use docker-compose in this way. But it's good to use right now. If you have any suggestions in this area, you can [email me] (ewan.valentine89@gmail.com)! Now you want to start the database manually: ' $ docker run-d-P 5432:5432 postgres$ Docker run-d-P 27017:27017 Mongo ' ' The latest warehouse address is below:-[https://github.co M/ewanvalentine/shippy-consignment-service] (https://github.com/EwanValentine/shippy-consignment-service)-[ Https://github.com/EwanValentine/shippy-user-service] (https://github.com/EwanValentine/shippy-user-service)-[ Https://github.com/EwanValentine/shippy-vessel-service] (https://github.com/EwanValentine/shippy-vessel-service )-[HTTPS://GITHUB.COM/EWANVALENTINE/SHIPPY-USER-CLI] (HTTPS://GITHUB.COM/EWANVALENTINE/SHIPPY-USER-CLI)-[HTTPS ://github.com/ewanvalentine/shippy-consignment-cli] (HTTPS://GITHUB.COM/EWANVALENTINE/SHIPPY-CONSIGNMENT-CLI) First , we want to update the next handler file, do the password haH, this is very necessary. Absolutely not, also resolutely do not use plaintext password. You might think, ' nonsense, that's what to say, ' but I'm going to stick with that! "' go//shippy-user-service/handler.go ... func (SRV *service) Auth (CTX context. Context, req *PB. User, Res *PB. Token) Error {log. Println ("Logging in with:", req. Email, req. Password) User, err: = Srv.repo.GetByEmail (req. Email) log. PRINTLN (user) if err! = Nil {return err}//compares our given password against the hashed password//stored in the database If err: = Bcrypt.comparehashandpassword ([]byte (user. Password), []byte (req. Password)); Err! = Nil {return Err}token, err: = Srv.tokenService.Encode (user) if err! = Nil {return err}res. Token = Tokenreturn nil}func (SRV *service) Create (CTX context. Context, req *PB. User, Res *PB. Response) Error {//generates a hashed version of our passwordhashedpass, err: = Bcrypt. Generatefrompassword ([]byte (req. Password), Bcrypt. Defaultcost) If err! = Nil {return err}req. Password = string (hashedpass) If err: = Srv.repo.Create (req); Err! = Nil {return err}res. User = Reqreturn Nil} "Not much has been changed here, in addition to the password hashing feature, we save the newThe user has previously hashed the content as the new password. Similarly, in the authentication section, we will verify the hash value of the password. Now that we are able to securely authenticate the user information in the database, we need a mechanism to authenticate using interfaces and distributed services. There are many ways to implement such a feature, but I have found that the simplest authentication method that can be used through the service and the web is with [JWT] (https://jwt.io/). But before we go on, check out the changes I made to Dockfiles and makefiles in each of the services. To match the latest git repositories, I have also modified the imports. # # JWT[JWT] (https://jwt.io/) is the abbreviation for the JSON Web tokens and is a distributed security protocol. Similar to OAuth. The concept is simple, using algorithms to generate a unique hash for the user, which can be used to compare and verify. Not only that, token itself also contains the user's metadata (metadata). In other words, the user data itself can also be part of the token. Let's look at an example of a JWT: "' EyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBa B30rmhrhdcefxjoyzgefonfh7hgq ' tokens are divided into three parts. Each part has its own meaning. The first part is the token's own metadata, like the token type, the creation of tokens using the algorithm and so on. Allow the client to know how to decode tokens. The second part is the user-defined meta-data. It can be your user details, expiration time, whatever you want to fill in the content. The final part is the authentication signature, including what method to use, what data is used to hash this information to token. Of course, the use of JWT is also flawed and risky, and these shortcomings are well summarized in [this article] (http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/). At the same time, I recommend that you read [this article] (Https://www.owasp.org/index.php/JSON_Web_Token_ (JWT) _cheat_sheet_for_java), here are the best practices on the security plane. One thing I want people to pay special attention to is to get the user's source IP, and use it as part of a token declaration. This ensures that no one can steal your tokens and pretend to be you on another machine. Make sure to use HTTPS to reduce this type of attack, because using HTTPS can protect your tokens from man-in-the-middle attacks. There are many algorithms used to make JWT hashes, broadly divided into two categories. Symmetric and asymmetric encryption. Symmetric encryption is similar to the way we are now using a shared salt value (salt). Asymmetric encryption uses the public and private keys on both the client and the service side. It's great to be used to authenticate between multiple services. Additional resources:-[Auth0] (https://auth0.com/blog/json-web-token-signing-algorithms-overview/)-[RFC spec for algorithms] (HTTPS ://tools.ietf.org/html/rfc7518#section-3) Now we know the basic concept of JWT. We updated the ' token_service.go ' to implement these operations. We can use this Go library ' github.com/dgrijalva/jwt-go ', this library is very good to write, there are a lot of good examples. "' go//shippy-user-service/token_service.gopackage mainimport (" Time "PB" github.com/ewanvalentine/ Shippy-user-service/proto/user "" Github.com/dgrijalva/jwt-go ") var (//Define a secure key string used//as a salt when hash ing our tokens.//-make your own-in-a-than this,//use a randomly generated MD5 hash or something.key = [ ]byte ("Mysupersecretkeylol"))//Customclaims are our custom metadata, which'll be hashed//and sent as the second segment In our Jwttype customclaims struct {User *pb. Userjwt.standardclaIms}type authable Interface {Decode (token string) (*customclaims, error) Encode (user *pb. User) (string, error)}type tokenservice struct {repo repository}//Decode a token string into a token objectfunc (SRV *tok Enservice) Decode (tokenstring string) (*customclaims, error) {//Parse the Tokentoken, err: = JWT. Parsewithclaims (tokenstring, &customclaims{}, func (token *JWT. token) (interface{}, error) {return key, nil})//Validate the token and return the custom claimsif claims, OK: = token. Claims. (*customclaims); OK && token. Valid {return claims, nil} else {return nil, err}}//Encode a claim into a jwtfunc (SRV *tokenservice) Encode (user *pb. User) (string, error) {expiretoken: = time. Now (). ADD (time. Hour * 72). Unix ()//Create the Claimsclaims: = CUSTOMCLAIMS{USER,JWT. Standardclaims{expiresat:expiretoken,issuer: "Go.micro.srv.user",},}//Create tokentoken: = JWT. Newwithclaims (JWT. SIGNINGMETHODHS256, claims)//sign token and Returnreturn token. Signedstring (Key)} "" As usual, I wrote some notes explaining some of the details, but the introduction here is relatively simple to write. The Decode method is to parse a string token into a token object, verifying that, if valid, a Customclaims object is returned. This allows us to obtain the user's metadata through this Customclaims object to verify that the user is valid. The Encode method, doing exactly the opposite, will hash your custom meta data into a new JWT and return it. Note that we have set a ' key ' variable above, which is a safe salt value, in a production environment, use a more secure salt value. Now we have a service that validates tokens. We update the next user-cli, I have now simplified this part into a script, because this previous CLI part of the code a bit of a problem, I will later to say this problem, this tool is to test with: "" go//shippy-user-cli/cli.gopackage Mainimport ("Log" "OS" PB "Github.com/ewanvalentine/shippy-user-service/proto/user" Micro "Github.com/micro/go-micro "Microclient" github.com/micro/go-micro/client "" Golang.org/x/net/context ") func main () {srv: = Micro. NewService (micro. Name ("Go.micro.srv.user-cli"), Micro. Version ("latest"),//Init would parse the command line Flags.srv.Init () Client: = PB. Newuserserviceclient ("Go.micro.srv.user", microclient. defaultclient) Name: = "Ewan Valentine" Email: = "ewan.valentine89@gmail.com" Password: = "Test123" Company: = "BBC" r, Err: = Client. Create (context. TODO (), &AMP;PB. User{name:name,email:email,password:password,company:companY,}) if err! = Nil {log. Fatalf ("Could not create:%v", err)}log. Printf ("Created:%s", r.user.id) getAll, err: = client. GetAll (context. Background (), &AMP;PB. request{}) If err! = Nil {log. Fatalf ("Could not list users:%v", err)}for _, V: = range getall.users {log. Println (v)}authresponse, err: = client. Auth (context. TODO (), &AMP;PB. User{email:email,password:password,}) if err! = Nil {log. Fatalf ("Could not authenticate User:%s Error:%v\n", email, err)}log. Printf ("Your access token is:%s \ n", Authresponse.token)//Let's just exit Becauseos. Exit (0)} "Now we have some hard-coded values, replace these values, and execute the script with ' $make build && make run '. You can see the return of a token. Copy this long token value and you'll use it right away! Now we update our CONSIGNMENT-CLI to generate a token string, passed to Consignment-service: "' go//shippy-consignment-cli/cli.go...func Main () {cmd. Init ()//Create new greeter Clientclient: = PB. Newshippingserviceclient ("Go.micro.srv.consignment", microclient. defaultclient)//Contact the server and print out its response.file: = Defaultfilenamevar token stringlog. PrIntln (OS. Args) If Len (OS. Args) < 3 {log. Fatal (Errors. New ("Not enough arguments, expecing file and token.")} FILE = os. Args[1]token = OS. Args[2]consignment, err: = Parsefile (file) if err! = Nil {log. Fatalf ("Could not parse File:%v", err)}//Create A new context which contains we given token.//this same context would b E passed into both the calls we make//to our consignment-service.ctx: = metadata. Newcontext (context. Background (), map[string]string{"token": Token,})//First call using our tokenised contextr, err: = client. Createconsignment (CTX, consignment) if err! = Nil {log. Fatalf ("Could not create:%v", err)}log. Printf ("Created:%t", r.created)//Second Callgetall, err: = client. Getconsignments (CTX, &AMP;PB. getrequest{}) If err! = Nil {log. Fatalf ("Could not list consignments:%v", err)}for _, V: = range getall.consignments {log. Println (v)}} "Now we update Consignment-service to check the request for token and pass it to User-service: ' go//shippy-consignment-service/ Main.gofunc Main () {...//Create a new serviCe. Optionally include some options here.srv: = Micro. NewService (//This name must match the package name given in your Protobuf definitionmicro. Name ("Go.micro.srv.consignment"), Micro. Version ("latest"),//Our auth middlewaremicro. Wraphandler (Authwrapper),) ...} ...//Authwrapper is a high-order function which takes a handlerfunc//and returns a function, which takes a context, req Uest and Response interface.//The token is extracted from the context set in our consignment-cli, that//token was then SE NT over-to-the user service to being validated.//If valid, the call was passed along to the handler. If not,//An error is Returned.func authwrapper (FN server. HANDLERFUNC) server. Handlerfunc {return func (CTX context). Context, req server. Request, resp interface{}) error {meta, OK: = metadata. Fromcontext (CTX) If!ok {return errors. New ("No auth meta-data found in Request")}//Note This is now uppercase (not entirely sure what this is ...) Token: = meta["token"]log. Println ("Authenticating with token:", token)//Auth Hereauthclient: = Userservice.newuserserviceclient ("Go.micro.srv.user", client. Defaultclient) _, Err: = Authclient.validatetoken (context. Background (), &userservice.token{token:token,}) if err! = Nil {return Err}err = fn (CTX, req, resp) return err}} "and then we hold Line CONSIGNMENT-CLI tool, CD to the new SHIPPY-CONSIGNMENT-CLI repository, execute ' $make build ' to create a new Docker image and now run: ' $ make build$ Docker run-- net= "Host" \-e micro_registry=mdns \ consignment-cli consignment.json \ <TOKEN_HERE> "Note that we used the '-" when we ran the Docker container. -net= "host" ' tag. Used to tell Docker to run our containers on a local LAN, such as 127.0.0.1 or localhost, rather than the Docker intranet. Note that using this method does not require port forwarding. So we just need to use-p 8080 instead of-P 8,080:8,080. [See here for more information on the Docker network] (https://docs.docker.com/engine/userguide/networking/). In this step, you can see that the new consignment is created. Try to remove a few letters from the token and the token will become invalid. An error will occur. Well, now we've created a JWT token service, and a middleware for validating JWT tokens, and JWT tokens are used to authenticate users. If you do not want to use Go-micro, but vanilla Grpc, your middleware is similar to this: "' gofunc main () {myServer: = Grpc. NewServer (GRPC. Unaryinterceptor (Grpc_middleware. ChainunaRyserver (Authinterceptor),)}func Authinterceptor (CTX context. Context, req interface{}, Info *grpc. Unaryserverinfo, Handler Grpc. Unaryhandler) (interface{}, error) {//Set up a connection to the server.conn, err: = Grpc. Dial (Authaddress, Grpc. Withinsecure ()) if err! = Nil {log. Fatalf ("Did not connect:%v", err)}defer Conn. Close () c: = PB. Newauthclient (conn) R, Err: = C.validatetoken (CTX, &AMP;PB. Validatetoken{token:token}) If err! = Nil {log. Fatalf ("Could not authenticate:%v", err)}return handler (CTX, req)} ' This setting is a bit inflexible to run locally. But we don't always run every single service locally. The services we create should be independent of each other and can be tested separately. Now, if we just need to test consignment-service, we don't need to start auth-service. So I added a switch here to turn on or off other services. I updated the auth package in Consignment-service: "' go//shippy-user-service/main.gofunc authwrapper (FN server. HANDLERFUNC) server. Handlerfunc {return func (CTX context). Context, req server. Request, resp interface{}) Error {//This skips our auth check if Disable_auth are set to Trueif OS. Getenv ("disable_auth") = = "true" {return fn (CTX, req, resp)}} "" And then in Makefile add a openOff: "'//Shippy-user-service/makefilerun:docker run-d--net=" host "\-p 50052 \-e micro_server_address=:50052 \-e MICRO_ Registry=mdns \-e disable_auth=true \consignment-service "In this way, it is easier to run a part of the microservices locally, and there are several different ways to solve the problem, but I think this is the simplest. I hope you feel a little help, although there are minor changes in the direction. At the same time, if you have any suggestions on how to run MicroServices using a single warehouse, it's a lot easier to let me know. If you find any bug in this article, or if you have any feedback or helpful suggestions for this article, [please send me an e-mail] (ewan.valentine89@gmail.com). If you think this series of articles is useful, and you use AD filtering (I don't blame you). Please consider my hard work. Thank you so much!. [Https://monzo.me/ewanvalentine] (Https://monzo.me/ewanvalentine)

Via:https://ewanvalentine.io/microservices-in-golang-part-4

Author: Andrécarvalho Translator: arisaries proofreading: polaris1119

This article by GCTT original compilation, go language Chinese network honor launches

This article was originally translated by GCTT and the Go Language Chinese network. Also want to join the ranks of translators, for open source to do some of their own contribution? Welcome to join Gctt!
Translation work and translations are published only for the purpose of learning and communication, translation work in accordance with the provisions of the CC-BY-NC-SA agreement, if our work has violated your interests, please contact us promptly.
Welcome to the CC-BY-NC-SA agreement, please mark and keep the original/translation link and author/translator information in the text.
The article only represents the author's knowledge and views, if there are different points of view, please line up downstairs to spit groove

1528 Reads
Related Article

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.