ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

若依认证鉴权实现原理

2021-08-28 23:03:58  阅读:361  来源: 互联网

标签:return 用户 认证 若依 public token loginUser 权限 鉴权


一、什么是认证鉴权

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

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

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

标签:return,用户,认证,若依,public,token,loginUser,权限,鉴权
来源: https://www.cnblogs.com/Naylor/p/15201427.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有