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
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 reusedPublic 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:
Authentication principle:
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