• 若依认证鉴权实现原理


    一、什么是认证鉴权

    通俗来说,认证就是系统用户通过提供系统颁发给自己的信任凭证(如用户名和密码)登录系统,系统对用户提交的凭证进行验证这个过程。一般情况下,认证成功之后,系统会给用户分发令牌,令牌由用户代理客户端(如浏览器)存储,当用户需要请求系统资源时候,客户端将令牌传递给系统,系统通过检验令牌来核实访问的用户是谁,这样避免了用户每次获取系统资源都需要提供信任凭证。

    鉴权,有时候也可以说是授权,是指用户在认证成功之后,系统按照之前的约定授予用户可访问的资源的权限,当用户发起对资源的请求的时候,通过鉴别已授予用户的资源和当前要访问的资源是否一致,来做数据的隔离。

    可以看到,无论是认证还是授权,本质都是为了维护系统的安全性。在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

    邮箱:cnaylor@163.com
    技术交流QQ群:1158377441
  • 相关阅读:
    [转]Flash Builder 4安装SVN插件
    [转]ActionScript为什么不支持函数重载
    [转]Flash Builder 4 官网下载、安装与注册
    ActionScript3.0导入XML数据
    Flex与.NET互操作(六):Flex和.NET协同开发利器FluorineFx
    Flex与.NET互操作(八):使用FluorineFx网关实现远程访问
    Flex—鼠标样式设置
    Shape、Sprite 和 MovieClip 对象的 graphics 属性(graphics类)的简单用法
    IN&EXISTS与NOT IN&NOT EXISTS 的优化原则的讨论
    Windows 无法启动 SQL Server (MSSQLSERVER) 服务(位于 本地计算机 上)。错误 1067: 进程意外终止。
  • 原文地址:https://www.cnblogs.com/Naylor/p/15201427.html
Copyright © 2020-2023  润新知