赞
踩
鉴权,也被称为权限控制,是一种在计算机网络中保护用户对特定资源的使用,防止未经授权的访问的技术手段。
它是在信息系统、网络或应用中,确定用户的身份是否合法,并根据用户的身份和权限,决定其在系统中能够访问哪些资源或执行哪些操作的过程。这个过程通常包括身份验证和权限验证两个阶段。
鉴权是一种重要的安全机制,通过合理的身份验证和授权策略,以及采用多种安全措施,可以有效地防止网络攻击和滥用行为,保护网络中的资源不被未经授权的用户使用。
在Java开发环境非常成熟的当下,框架的产生可谓是日新月异,在近十年的Java开发中,有如下权限框架非常火爆。
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
三个核心组件:Subject、 SecurityManager 、 Realms。
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
Spring Security本质上就是一系列的过滤器。
它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Spring Security对Web安全性的支持大量地依赖于Servlet过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性。根据自己的需要,可以使用适当的过滤器来保护自己的应用程序。
1·AuthenticationManager
用于处理用户认证。它是一个身份验证管理器,负责协调和执行身份验证过程。AuthenticationManager接受一个Authentication对象作为输入,并返回一个已经填充了身份验证信息的完整的Authentication对象。
2.DaoAuthenticationProvider
是AuthenticationProvider接口的一个实现类。DaoAuthenticationProvider使用一个UserDetailsService来获取用户的详细信息,并使用PasswordEncoder对用户提供的凭据进行验证。它的主要作用是从数据库或其 他持久化存储中获取用户信息,并进行身份验证。
3.AuthenticationProvider
AuthenticationProvider是一个接口,定义了身份验证提供者的通用行为。它是一个抽象的身份验证机制,可以有多个实现类。DaoAuthenticationProvider就是AuthenticationProvider接口的一个实现类之一。通过实现AuthenticationProvider接口,可以自定义身份验证逻辑。
Sa-Token官网:https://sa-token.cc/doc.html#/
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。
SpringBoot 2.x
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
SpringBoot 3.x
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.37.0</version>
</dependency>
分布式
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: true
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true
本文主要记录Sa-Token在分布式Gateway环境下的鉴权方式,想进一步了解Sa-Token请看官网。
用户表
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号',
`sex` int NOT NULL DEFAULT '0' COMMENT '0男,1女,2其他',
`avatar` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '头像',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '签名',
`identify_card` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '身份证号',
`status` bit(1) DEFAULT NULL COMMENT '当前用户是否被冻结',
`travel_official` bit(1) DEFAULT b'0' COMMENT '是否进行了官方认证',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `PHONE_INDEX` (`phone`) USING BTREE,
UNIQUE KEY `IDENTIFY_CARD_INDEX` (`identify_card`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=91706 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
角色表
CREATE TABLE `roles` (
`id` int NOT NULL AUTO_INCREMENT,
`role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
角色用户映射表
CREATE TABLE `user_role_relationship` (
`user_id` int NOT NULL,
`role_id` int NOT NULL,
PRIMARY KEY (`user_id`,`role_id`) USING BTREE,
UNIQUE KEY `USER_ROLE_INDEX` (`user_id`,`role_id`) USING BTREE,
KEY `ROLE_ID` (`role_id`) USING BTREE,
CONSTRAINT `ROLE_ID` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `USER_ID` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
菜单表
CREATE TABLE `menu` (
`id` int NOT NULL AUTO_INCREMENT,
`menu_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`menu_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`need_login` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
菜单角色映射表
CREATE TABLE `role_menu_relationship` (
`roles_id` int NOT NULL,
`menu_id` int NOT NULL,
PRIMARY KEY (`roles_id`,`menu_id`) USING BTREE,
UNIQUE KEY `ROLE_MENU_INDEX` (`roles_id`,`menu_id`) USING BTREE,
KEY `MENU_INDEX` (`menu_id`) USING BTREE,
CONSTRAINT `MENU_INDEX` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `ROLE_INDEX` FOREIGN KEY (`roles_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
在系统中需要Sa-Token整合Redis实现登录持久化记录,所以需要引入:sa-token-redis-jackson
、commons-pool2
两个依赖。
官网解释:https://sa-token.cc/doc.html#/up/integ-redis
<dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-reactor-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-redis-jackson</artifactId> <version>1.37.0</version> </dependency> <!-- 提供Redis连接池 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
spring: redis: database: 0 host: 127.0.0.1 port: 6379 timeout: 10s lettuce: pool: # 连接池最大连接数 max-active: 200 # 连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms # 连接池中的最大空闲连接 max-idle: 10 # 连接池中的最小空闲连接 min-idle: 0 sa-token: token-name: authentication timeout: 604800 token-style: simple-uuid is-share: true is-concurrent: true is-log: false is-read-cookie: false is-read-head: true
623368f0-ae5e-4475-a53f-93e4225f16ae
6fd4221395024b5f87edd34bc3258ee8
qEjyPsEA1Bkc9dr8YP6okFr5umCZNR6W
v4ueNLEpPwMtmOPMBtOOeIQsvP8z9gkMgIVibTUVjkrNrlfra5CGwQkViDjO8jcc
nojYPmcEtrFEaN0Otpssa8I8jpk8FO53UcMZkCP9qyoHaDbKS6dxoRPky9c6QlftQ0pdzxRGXsKZmUSrPeZBOD6kJFfmfgiRyUmYWcj4WU4SSP2ilakWN1HYnIuX0Olj
gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__
@Configuration @RequiredArgsConstructor public class SaTokenConfig { @Lazy @Resource private UserFeign userFeign; private final RedisTemplate<String, Object> redisTemplate; @Bean public SaReactorFilter getSaReactorFilter() { return new SaReactorFilter() .addInclude("/**") .setAuth(obj -> { String travelId = SaHolder.getRequest().getHeader("authentication"); Object cache = redisTemplate.opsForValue() .get(SystemProperties.USER_INFO_PREFIX + StpUtil.getLoginIdByToken(travelId)); if (travelId != null && cache == null) { throw new RuntimeException(); } executorMenuPath(); }) .setError(e -> { e.printStackTrace(); ServerWebExchange exchange = SaReactorSyncHolder.getContext(); exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8"); return SaResult.get(HttpStatus.BAD_REQUEST.value(), "请先进行登录", null); }) // 跨域信息 .setBeforeAuth(obj -> { SaHolder.getResponse() .setHeader("Access-Control-Allow-Origin", "*") .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE") .setHeader("Access-Control-Max-Age", "3600") .setHeader("Access-Control-Allow-Headers", "*"); SaRouter.match(SaHttpMethod.OPTIONS).back(); }); } // 进行权限判断 private void executorMenuPath(){ Result<Map<String, List<Menu>>> systemMenu = userFeign.allMenu(); if(systemMenu.getCode() != HttpStatus.OK.value()) return; Map<String, Menu> path = new HashMap<>(); systemMenu.getBody().forEach((key, value) -> { value.forEach(menu -> path.put(menu.getMenuPath(), menu)); }); Menu menu = path.get(SaHolder.getRequest().getRequestPath()); if(menu.isNeedLogin()) StpUtil.checkLogin(); } }
StpInterface接口是用来获取用户所拥有的权限和可访问路径的接口。
Sa-Token的鉴权方式就是会调用这个接口的实现类。
@Component public class StpInterceptorImpl implements StpInterface { @Resource @Lazy private UserFeign userFeign; // 获取当前用户可以访问的路径 @Override public List<String> getPermissionList(Object o, String s) { Result<List<Menu>> response = userFeign.userAccessMenuByRoles(); if(response.getCode() == HttpStatus.OK.value()){ return response.getBody().stream().map(Menu::getMenuPath).toList(); } return Collections.emptyList(); } // 获取当前用户的所有角色 @Override public List<String> getRoleList(Object o, String s) { Result<List<Role>> response = userFeign.roleList(); if(response.getCode() == HttpStatus.OK.value()){ return response.getBody().stream().map(Role::getRoleName).toList(); } return Collections.emptyList(); } }
StpUtil.checkLogin()
和StpUtil.checkRole()
方法都是有顺序的,不能直接对系统中的所有路径进行配置,而是需要对当前请求的路径做配置。Shior正在逐步退出国内市场,原因有如下几个:
Spring Security 并没有逐渐退出国内市场,在国内外市场,Spring Security 的使用仍然非常广泛。许多大型企业和关键基础设施都在使用 Spring Security 来保护其应用程序和数据。同时,Spring Security 也在不断进行更新和改进,以适应不断变化的安全威胁和需求。
阿里开源的轻量级权限框架,伴随简便的开发模式在国内越来越流行。
使用Sa-Token的理由主要有以下几点:
最为后端开发者我个人是非常喜欢Sa-Token的简单开发模式。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。