写在最前
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
源码地址(后端):https://gitee.com/csps/mingyue
源码地址(前端):https://gitee.com/csps/mingyue-ui
文档地址:https://gitee.com/csps/mingyue/wikisapplication-common.yml
前情回顾
截止目前我们的系统并未增加拦截鉴权,都是直接放行。接下来我列举两个接口,测试一下未加拦截鉴权的访问,结果都是直接返回。
用户信息接口
非网关: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 全局过滤器*/@Beanpublic SaReactorFilter getSaReactorFilter(IgnoreWhiteProperties ignoreWhite) {return new SaReactorFilter()// 拦截地址.addInclude("/**")// 开放地址.addExclude("/favicon.ico", "/actuator/**")// 鉴权方法:每次访问进入.setAuth(obj -> {// 登录校验 -- 拦截所有路由SaRouter.match("/**").notMatch(ignoreWhite.getWhites()).check(r -> {// 检查是否登录 是否有tokenStpUtil.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 的拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册路由拦截器,自定义验证规则registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");}/*** 校验是否从网关转发*/@Beanpublic 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 {@Overridepublic 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);}@Overridepublic int getOrder() {return -100;}}
此时我们再打开接口文档,查看是否展示正常即可。
获取所有用户信息
接口下演示调用【获取所有用户信息】,看看增加了拦截器后如何访问该接口
1. 直接调用
返回:认证失败
2. 登录接口获取 Token
data: hiSTb6JAQrA9LTOhYuA27UD2LhGBI40x
3. 切换 system 进行 Authorize 认证
点击
Authorize
按钮,Value
中录入登录接口获取的 Token,再次点击Authorize
按钮确定即可。
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}]
}
小结
登录认证算是完成一小半了,没错才一小半,路漫漫其修远兮~
接下来我们设计一下数据库权限模型,也就是用户、角色、菜单的关系。