1. The background

The Spring Cloud Gateway is an API gateway that serves as a unified entry point for API interfaces. In practice, a URL must be authenticated. For example, a token must be carried to access a SPECIFIC URL. This process can be implemented on the Gateway.

JWT is a format for digital signatures (tokens). With the JWT implementation of Java class library, we can easily achieve the generation of tokens, and verify and resolve tokens.

The Gateway collection JWT can implement basic authentication functions.

2. Knowledge

Spring-cloud-gateway provides an API gateway built on top of the Spring ecosystem, designed to provide a simple and efficient way to route apis and provide them with crosscutting concerns such as security, monitoring/metrics, and resiliency.

JWT: JWT is a digital signature (token) format. JSON Web Token (JWT) is an open standard that defines a compact, self-contained way to securely transfer information between parties as JSON objects. This information can be authenticated and trusted because it is digitally signed.

Implementation approach

  • Write a gateway gateway, which is the external access point. Any URL must pass through this gateway first.
  • 2. We also need an interface to generate tokens, such as /login, which accepts accounts and secrets and returns a valid token if authenticated.
  • 3. The above valid tokens are generated with the help of JWT.
  • 4. When accessing other resources again, the request header should contain the token generated in the previous step, which can be understood as a token, key.
  • 5. When a request comes in, check whether there is a token and whether the token is valid, with the help of JWT.
  • 6. We will use JWT to generate token and verify token classes in a micro-service named auth-Service.

Here’s a picture:

image.png

Example 3.

(1) The implementation requires a Gateway’s AuthorizationFilter, which will intercept all requests.

@Slf4j @Component public class AuthorizationFilter extends AbstractGatewayFilterFactory<AuthorizationFilter.Config> { @Autowired private AuthorizationClient1 authorizationClient; @Autowired private IgnoreAuthorizationConfig ignoreAuthorizationConfig; public AuthorizationFilter() { super(AuthorizationFilter.Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, Chain) -> {log.info("## trigger in filter :AuthorizationFilter2"); String targetUriPath = exchange.getRequest().getURI().getPath(); If (isSkipAuth(targetUriPath)) {log.info("## skip authentication, targetUriPath={}", targetUriPath); return goNext(exchange, chain); } String token = exchange.getRequest().getHeaders().getFirst("token"); If (token = = null | | token. IsEmpty ()) {the info (" # # invalid token = {}, targetUriPath = {} ", token, targetUriPath); return responseInvalidToken(exchange, chain); } if (! VerifyToken (token) {log.info("## token failed to check, parameter token = {}, targetUriPath= {}", token, targetUriPath); return responseInvalidToken(exchange, chain); } log.info("## token verified! Parameter token = {}, targetUriPath= {}", token, targetUriPath); return chain.filter(exchange); }; }Copy the code

Modifying a configuration file:

spring:
  application:
    name: api-gateway

