写在最前
如果这个项目让你有所收获,记得 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 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
即可
com.csp.mingyue
mingyue-common-security
启动测试
测试 【前情回顾】中的接口,如: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 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. 直接调用
返回:认证失败
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
}
]
}
小结
登录认证算是完成一小半了,没错才一小半,路漫漫其修远兮~
接下来我们设计一下数据库权限模型,也就是用户、角色、菜单的关系。