Spring Cloud微服务架构从入门到会用(五)—服务网关鉴权

上一篇文章我们集成了服务网关Spring Cloud Gateway,所有的服务请求都可以通过Gateway访问。那我们就可在服务网关这一层对用户的请求进行鉴权,判断是否可以访问路由的API接口。

加下来我们开始增加鉴权,这里我们使用jwt

1. 创建授权服务module

按照第二篇文章创建一个module,起名为app-auth。

2. 修改service-auth的pom文件
<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鉴权  -->
        <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. 修改启动类
@SpringBootApplication
@EnableEurekaClient  // 开启eureka客户端模式
public class AppAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AppAuthApplication.class, args);
    }
}
4. 增加jwt工具类

这里我们将生成的token和refreshToken都存储到redis中,所以我们也引入了redis依赖。为了保证一定的安全性,我们的token和refreshToken都是有时效的,这里我们将这两个值的有效时间放到配置文件中,以便灵活修改。

在生成token的时候,我们将账号和客户端类型传入,客户端类型主要分为,网页浏览器端,手机app端和桌面程序端。三端都有各自的token。

// 生成token和refreshToken
public Map<String, String> getToken(String phone, String type){
        //生成refreshToken
        String refreshToken = UUID.randomUUID().toString().replaceAll("-","");
        String token = this.buildJWT(phone, type);
        String key = SecureUtil.md5(type + phone);
        //向hash中放入数值
        stringRedisTemplate.opsForHash().put(key,"token", token);
        stringRedisTemplate.opsForHash().put(key,"refreshToken", refreshToken);
        //设置key过期时间
        stringRedisTemplate.expire(key, refreshTokenExpireTime, TimeUnit.MILLISECONDS);
        Map<String , String> map = new HashMap<>(2);
        map.put("token", token);
        map.put("refreshToken", refreshToken);
        return map;
    }
5. 编写登录接口
@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不能为空");
        }
        //账号密码校验
        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("账号或密码错误");
            }
        }else {
            return ResponseDTO.error().setMsg("账号或密码错误");
        }
    }

此处暂时只展示登录方法,刷新token方法和退出登录方法请查看git仓库。

5. server-gateway增加Filter,用于权限验证

此处代码较多,只展示部分核心代码

创建RequestGlobalFilter类,增加@Component注解,实现GlobalFilter, Ordered这两个接口;实现public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) 方法

由于这个Filter拦截所有请求,所以我们要提前定义一下不拦截的接口,例如登录接口和刷新token的接口。我们在server-gateway的配置文件中增加ignore.urls,把不拦截的接口用逗号分隔的方式放到这个配置上,例如:

ignore.urls=/auth/login,/authrefresh

对于需要拦截的url,我们需要从header中拿到token和clientType,然后通过jwt工具进行校验该token是否有效,如果无效返回401错误信息。

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;
    }

在此处我们需要引入jwt依赖

<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.0</version>
        </dependency>
6. 增加app-auth路由配置
# 路由到app-auth服务
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. 启动服务测试请求

分别启动server-eureka,server-gateway,app-auth,app-order,app-storage

  • 先访问http://127.0.0.1:10010/order/v1/order/placeOrder/commit会提示“认证失败”
  • 然后在请求中增加请求头Authorization和clientType,继续访问会提示“认证失败,请重新登录”。
  • 请求登录接口http://127.0.0.1:10010/auth/login?account=admin&password=123456a,并添加请求头clientType,得到token和refreshToken
  • 把上一步得到的token和使用clientType放到第一步的请求中,再次请求,会返回true。

由于本文涉及的代码偏多,请大家移步至git仓库查看更多代码。
https://gitee.com/hedavid/spring-cloud-example

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值