  cloud:
    gateway:
      default-filters:
        - AuthorizationFilter
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      globalcors:
        corsConfigurations:
          '[/auth/**]':
            allowedOrigins: '*'
            allowedHeaders:
              - x-auth-token
              - x-request-id
              - Content-Type
              - x-requested-with
              - x-request-id
            allowedMethods:
              - GET
              - POST
              - OPTIONS
      routes:
        - id: auth-service
          uri: lb://auth-service
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
        - id: hello-service-1
          uri: lb://hello-service
          predicates:
            - Path=/hello/**
          filters:
            - StripPrefix=1
Copy the code

(2) Filter to the special URL that does not need verification

@Autowired private IgnoreAuthorizationConfig ignoreAuthorizationConfig; /** * Whether to skip authentication check ** @param targetUriPath request resource URI * @return */ private Boolean isSkipAuth(String targetUriPath) { boolean isSkip = ignoreAuthorizationConfig.getUrlList().contains(targetUriPath); log.info("## isSkip={}, ignoreAuthorizationConfig={}, targetUriPath={}", isSkip, ignoreAuthorizationConfig, targetUriPath); return isSkip; } @Data @Component @ConfigurationProperties(prefix = "ignore.authorization") public class IgnoreAuthorizationConfig { /** * private Set<String> urlList; }Copy the code

Also modify the configuration file:

ignore:
  authorization:
    urlList:
      - /auth/login
      - /auth/logout
Copy the code

(3) Verify the validity of token by calling auth service

/** * Validate token ** @param token * @return */ private Boolean verifyToken(String token) {try {String verifyToken =  authorizationClient.verifyToken(token); Log.info ("## verifyToken, parameter token={}, result ={} ", token, verifyToken); return verifyToken ! = null && ! verifyToken.isEmpty(); } catch (Exception ex) { ex.printStackTrace(); Log.info ("## verifyToken, token={}, exception ={} ", token, ex.toString()); return false; }}Copy the code

The AuthorizationClient1 class is responsible for initiating network requests to the Auth microservice.

/** * @author zhangyunfei * @date 2019/2/20 */ @Slf4j @Service public class AuthorizationClient1 { @Autowired private RestTemplate restTemplate; /** * Note: * 1, RestTemplate LoadBalanced: BlockLast () is blocking, which is not supported in thread reactor- HTTP-NIo-3 * 2, so, LoadBalanced, Private static final String URL_VERIFY_TOKEN = "http://auth-service/verifytoken"; // Private static final String URL_VERIFY_TOKEN = "http://auth-service/verifytoken"; Private static final String URL_VERIFY_TOKEN = "http://127.0.0.1:8082/verifytoken"; Public String verifyToken(String token) {log.info("## verifyToken ready to execute: verifyToken"); HttpHeaders headers = new HttpHeaders(); LinkedMultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>(); HttpEntity entity = new HttpEntity<>(paramMap, headers); paramMap.add("token", token); String url = URL_VERIFY_TOKEN; ResponseEntity<String> forEntity = restTemplate .exchange(url, HttpMethod.POST, entity, new ParameterizedTypeReference<String>() { }); HttpStatus statusCode = forEntity.getStatusCode(); String res = forEntity.getBody(); Log.info ("## verifyToken execution end: verifyToken, statusCode={}, result ={}", statusCode, res); return res; }}Copy the code

(4) Write an Auth authentication microservice

Responsibilities:

  • 1. /login Generates token
  • 2. Check whether the token is valid
@RestController() public class AuthController { private Logger logger = LoggerFactory.getLogger("AuthController"); /** * Authentication: Obtain user information by token. * - Successful: return user information * - Failed: return 401 * - Failed: 1. Token expires. 2. Token is empty or invalid. * * @param token * @return */ @RequestMapping(value = {"/authority"}, method = RequestMethod.POST) public String authority(@RequestParam String token, @RequestParam String resource) { logger.info("## auth" + token); return "{ userId:123, userName:\"zhang3\" }"; } /** * verifytoken validity ** @param token * @return */ @requestmapping (value = {"/verifytoken"}, method = RequestMethod.POST) public ResponseEntity<String> verifyToken(@RequestParam String token) { logger.info("## VerifyToken Parameter Token ={}", token); String userName = JwtUtils.decode(token); If (userName = = null | | userName. IsEmpty ()) {logger. The info (" # # verifyToken parameter token = {}, failure ", token); return new ResponseEntity<>("internal error", HttpStatus.UNAUTHORIZED); } UserInfo user = new UserInfo(userName, "", 18); Logger. info("## verifyToken parameter token={}, success, user info ={}", token, user); return new ResponseEntity<>(JSON.toJSONString(user), HttpStatus.OK); } @requestMapping (value = "/mine", RequestMapping(value = "/mine"); method = RequestMethod.POST) public String mine(@RequestParam String token, @RequestParam String resource) { logger.info("## auth" + token); return "{ userId:123, userName:\"zhang3\", group:\"zh\", country:\"china\" }"; } /** * Authentication: * * @param name * @param password * @return */ @requestMapping (value = {"/authorization", "/login"}) public String authorization(@RequestParam String name, @RequestParam String password) { String token = JwtUtils.sign(name); logger.info("## authorization name={}, token={}", name, token); return token; }}Copy the code

(5)

Can be initiated in the postman request access: http://localhost:9000/auth/login? Name = wang5 & password = 1 business visit http://localhost:9000/hello/hi? name=zhang3

4. The extension

My demo: github.com/vir56k/demo…

JWT auxiliary class

package eureka_client.demo.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTCreationException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import java.util.Date; public class JwtUtils { private static final String SECRET = "zhangyunfei789! @ "; private static final long EXPIRE = 1000 * 60 * 60 * 24 * 7; // Expiration time, 7 days /** * build a token * pass userID ** @param userID * @return */ public static String sign(String userID) {try {Date now  = new Date(); long expMillis = now.getTime() + EXPIRE; Date expDate = new Date(expMillis); Algorithm algorithmHS = Algorithm.HMAC256(SECRET); String token = JWT.create() .withIssuer("auth0") .withJWTId(userID) .withIssuedAt(now) .withExpiresAt(expDate) .sign(algorithmHS); return token; } catch (JWTCreationException exception) { //Invalid Signing configuration / Couldn't convert Claims. return null; }} public static Boolean verify(String token) {try {Algorithm} public static Boolean verify(String token) {try {Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); String userID = jwt.getId(); return userID ! = null && !" ".equals(userID); } catch (JWTVerificationException exception) { //Invalid signature/claims return false; } /** * Parse token * Return userID * @param token * @return */ public static String decode(String token) {try {Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); return jwt.getId(); } catch (JWTVerificationException exception) { //Invalid signature/claims return null; }}}Copy the code

5. Reference:

“Spring Cloud Micro-service Combat”