packagestore.auth;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.HttpStatus;importorg.springframework.http.ResponseEntity;importorg.springframework.stereotype.Service;importorg.springframework.web.server.ResponseStatusException;importstore.account.AccountController;importstore.account.AccountIn;importstore.account.AccountOut;@ServicepublicclassAuthService{privateLoggerlogger=LoggerFactory.getLogger(AuthService.class);@AutowiredprivateAccountControlleraccountController;@AutowiredprivateJwtServicejwtService;publicStringregister(Stringname,Stringemail,Stringpassword){logger.debug(String.format("registrando uma conta: [%s] for [%s]",name,email));// Salvar no servico de AccountAccountOutaccount=accountController.create(AccountIn.builder().name(name).email(email).password(password).build()).getBody();// Gera um tokenStringjwtString=jwtService.generate(account);// Retorna o tokenreturnjwtString;}publicStringlogin(Stringemail,Stringpassword){logger.debug(String.format("required login for %s:%s",email,password));// Verify credentialsResponseEntity<AccountOut>response=accountController.findByEmailAndPassword(AccountIn.builder().email(email).password(password).build());if(!response.hasBody()){logger.debug(String.format("user not found"));thrownewResponseStatusException(HttpStatus.UNAUTHORIZED);}AccountOutaccount=response.getBody();logger.debug(String.format("found user",account));// generate tokenreturnjwtService.generate(account);}publicAccountOutsolve(Stringjwt){returnAccountOut.builder().id(jwtService.getId(jwt)).build();}}
packagestore.auth;importjava.util.Date;importjava.util.Map;importjavax.crypto.SecretKey;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.http.HttpStatus;importorg.springframework.stereotype.Service;importorg.springframework.web.server.ResponseStatusException;importio.jsonwebtoken.Claims;importio.jsonwebtoken.JwtParser;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.io.Decoders;importio.jsonwebtoken.security.Keys;importstore.account.AccountOut;@ServicepublicclassJwtService{@Value("${store.jwt.secretKey}")privateStringsecretKey;publicStringgenerate(AccountOutaccount){Datenow=newDate();Stringjwt=Jwts.builder().header().and().id(account.id()).issuer("Insper::PMA").claims(Map.of("email",account.email())).signWith(getKey()).subject(account.name()).notBefore(now).expiration(newDate(now.getTime()+1000*60*120))// em milisegundos.compact();returnjwt;}publicStringgetId(Stringjwt){// constroe o parserJwtParserparser=Jwts.parser().verifyWith(getKey()).build();// recupero os atributosClaimsclaims=parser.parseSignedClaims(jwt).getPayload();Datenow=newDate();if(claims.getNotBefore().after(now)){thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Token is not valid yet!");}if(claims.getExpiration().before(now)){thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Token is expired!");}returnclaims.getId();}privateSecretKeygetKey(){returnKeys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));}}
{"name":"Antonio do Estudo","email":"acme@insper.edu.br","password":"123@321"}
Sequence Diagram
sequenceDiagram
autonumber
actor User
User->>+Auth: register (RegisterIn)
Auth->>+Account: create (AccountIn)
Account->>-Auth: returns the new account (AccountOut)
Auth->>-User: returns 201 (TokenOut)
sequenceDiagram
autonumber
actor User
User->>+Auth: authenticate (LoginIn)
Auth->>+Account: findByEmailAndPassword
critical validated
Account->>-Auth: returns the account
option denied
Auth-->>User: unauthorized message
end
Auth->>Auth: generates a token
Auth->>-User: returns TokenOut
User->>User: stores the token to use for the next requests
Security
Security is an important aspect of software development. It involves protecting the confidentiality, integrity, and availability of data and resources. Two key concepts in security are authentication and authorization.
Authentication is the process of verifying the identity of a user or system. It ensures that the user or system is who they claim to be. Common authentication methods include passwords, biometrics, and two-factor authentication. The system checks these credentials against the stored data. If the credentials are valid, the system confirms the user's identity.
In many systems, after successful authentication, the system generates a token. This token is a piece of data that represents the user's authentication session. It's like a digital ticket that proves the user's identity for a certain period of time.
This token is then sent back to the user. The user's client software (like a web browser) stores this token and sends it along with every subsequent request to the server (in case of stateless server). This way, the server knows that the request comes from an authenticated user without needing to ask for the credentials again.
Here's a simplified step-by-step process:
sequenceDiagram
autonumber
actor User
User->>+Auth Service: authentication(credentials)
Auth Service->>Auth Service: verifies credenditals and generates a token
Auth Service->>-User: returns the token
User->>User: stores the token to use for the next requests
The user sends their username and password (or other credentials) to the server;
The server verifies the credentials. If they're valid, the server generates a token.
The server sends this token back to the user.
The user's client software stores this token.
For every subsequent request, the client sends this token along with the request.
The server checks the token to ensure it's valid and hasn't expired.
This token-based authentication process is commonly used in many modern web applications and APIs. It helps maintain the user's session and allows the server to authenticate requests without storing the user's state.
Authorization is the process of granting or denying access to specific resources or actions based on the authenticated user's privileges. It determines what a user is allowed to do within a system. Authorization can be role-based, where permissions are assigned based on predefined roles, or attribute-based, where permissions are based on specific attributes of the user.
In many systems, the token not only represents the user's identity, but also includes information about their permissions or roles. This is often done using a type of token called a JSON Web Token (JWT), which can include a payload of data.
Here's a simplified step-by-step process:
sequenceDiagram
autonumber
actor User
User->>Auth Service: request with token
Auth Service->>Auth Service: decodes the token and extracts claims
Auth Service->>Auth Service: verifies permissions
critical allowed
Auth Service->>Secured Resource: authorizes the request
Secured Resource->>User: returns the response
option denied
Auth Service-->>User: unauthorized message
end
After authentication, the user's client software sends a request to a server. This request includes the token.
The server decodes the token and extracts the user's identity and permissions.
The server checks whether the user has the necessary permissions for the requested action. This could involve checking the user's roles or other attributes against the requirements for the action.
If the user has the necessary permissions, the server allows the action. If not, the server denies the action.
This process allows the server to authorize actions without needing to repeatedly look up the user's permissions. It also allows for stateless servers, as the necessary information is included in every request.
By implementing strong authentication and authorization mechanisms, software systems can ensure that only authorized users have access to sensitive data and functionalities, reducing the risk of unauthorized access and potential security breaches.
As the platform has only one entrace point, it is
JWT is a decentralized
The point of entrance of API is the gateway, then as suggested by 1.
The gateway is responsible for the security of the system. It is the first point of contact for all incoming requests. The gateway is responsible for routing requests to the appropriate services and ensuring that only authorized users can access the system.
sequenceDiagram
autonumber
actor User
User->>Gateway: route(ServerHttpRequest)
Gateway->>+AuthorizationFilter: filter(ServerWebExchange, GatewayFilterChain)
AuthorizationFilter->>RouteValidator: isSecured.test(ServerHttpRequest)
RouteValidator-->>AuthorizationFilter: True | False
critical notSecured
AuthorizationFilter->>Gateway: follow the flux
end
AuthorizationFilter->>AuthorizationFilter: isAuthMissing(ServerHttpRequest)
critical isAuthMissing
AuthorizationFilter->>User: unauthorized message
end
AuthorizationFilter->>AuthorizationFilter: validateAuthorizationHeader()
critical isInvalidAuthorizationHeader
AuthorizationFilter->>User: unauthorized message
end
AuthorizationFilter->>Auth: solve(Token)
critical isInvalidToken
Auth->>User: unauthorized message
end
Auth->>AuthorizationFilter: returns SolveOut
AuthorizationFilter->>AuthorizationFilter: updateRequestHeader(ServerHttpRequest)
AuthorizationFilter->>Gateway: follow the flux
packagestore.gateway;importjava.util.Map;importorg.springframework.http.ResponseEntity;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassGatewayResource{@GetMapping("/health-check")publicResponseEntity<Map<String,String>>healthCheck(){returnResponseEntity.ok().body(Map.of("osArch",System.getProperty("os.arch"),"osName",System.getProperty("os.name"),"osVersision",System.getProperty("os.version")));}@GetMapping("/")publicResponseEntity<String>hello(){returnResponseEntity.ok().body("API for Store");}}
packagestore.gateway.security;importjava.util.Map;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.cloud.gateway.filter.GatewayFilterChain;importorg.springframework.cloud.gateway.filter.GlobalFilter;importorg.springframework.http.HttpHeaders;importorg.springframework.http.HttpStatus;importorg.springframework.http.MediaType;importorg.springframework.http.server.reactive.ServerHttpRequest;importorg.springframework.stereotype.Component;importorg.springframework.web.reactive.function.client.WebClient;importorg.springframework.web.server.ResponseStatusException;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;@ComponentpublicclassAuthorizationFilterimplementsGlobalFilter{privateLoggerlogger=LoggerFactory.getLogger(AuthorizationFilter.class);privatestaticfinalStringAUTHORIZATION_HEADER="Authorization";privatestaticfinalStringAUTHORIZATION_BEARER_HEADER="Bearer";privatestaticfinalStringAUTH_SERVICE_TOKEN_SOLVE="http://auth:8080/auth/solve";@AutowiredprivateRouterValidatorrouterValidator;@AutowiredprivateWebClient.BuilderwebClient;@OverridepublicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){logger.debug("filter: entrou no filtro de autorizacao");ServerHttpRequestrequest=exchange.getRequest();if(!routerValidator.isSecured.test(request)){logger.debug("filter: rota nao eh segura");returnchain.filter(exchange);}logger.debug("filter: rota eh segura");if(!isAuthMissing(request)){logger.debug("filter: tem [Authorization] no Header");Stringauthorization=request.getHeaders().get(AUTHORIZATION_HEADER).get(0);logger.debug(String.format("filter: [Authorization]=[%s]",authorization));String[]parts=authorization.split(" ");if(parts.length!=2){logger.debug("filter: bearer token is invalid");thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Authorization header is not well formatted");}if(!AUTHORIZATION_BEARER_HEADER.equals(parts[0])){logger.debug("filter: bearer token is invalid");thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Authorization header is not well formatted");}logger.debug("filter: bearer token is formatted");finalStringjwt=parts[1];returnrequestAuthTokenSolve(exchange,chain,jwt);}logger.debug("filter: access is denied!");// if access is deniedthrownewResponseStatusException(HttpStatus.UNAUTHORIZED);}privatebooleanisAuthMissing(ServerHttpRequestrequest){return!request.getHeaders().containsKey(AUTHORIZATION_HEADER);}// este metodo eh responsavel por enviar o token ao Auth Microservice// a fim de interpretar o token, a chamada eh feita via Rest.privateMono<Void>requestAuthTokenSolve(ServerWebExchangeexchange,GatewayFilterChainchain,Stringjwt){logger.debug("solve: solving jwt: "+jwt);returnwebClient.defaultHeader(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE).build().post().uri(AUTH_SERVICE_TOKEN_SOLVE).bodyValue(Map.of("jwt",jwt)).retrieve().toEntity(Map.class).flatMap(response->{if(response!=null&&response.hasBody()&&response.getBody()!=null){finalMap<String,String>map=response.getBody();StringidAccount=map.get("idAccount");logger.debug("solve: id account"+idAccount);returnchain.filter(exchange);}else{thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Invalid token");}});}}
JWT stands for JSON Web Token. It is a compact, URL-safe means of representing claims between two parties. JWTs are commonly used to secure the transmission of information between parties in a web environment, typically for authentication and information exchange. The JWT specification is defined by RFC 75192 and it is a decentralized approach for security (which can support horizontal scalability).
Here are the key components and concepts of JWT:
JSON Format: JWTs are represented as JSON objects that are easy to parse and generate. The JSON format makes them human-readable and easy to work with.
Three Parts: JWTs consist of three parts separated by dots (.): Header, Payload, and Signature.
Header: The header typically consists of two parts: the type of the token (JWT) and the signing algorithm being used, such as HMAC SHA256 or RSA.
Payload: The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. There are three types of claims: registered, public, and private claims.
Signature: To create the signature part, you take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.
Encoding: Each of the three parts is Base64Url encoded, and the resulting strings are concatenated with periods between them. The final JWT looks like: xxxxx.yyyyy.zzzzz.
Stateless and Self-contained: JWTs are stateless, meaning that all the information needed is within the token itself. The server doesn't need to store the user's state. They are also self-contained, meaning that all the information needed is contained within the token.
Use Cases: JWTs are commonly used for authentication and information exchange between parties. For example, after a user logs in, a server could generate a JWT and send it to the client. The client can then include the JWT in the headers of subsequent requests to access protected resources. The server can verify the authenticity of the JWT using the stored secret key.
Security Considerations: While JWTs are widely used and versatile, it's important to handle them securely. For instance, the key used to sign the JWT should be kept secret, and HTTPS should be used to transmit JWTs to prevent man-in-the-middle attacks.
Here's a simple example of a JWT created on JWT Builder3:
JWTs are widely used in web development due to their simplicity, flexibility, and support across various programming languages and frameworks. They are commonly used in token-based authentication systems.