Spring Cloud Micro Service Architecture From Getting Started to Getting Used - Service Gateway Authentication

Keywords: Programming Spring Java Redis Junit

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

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

Posted by Zero20two on Tue, 24 Mar 2020 10:07:39 -0700