In the previous article, we integrated a service gateway, Spring Cloud Gateway, where all service requests can be accessed through Gateway.We can then authenticate the user's request at the service gateway level to determine whether the routed API interface can be accessed.
So let's start with more authentication, and here we're using jwt
1. Create authorization service module
Follow the second article to create a module named app-auth.
2. Modify the service-auth pom file
<properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</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> <version> 2.6.1</version> </dependency> <!-- jwt authentication --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.0</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
3. Modify Startup Class
@SpringBootApplication @EnableEurekaClient // Turn on eureka client mode public class AppAuthApplication { public static void main(String[] args) { SpringApplication.run(AppAuthApplication.class, args); } }
4. Add jwt Tool Class
Here we store both the generated token and refreshToken in redis, so we also introduced the redis dependency.To ensure some security, our token and refreshToken are both time-effective, and here we put the valid time of these two values in the configuration file for flexible modification.
When token is generated, we pass in account and client types, which are mainly divided into web browser, mobile app and desktop application.Each end has its own token.
// Generate token and refreshToken public Map<String, String> getToken(String phone, String type){ //Generate refreshToken String refreshToken = UUID.randomUUID().toString().replaceAll("-",""); String token = this.buildJWT(phone, type); String key = SecureUtil.md5(type + phone); //Put a value into hash stringRedisTemplate.opsForHash().put(key,"token", token); stringRedisTemplate.opsForHash().put(key,"refreshToken", refreshToken); //Set key expiration time stringRedisTemplate.expire(key, refreshTokenExpireTime, TimeUnit.MILLISECONDS); Map<String , String> map = new HashMap<>(2); map.put("token", token); map.put("refreshToken", refreshToken); return map; }
5. Write login interface
@GetMapping(value = "/login") public ResponseDTO<Map<String, String>> login(@RequestParam("account") String account, @RequestParam("password") String password, HttpServletRequest request){ String clientType = request.getHeader("clientType"); if(StrUtil.isEmpty(clientType)) { return ResponseDTO.error().setMsg("clientType Cannot be empty"); } //Account Password Verification if(StrUtil.isNotEmpty(account) && StrUtil.isNotEmpty(password)){ if ("admin".equals(account) && "123456a".equals(password)){ Map<String, String> map = tokenUtil.getToken(account, clientType); return ResponseDTO.ok().setData(map); }else { return ResponseDTO.error().setMsg("Account or password error"); } }else { return ResponseDTO.error().setMsg("Account or password error"); } }
For the time being, only the login methods are shown here, refresh the token method and exit the login method, check the git repository.
5. server-gateway adds Filter for privilege validation
There's more code here, just a few core codes
Create the RequestGlobalFilter class, add the @Component annotation, implement the GlobalFilter, Ordered interfaces, and implement the public Mono <Void> filter (ServerWebExchange exchange, GatewayFilterChain) method
Since this Filter intercepts all requests, we need to define in advance interfaces that are not intercepted, such as the login interface and the refresh token interface.We add ignore.urls to the server-gateway configuration file to place non-intercepting interfaces on this configuration separated by commas, for example:
ignore.urls=/auth/login,/authrefresh
For URLs that need to be intercepted, we need to get the token and clientType from the header, then verify that the token is valid using the jwt tool, and return a 401 error message if it is not valid.
private String verifyJWT(String token, String clientType) { String userPhone; try { Algorithm algorithm = Algorithm.HMAC256(clientType); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("userPhone") .build(); DecodedJWT jwt = verifier.verify(token); userPhone = jwt.getClaim("phone").asString(); } catch (JWTVerificationException e) { return ""; } return userPhone; }
Here we need to introduce a jwt dependency
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.0</version> </dependency>
6. Increase app-auth routing configuration
# Routing to app-auth service spring.cloud.gateway.routes[2].id=app-auth spring.cloud.gateway.routes[2].uri=lb://APP-AUTH spring.cloud.gateway.routes[2].predicates[0]=Path=/auth/** spring.cloud.gateway.routes[2].filters[0]=StripPrefix=1
7. Start a service test request
Start server-eureka, server-gateway, app-auth, app-order, app-storage, respectively
- Visit first http://127.0.0.1:10010/order/v1/order/placeOrder/commit will prompt "Authentication Failure"
- Then add the request headers Authorization and clientType to the request, and continue access will prompt "Authentication failed, please log in again".
- Request Logon Interface Http://127.0.0.1:10010/auth/login?Account=admin&password=123456a, and add the request header clientType to get token and refreshToken
- Put the token obtained from the previous step and the clientType in the request from the first step, and request again returns true.
Since there is a lot of code involved in this article, please move to the git repository to see more code. https://gitee.com/hedavid/spring-cloud-example