likes
comments
collection
share

016-从零搭建微服务-认证中心(七)

作者站长头像
站长
· 阅读数 15

写在最前

如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。

源码地址(后端):gitee.com/csps/mingyu…

源码地址(前端):gitee.com/csps/mingyu…

文档地址:gitee.com/csps/mingyu…

前情回顾

截止目前我们的系统并未增加拦截鉴权,都是直接放行。接下来我列举两个接口,测试一下未加拦截鉴权的访问,结果都是直接返回。

用户信息接口

非网关:http://mingyue-gateway:8000/sysUser/getSysUserInfoByUsername?username=mingyue

网关:http://mingyue-gateway:9100/system/sysUser/getSysUserInfoByUsername?username=mingyue

{
    "code": 200,
    "msg": "操作成功",
    "data": [
        {
            "userId": 1,
            "username": "mingyue",
            "nickname": "明月",
            "sex": "0",
            "password": "123456",
            "phone": "13260718262",
            "email": null,
            "avatar": null,
            "lockFlag": "0",
            "delFlag": "0",
            "createTime": null,
            "updateTime": null,
            "createBy": null,
            "updateBy": null
        }
    ]
}

登录接口

非网关:http://mingyue-gateway:9000/login

网关:http://mingyue-gateway:9100/auth/login

curl -X 'POST' \
  'http://mingyue-gateway:9100/auth/login' \
  -H 'accept: */*' \
  -H 'Content-Type: application/json' \
  -d '{
  "username": "mingyue",
  "password": "123456"
}'

响应

{
  "code": 200,
  "msg": "登录成功",
  "data": "pVMCmkG1Q320sbKOJxzxjBXOOYNOT1MH"
}

网关注册权限认证拦截器

项目中所有接口均需要登录认证,只有 “登录接口” 本身对外开放

我们怎么实现呢?给每个接口加上鉴权注解?手写全局拦截器?似乎都不是非常方便。

在这个需求中我们真正需要的是一种基于路由拦截的鉴权模式,那么在 Sa-Token 怎么实现路由拦截鉴权呢?

白名单

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
 * 放行白名单配置
 *
 * @author Strive
 */
@Data
@NoArgsConstructor
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties {

  /**
   * 放行白名单配置,网关不校验此处的白名单
   */
  private List<String> whites = new ArrayList<>();

}

mingyue-gateway.yml Nacos 增加白名单配置

# 安全配置
security:
  # 不校验白名单
  ignore:
    whites:
      - /**/v3/api-docs
      - /v3/api-docs/**
      - /webjars/**
      - /auth/oauth2/**
      - /auth/logout
      - /auth/login

网关统一鉴权

import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.csp.mingyue.common.core.constant.HttpStatus;
import com.csp.mingyue.gateway.support.IgnoreWhiteProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * [Sa-Token 权限认证] 拦截器
 *
 * @author Strive
 */
@Configuration
public class AuthFilter {

  /**
   * 注册 Sa-Token 全局过滤器
   */
  @Bean
  public SaReactorFilter getSaReactorFilter(IgnoreWhiteProperties ignoreWhite) {
    return new SaReactorFilter()
        // 拦截地址
        .addInclude("/**")
        // 开放地址
        .addExclude("/favicon.ico", "/actuator/**")
        // 鉴权方法:每次访问进入
        .setAuth(obj -> {
          // 登录校验 -- 拦截所有路由
          SaRouter.match("/**").notMatch(ignoreWhite.getWhites()).check(r -> {
            // 检查是否登录 是否有token
            StpUtil.checkLogin();
          });
        }).setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
  }

}

启动测试

访问接口:网关:http://mingyue-gateway:9100/system/sysUser/getSysUserInfoByUsername?username=mingyue,响应如下:

{
"code": 401,
"msg": "认证失败,无法访问系统资源",
"data": null
}

网关拦截成功,此时我们直接通过非网关访问,发现接口是可以取到数据的。测试:http://mingyue-gateway:8000/sysUser/getSysUserInfoByUsername?username=mingyue,响应如下:

