Detailed explanation of authentication and authentication of SpringCloud with JWT

  • 2021-07-09 08:16:57
  • OfStack

JWT (JSON WEB TOKEN) is a compact and self-contained JSON object defined based on RFC 7519 standard, which can be safely transmitted. Because the data is digitally signed, it is trusted and secure. JWT can use HMAC algorithm to encrypt secret or use RSA's public-private key pair to sign.

JWT is usually composed of three parts: header (Header), payload (Payload) and signature (Signature), separated by. in the middle, and its format is Header. Payload. Signature

Header: Declare the type of token and the algorithm used

alg: Signature algorithm typ: Type of token, such as JWT

Payload: Also known as JWT Claims, contains some information about the user

Declaration of System Reservation (Reserved claims):

iss (issuer): Issuer exp (expiration time): Expired time sub (subject): Topics aud (audience): Audience users nbf (Not Before): Not available until then iat (Issued At): Date of issuance jti (JWT ID): JWT has only one identification, which can be used to prevent JWT from being reused

Public statement (public): see http://www.iana.org/assignments/jwt/jwt.xhtml

Private Declaration (private claims): Self-defined data according to business needs

Signature: Signature

Signature format: HMACSHA256 (base64UrlEncode (header) + "." + base64UrlEncode (payload), secret)

JWT features:

JWT is not encrypted by default, and user sensitive class information cannot be placed in Payload. JWT can be used not only for authentication, but also for exchanging information. The biggest drawback of JWT is that the server does not save session state, so it is impossible to cancel the token or change the permissions of the token during use. JWT itself contains authentication information. In order to reduce theft, the validity period of JWT should not be set too long. To reduce embezzlement and theft, JWT does not recommend using the HTTP protocol for code transmission, but uses the encrypted HTTPS protocol for transmission. It is slow to generate token for the first time, which consumes CPU. In the case of high concurrency, it is necessary to consider the occupation of CPU. The generated token is relatively long, so traffic problems may need to be considered.

Authentication principle:

The client applies to the server for authorization. After the server authenticates, it generates an token string and returns it to the client. After that, the client requests Protected resources carry this token, and the server authenticates and parses the user's identity information from this token.

How to use JWT: One way is to put it in the header information Authorization field of HTTP request, and the format is as follows:

Authorization: <token>

The server needs to be set up to accept requests from all domains, using Access-Control-Allow-Origin: *

Alternatively, JWT is placed in the data body requested by POST when cross-domain.

How to renew token for JWT:

1. An additional refreshToken is generated to obtain a new token, and refreshToken needs to be stored on the server, and its expiration time is slightly longer than that of JWT.

2. The user carries refreshToken parameters to request token to refresh the interface. After judging that refreshToken has not expired, the server takes out the associated user information and the current token.

3. Regenerate the token with the current user information, put the old token on the blacklist, and return the new token.

Create engineering auth-service for login authentication:

1. Create the pom. xml file


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 
 <modelVersion>4.0.0</modelVersion> 
 <groupId>com.seasy.springcloud</groupId> 
 <artifactId>auth-service</artifactId> 
 <version>1.0.0</version> 
 <packaging>jar</packaging> 
  
 <parent> 
  <groupId>org.springframework.boot</groupId> 
  <artifactId>spring-boot-starter-parent</artifactId> 
  <version>2.0.8.RELEASE</version> 
  <relativePath/> 
 </parent> 
 
 <properties> 
  <java.version>1.8</java.version> 
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 
 </properties> 
  
 <dependencies> 
  <dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-web</artifactId> 
  </dependency> 
  <dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-actuator</artifactId> 
  </dependency> 
   
  <!-- spring cloud --> 
  <dependency> 
    <groupId>org.springframework.cloud</groupId> 
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> 
  </dependency> 
   
  <!-- redis --> 
  <dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-redis</artifactId> 
  </dependency> 
  <dependency> 
    <groupId>org.apache.commons</groupId> 
    <artifactId>commons-pool2</artifactId> 
  </dependency> 
   
  <!-- jwt --> 
  <dependency> 
    <groupId>com.auth0</groupId> 
    <artifactId>java-jwt</artifactId> 
    <version>3.7.0</version> 
  </dependency> 
 </dependencies> 
  
 <dependencyManagement> 
  <dependencies> 
    <dependency> 
      <groupId>org.springframework.cloud</groupId> 
      <artifactId>spring-cloud-dependencies</artifactId> 
      <version>Finchley.RELEASE</version> 
      <type>pom</type> 
      <scope>import</scope> 
    </dependency> 
  </dependencies> 
 </dependencyManagement> 
