之前我们把redis缓存工具模块做好了、下面结合RBAC权限模型,我们来进行用户的认证鉴权设计。关于RBAC权限模型在之前的文章跟网上都有很多很详细的描述,这里就简单说一下、就是通过用户关联角色(多对多)、角色关联权限(多对多)、并且大部分系统还在角色与角色之间做了上下级关系。用来控制下级角色不能拥有上级角色没有的权限。有些系统还是组合权限的形式、给机构(部门)也跟角色挂钩。具体的根据业务需求来做就好了、反正最后我们的目的就是通过URL跟用户的token来校验当前用户是否拥有该URL的权限。
Spring Security跟shiro等安全框架我们就不使用了、直接使用URL在网关进行权限验证就好。首先先来看看我们的认证、然后在看授权。
登录认证
在用户进行登录的时候、我们就不使用传统的cookie来存储用户信息了、而是使用token来进行存储。这里的token可以是jwtToken也可以是我们自定义的token、需要注意的是自定义的token是需要对返回的token内容进行加密的。请求上来过后、需要先解密token在进行认证。
这里我们需要对token的内容做一些设计、保证唯一性(比如雪花字符串、UUID)的同时、要把用户ID、生成时间等信息也存储进去、过期时间则不需要了(当然也可以加上去、用来提示token过期)。过期时间由redis来进行管理。按照一定的组合规则来组装token、比如使用@来分割token字符串。拼接好过后,在使用AES、SM2等对称加密算法对token进行加密。当然AES算法记得是使用CBC模式,并且长度要128位才行,否则容易被破解。
登录成功后、在返回token之前记得将用户的个人信息、角色信息缓存到redis当中去。并记得设置key的时效。不能长期有效的存储。
URL鉴权
通过上面的登录、我们已经将用户的个人信息、角色信息都缓存进去了。看一下上面我们设计的模式是用户关联角色、角色关联权限。那么现在拿到了用户角色信息只需要加上角色权限的信息就能鉴权了。
初始化角色权限信息
在系统启动的时候、我们需要对角色的权限做一次初始化动作。只需要实现CommandLineRunner接口即可:
/*** 启动执行权限初始化*/
@Component
@Slf4j
@RequiredArgsConstructor
public class AuthCommandLineRunnerImpl implements CommandLineRunner {private final RedisDao<String, OrgAuth> orgAuthRedisDao;private final RedisDao<String, RoleAuth> roleAuthRedisDao;@Overridepublic void run(String... args){try {initRoleAuth();log.debug(">>>>>>>>>>>>>>>>>>>>初始化业务角色权限成功");}catch (Exception e){log.error("初始化权限出错:{}",e.toString());}}}
需要注意的是、我们在网关进行鉴权时、只能拿到用户角色、权限、跟URI。那么最快的访问方式应该是角色编码+URI作为redis缓存的key。鉴权的时候只需要拿到用户的角色编码+URL去redis获取、获取到了就有权限、获取不到就没有权限。
初始化设置角色权限
/*** 设置URI权限* @param list* @param key CacheConstant.AUTH_ROLE_KEY+角色编码+URI*/
private void setUrlAuth(List<Auth> list,String key){if(list == null || list.size() == 0){return;}String authUri = null;for(Auth a : list){authUri = a.getAuthUri();if(StringUtils.isNotBlank(authUri)){authRedisDao.setKey(key+":"+authUri.replaceAll("\\/","-"),1);}}
}
验证角色权限
/*** 角色鉴权* @param user* @return*/
private boolean roleAuth(UserInfo user,String uri){List<String> roleList = user.getRoleList();int size = roleList.size();for(int i=0;i<size;i++){// 遍历角色权限if(auth(roleList.get(i),uri)){return true;}}return false;
}
/*** 权限匹配* @param code 编码* @param uri 鉴权的uri* @return*/
private boolean auth(String code,String uri){uri = uri.replaceAll("\\/","-");Integer a = authRedis.getValue(CacheConstant.AUTH_ROLE_KEY+code+":"+uri);if(a == null || a.intValue() != 1){// 没有权限return false;}return true;
}