{
    "code": 200,
    "msg": "操作成功",
    "data": [
        {
            "userId": 1,
            ...
        }
    ]
}

这肯定是不可以的,接下来我们需要给非网关的服务增加拦截器~~~,防止直接访问!

微服务(非网关)拦截鉴权

注册 Sa-Token 路由拦截器

mingyue-common-security 模块注册 Sa-Token 路由拦截器

import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.same.SaSameUtil;
import cn.dev33.satoken.util.SaResult;
import com.csp.mingyue.common.core.constant.HttpStatus;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 权限安全配置
 *
 * @author Strive
 */
@AutoConfiguration
public class SecurityConfiguration implements WebMvcConfigurer {

  /**
   * 注册 sa-token 的拦截器
   */
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 注册路由拦截器,自定义验证规则
    registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
  }

  /**
   * 校验是否从网关转发
   */
  @Bean
  public SaServletFilter getSaServletFilter() {
    return new SaServletFilter()
    .addInclude("/**")
    .addExclude("/actuator/**").setAuth(obj -> {
      SaSameUtil.checkCurrentRequestToken();
    }).setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
  }

}

自动装配导入添加:org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.csp.mingyue.common.security.config.SecurityConfiguration

微服务引入 mingyue-common-security

非网关服务直接引入 mingyue-common-security 即可

<!-- 认证工具类 -->
<dependency>
  <groupId>com.csp.mingyue</groupId>
  <artifactId>mingyue-common-security</artifactId>
</dependency>

启动测试

测试 【前情回顾】中的接口,如:http://mingyue-gateway:8000/sysUser/getSysUserInfoByUsername?username=mingyue,返回如下

{
"code": 401,
"msg": "认证失败,无法访问系统资源",
"data": null
}

Feign 调用鉴权处理

此时网关服务、系统服务、认证中心都已经增加了权限拦截,此时我们打开接口文档,页面打印错误如下:

Unable to render this definition
The provided definition does not specify a valid version field.

Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: "2.0" and those that match openapi: 3.0.n (for example, openapi: 3.0.0).

这是因为【网关服务】虽然放行了接口文档,但是【系统服务、认证中心】并未放行,所以出现错误。我们需要转发认证过滤器(内部服务外网隔离) 为请求添加 Same-Token

转发认证过滤器

为请求追加 Same-Token 参数

import cn.dev33.satoken.same.SaSameUtil;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 转发认证过滤器(内部服务外网隔离) 为请求添加 Same-Token
 *
 * @author Strive
 */
@Component
public class ForwardAuthFilter implements GlobalFilter, Ordered {

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest newRequest = exchange.getRequest().mutate()
        // 为请求追加 Same-Token 参数
        .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken()).build();

    ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
    return chain.filter(newExchange);
  }

  @Override
  public int getOrder() {
    return -100;
  }

}

此时我们再打开接口文档,查看是否展示正常即可。

获取所有用户信息

接口下演示调用【获取所有用户信息】,看看增加了拦截器后如何访问该接口

1. 直接调用

返回:认证失败

016-从零搭建微服务-认证中心(七)

2. 登录接口获取 Token

data: hiSTb6JAQrA9LTOhYuA27UD2LhGBI40x

016-从零搭建微服务-认证中心(七)

3. 切换 system 进行 Authorize 认证

点击 Authorize 按钮,Value 中录入登录接口获取的 Token,再次点击 Authorize 按钮确定即可。

016-从零搭建微服务-认证中心(七)

4. 再次访问获取所有用户信息接口

接口正常返回,输出如下:

{
  "code": 200,
  "msg": "操作成功",
  "data": [
    {
      "userId": 1,
      "username": "mingyue",
      "nickname": "明月",
      "sex": "0",
      "password": "123456",
      "phone": "13260718262",
      "email": null,
      "avatar": null,
      "lockFlag": "0",
      "delFlag": "0",
      "createTime": null,
      "updateTime": null,
      "createBy": null,
      "updateBy": null
    }
  ]
}

小结

登录认证算是完成一小半了,没错才一小半,路漫漫其修远兮~

接下来我们设计一下数据库权限模型,也就是用户、角色、菜单的关系。