</project> 

2. JWT tool class


public class JWTUtil { 
  public static final String SECRET_KEY = "123456"; // Secret key  
  public static final long TOKEN_EXPIRE_TIME = 5 * 60 * 1000; //token Expired time  
  public static final long REFRESH_TOKEN_EXPIRE_TIME = 10 * 60 * 1000; //refreshToken Expired time  
  private static final String ISSUER = "issuer"; // Issuer  
 
  /** 
   *  Generate signature  
   */ 
  public static String generateToken(String username){ 
    Date now = new Date(); 
    Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY); // Algorithm  
     
    String token = JWT.create() 
      .withIssuer(ISSUER) // Issuer  
      .withIssuedAt(now) // Issue time  
      .withExpiresAt(new Date(now.getTime() + TOKEN_EXPIRE_TIME)) // Expired time  
      .withClaim("username", username) // Save identity  
      .sign(algorithm); 
    return token; 
  } 
   
  /** 
   *  Validation token 
   */ 
  public static boolean verify(String token){ 
    try { 
      Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY); // Algorithm  
      JWTVerifier verifier = JWT.require(algorithm) 
          .withIssuer(ISSUER) 
          .build(); 
      verifier.verify(token); 
      return true; 
    } catch (Exception ex){ 
      ex.printStackTrace(); 
    } 
    return false; 
  } 
   
  /** 
   *  From token Get username 
   */ 
  public static String getUsername(String token){ 
    try{ 
      return JWT.decode(token).getClaim("username").asString(); 
    }catch(Exception ex){ 
      ex.printStackTrace(); 
    } 
    return ""; 
  } 
}

3. LoginController class


@RestController 
public class LoginController { 
  @Autowired 
  StringRedisTemplate redisTemplate; 
   
  /** 
   *  Login authentication  
   * @param username  User name  
   * @param password  Password  
   */ 
  @GetMapping("/login") 
  public AuthResult login(@RequestParam String username, @RequestParam String password) { 
    if("admin".equals(username) && "admin".equals(password)){ 
      // Generate token 
      String token = JWTUtil.generateToken(username); 
       
      // Generate refreshToken 
      String refreshToken = StringUtil.getUUIDString(); 
       
      // Data placement redis 
      redisTemplate.opsForHash().put(refreshToken, "token", token); 
      redisTemplate.opsForHash().put(refreshToken, "username", username); 
       
      // Settings token Expiration time of  
      redisTemplate.expire(refreshToken, JWTUtil.REFRESH_TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS); 
       
      return new AuthResult(0, "success", token, refreshToken); 
    }else{ 
      return new AuthResult(1001, "username or password error"); 
    } 
  } 
   
  /** 
   *  Refresh token 
   */ 
  @GetMapping("/refreshToken") 
  public AuthResult refreshToken(@RequestParam String refreshToken) { 
    String username = (String)redisTemplate.opsForHash().get(refreshToken, "username"); 
    if(StringUtil.isEmpty(username)){ 
      return new AuthResult(1003, "refreshToken error"); 
    } 
 
    // Generate a new token 
    String newToken = JWTUtil.generateToken(username); 
    redisTemplate.opsForHash().put(refreshToken, "token", newToken); 
    return new AuthResult(0, "success", newToken, refreshToken); 
  } 
 
