若依认证鉴权实现原理
2021/8/28 23:06:08
本文主要是介绍若依认证鉴权实现原理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一、什么是认证鉴权
通俗来说,认证就是系统用户通过提供系统颁发给自己的信任凭证(如用户名和密码)登录系统,系统对用户提交的凭证进行验证这个过程。一般情况下,认证成功之后,系统会给用户分发令牌,令牌由用户代理客户端(如浏览器)存储,当用户需要请求系统资源时候,客户端将令牌传递给系统,系统通过检验令牌来核实访问的用户是谁,这样避免了用户每次获取系统资源都需要提供信任凭证。
鉴权,有时候也可以说是授权,是指用户在认证成功之后,系统按照之前的约定授予用户可访问的资源的权限,当用户发起对资源的请求的时候,通过鉴别已授予用户的资源和当前要访问的资源是否一致,来做数据的隔离。
可以看到,无论是认证还是授权,本质都是为了维护系统的安全性。在SpringBoot框架下,常见的安全框架有 SpringSecurity 和 Shiro 。
SpringSecurity官网:https://spring.io/projects/spring-security#overview
Shiro官网:http://shiro.apache.org/
二、ruoyi认证鉴权概述
在ruoyi微服务项目中,既没有用到 SpringBootSecurity 这个安全框架,也没有用到 Shiro 这个安全框架。
其认证鉴权流程大致为:用户输入用户名密码登录;系统校验用户名密码是否正确;生成uuid作为token返回给用户,并存储到redis;查询用户拥有的角色和权限并存储到redis;请求资源的时候将token转化为userId、userName存储到请求头中;根据 token 查询redis缓存中的权限并和目标资源上标注的权限名称做比对,比对成功即鉴权成功。
三、ruoyi认证鉴权实现原理
1:Auth项目的 TokenController 提供 login 方法登录
package com.ruoyi.auth.controller; @RestController public class TokenController{ @PostMapping("login") public R<?> login(@RequestBody LoginBody form) { // 用户登录 LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword()); // 获取登录token return R.ok(tokenService.createToken(userInfo)); } }
2:通过 FeignClient 调用 System 根据 userName 获取用户信息(包含基本信息,角色信息,权限信息)
package com.ruoyi.system.controller; @RestController@RequestMapping("/user") public class SysUserController extends BaseController{ /** * 获取当前用户信息 */ @InnerAuth @GetMapping("/info/{username}") public R<LoginUser> info(@PathVariable("username") String username) { SysUser sysUser = userService.selectUserByUserName(username); // 角色集合 Set<String> roles = permissionService.getRolePermission(sysUser.getUserId()); // 权限集合 Set<String> permissions = permissionService.getMenuPermission(sysUser.getUserId()); LoginUser sysUserVo = new LoginUser(); sysUserVo.setSysUser(sysUser); sysUserVo.setRoles(roles); sysUserVo.setPermissions(permissions); return R.ok(sysUserVo); }
3:将 token 和用户的角色权限信息存储到 redis
package com.ruoyi.common.security.service; @Componentpublic class TokenService{ /** * 创建令牌 */ public Map<String, Object> createToken(LoginUser loginUser) { // 生成token String token = IdUtils.fastUUID(); loginUser.setToken(token); loginUser.setUserid(loginUser.getSysUser().getUserId()); loginUser.setUsername(loginUser.getSysUser().getUserName()); loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest())); refreshToken(loginUser); // 保存或更新用户token Map<String, Object> map = new HashMap<String, Object>(); map.put("access_token", token); map.put("expires_in", EXPIRE_TIME); redisService.setCacheObject(ACCESS_TOKEN + token, loginUser, EXPIRE_TIME, TimeUnit.SECONDS); return map; } }
4:请求资源的时候,由网关中的全局过滤器从请求头中获取token,并根据token查询出 userId 和 userName,并把他们存储到请求头中,相当于在请求头中增加了userId 和userName ,然后放行该请求,该请求根据网关转发规则转发到了资源实际的微服务中。
package com.ruoyi.gateway.filter; @Component public class AuthFilter implements GlobalFilter, Ordered{ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ...... String userStr = sops.get(getTokenKey(token)); JSONObject cacheObj = JSONObject.parseObject(userStr); String userid = cacheObj.getString("userid"); String username = cacheObj.getString("username"); // 设置过期时间 redisService.expire(getTokenKey(token), EXPIRE_TIME); // 设置用户信息到请求 addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid); addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username); // 内部请求来源参数清除 removeHeader(mutate, SecurityConstants.FROM_SOURCE); return chain.filter(exchange.mutate().request(mutate.build()).build()); } }
5:当请求到达资源服务器之后,通过 Controller 层的自定义注解 PreAuthorize 判断用户是否有权限访问该资源,注解中注明了此资源所需要的权限。
package com.ruoyi.system.controller; @RestController@RequestMapping("/user") public class SysUserController extends BaseController{ /** * 获取用户列表 */ @PreAuthorize(hasPermi = "system:user:list") @GetMapping("/list") public TableDataInfo list(SysUser user) { startPage(); List<SysUser> list = userService.selectUserList(user); return getDataTable(list); } }
6:自定义注解 PreAuthorize 实现原理为根据 token 从redis 中查询该用户拥有的权限,和注解中 注明的权限名称做比较。
package com.ruoyi.common.security.aspect; @Aspect @Component public class PreAuthorizeAspect{ ...... /** * 验证用户是否具备某权限 * * @param permission 权限字符串 * @return 用户是否具备某权限 */ public boolean hasPermi(String permission) { LoginUser userInfo = tokenService.getLoginUser(); return hasPermissions(userInfo.getPermissions(), permission); } ...... /** * 判断是否包含权限 * * @param authorities 权限列表 从 redis 中获取 * @param permission 权限字符串 system:user:list * @return 用户是否具备某权限 */ private boolean hasPermissions(Collection<String> authorities, String permission) { return authorities.stream().filter(StringUtils::hasText) .anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission)); } }
7:全部鉴权方式
hasPermi:是否有某权限
lacksPermi:是否无某权限
hasAnyPermi:是否有以下权限的一种
hasRole:是否有某角色
lacksRole:是否无某角色
hasAnyRoles:是否有以下角色的一种
四、总结
若依提供的认证鉴权方式较为原始,甚至都没有集成到Spring容器中,提供的功能也比较单一,扩展性不强,不建议在中大型企业级项目中运用。
五、引用
https://spring.io/projects/spring-security#overview
http://shiro.apache.org/
https://www.yinxiang.com/everhub/note/b1425f79-3086-4f26-9f6f-430a979f96e2
这篇关于若依认证鉴权实现原理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南