  @GetMapping("/") 
  public String index() { 
    return "auth-service: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); 
  } 
} 

4. application configuration information


spring.application.name=auth-service 
server.port=4040 
 
eureka.instance.hostname=${spring.cloud.client.ip-address} 
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port} 
eureka.instance.prefer-ip-address=true 
 
eureka.client.service-url.defaultZone=http://root:123456@${eureka.instance.hostname}:7001/eureka/ 
 
#redis 
spring.redis.database=0 
spring.redis.timeout=3000ms 
spring.redis.lettuce.pool.max-active=100 
spring.redis.lettuce.pool.max-wait=-1ms 
spring.redis.lettuce.pool.min-idle=0 
spring.redis.lettuce.pool.max-idle=8 
 
#standalone 
spring.redis.host=192.168.134.134 
spring.redis.port=7001 
 
#sentinel 
#spring.redis.sentinel.master=mymaster 
#spring.redis.sentinel.nodes=192.168.134.134:26379,192.168.134.134:26380 

5. Startup class


@SpringBootApplication 
@EnableEurekaClient 
public class Main{ 
  public static void main(String[] args){ 
    SpringApplication.run(Main.class, args); 
  } 
} 

Reconstruction of SpringCloud Gateway Project

1. Add dependencies to the pom. xml file


<!-- redis --> 
<dependency> 
  <groupId>org.springframework.boot</groupId> 
  <artifactId>spring-boot-starter-data-redis</artifactId> 
</dependency> 
<dependency> 
  <groupId>org.apache.commons</groupId> 
  <artifactId>commons-pool2</artifactId> 
</dependency> 
 
<!-- jwt --> 
<dependency> 
  <groupId>com.auth0</groupId> 
  <artifactId>java-jwt</artifactId> 
  <version>3.7.0</version> 
</dependency>

2. Create global filter JWTAuthFilter


@Component 
public class JWTAuthFilter implements GlobalFilter, Ordered{ 
  @Override 
  public int getOrder() { 
    return -100; 
  } 
   
  @Override 
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { 
    String url = exchange.getRequest().getURI().getPath(); 
     
    // Ignore the following url Request  
    if(url.indexOf("/auth-service/") >= 0){ 
      return chain.filter(exchange); 
    } 
     
    // Obtain from the request header token 
    String token = exchange.getRequest().getHeaders().getFirst("Authorization"); 
    if(StringUtil.isEmpty(token)){ 
      ServerHttpResponse response = exchange.getResponse(); 
      response.setStatusCode(HttpStatus.OK); 
      response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); 
       
      Response res = new Response(401, "401 unauthorized"); 
      byte[] responseByte = JSONObject.fromObject(res).toString().getBytes(StandardCharsets.UTF_8); 
       
      DataBuffer buffer = response.bufferFactory().wrap(responseByte); 
      return response.writeWith(Flux.just(buffer)); 
    } 
     
    // In the request token Whether it is in redis Existence in  
    boolean verifyResult = JWTUtil.verify(token); 
    if(!verifyResult){ 
      ServerHttpResponse response = exchange.getResponse(); 
      response.setStatusCode(HttpStatus.OK); 
      response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); 
 
      Response res = new Response(1004, "invalid token"); 
      byte[] responseByte = JSONObject.fromObject(res).toString().getBytes(StandardCharsets.UTF_8); 
       
      DataBuffer buffer = response.bufferFactory().wrap(responseByte); 
      return response.writeWith(Flux.just(buffer)); 
    } 
     
    return chain.filter(exchange); 
  } 
} 

3. Key application configuration information


spring: 
 application: 
  name: service-gateway 
 cloud: 
  gateway: 
   discovery: 
    locator: 
     enabled: true 
     lowerCaseServiceId: true 
   routes: 
    # Authentication service routing  
    - id: auth-service 
     predicates: 
      - Path=/auth-service/** 
     uri: lb://auth-service 
     filters: 
      - StripPrefix=1

Related articles: