牛刀小试.开胃菜·代码思路·9-6
牛刀小试.开胃菜·代码思路·9-6
使用商机专员账号--公海池捞取数据时-提示没有操作权限
bug描述
bug描述
使用商机专员账号--公海池捞取数据时-提示没有操作权限
使用lisi账号进行商机捞取,提示没有操作权限

思路:
思路:
每个接口都有一个,用于标识符,当我们的用户具有对应接口标识符的时候,我们就判断用户具有该接口权限,这部分具体是如何做的参考
提示:
- 用户、角色、目录的关系?
- sys_menu表里的perms字段是什么意义?
- 每个接口上都有一个@PerAuthorize有什么意义?
@PreAuthorize("@ss.hasPermi('business:business:gainbussiness')")
改BUG步骤
1️⃣ 1.定位接口
BUG解决思路,通过前端F12来定位接口位置
商机捞取部分代码的位置
/**
* 批量捞取
*/
@PreAuthorize("@ss.hasPermi('business:business:gainbussiness')")
@Log(title = "批量捞取", businessType = BusinessType.UPDATE)
@PutMapping("/gain")
public AjaxResult gain(@RequestBody AssignmentVo assignmentVo) {
return AjaxResult.success(tbBusinessService.gain(assignmentVo.getIds(),assignmentVo.getUserId()));
}
2️⃣ 2.@PreAuthorize("@ss.hasPermi('xxx')")做了什么
注意:这里由于是接口资源鉴权,鉴权的是用户是否具有
business:business:gainbussiness
这是一个标识符,那么是如何判断的呢?
1.我们按住ctrl点击
hasPermi

2.跳转到
PermissionService
里看到如下内容

开始分析鉴权了!!!注意注意11
1.PermissionService与@ss
第一步 我们回到PermissionService
代码头部,可以看到@Service("ss")
将@Service
修饰的类存入到IOC容器中,并且声明一个别名叫ss方便调用
/**
* 自定义权限实现,ss取自SpringSecurity首字母
* @author wgl
*/
@Service("ss")
public class PermissionService {
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */
private static final String SUPER_ADMIN = "admin";
private static final String ROLE_DELIMETER = ",";
private static final String PERMISSION_DELIMETER = ",";
这里使用的是@ss.hasPermi
该部分就是 传入的参数就是(xxxx)里的内容,即:👇
business:business:gainbussiness
2.hasPermi方法
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission) {
if (StringUtils.isEmpty(permission)) {
return false;
}
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
return false;
}
return hasPermissions(loginUser.getPermissions(), permission);
}
这里我们传入的是
business:business:gainbussiness

3.LoginUser里的Permissions存储了什么
我们知道在登录的时候我们利用了返回了一个uuid和一些非敏感数据放在了token ⚠️里进行返回
提示
而uuid其实是redis里的key,而redis里的value是用户对象,通过解析jwt里的uuid去查询缓存就可以拿到对应的用户对象了
那么这个redis存储的用户对象里有些什么呢?❓ ❓
⚠️注意这部分存放的操作是在登录的时候执行的
在登录里由于使用的是SpringSecurity
框架去获取用户对象,如果这里同学你没学过SpringSecirity框架你可以通过注释的方式找到对应获取用户对象的位置

提示
如果没有阅读到注释,可以记住就是哪个类实现了SpringSecurity的UserDetailsService并且重写里面的loadUserByUsername方法获取用户对象
我们找到这部分代码
package com.huike.framework.web.service;
/**
* 用户验证处理
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private ISysUserService userService;
@Autowired
private SysPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userService.selectUserByUserName(username);
if (StringUtils.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", username);
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new BaseException("对不起,您的账号:" + username + " 已停用");
}
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user) {
return new LoginUser(user, permissionService.getMenuPermission(user));
}
}
警告
这部分代码就好比拆弹专家一样,如果剪错了,就抛异常。。。 如果不出问题,就正常登录通过了
在这部分代码中可以看到最终是调用了createLoginUser
方法,在该方法中调用了LoginUser的一个带参构造方法,该方法传入的是一个用户对象和Set集合permissions
public LoginUser(SysUser user, Set<String> permissions){
this.user = user;
this.permissions = permissions;
}
而传入的这个permissions是通过permissionService.getMenuPermission(user)这个方法获取的
/**
* 获取菜单数据权限
*
* @param user 用户信息
* @return 菜单权限信息
*/
public Set<String> getMenuPermission(SysUser user)
{
Set<String> perms = new HashSet<String>();
// 管理员拥有所有权限
if (user.isAdmin())
{
perms.add("*:*:*");
}
else
{
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
}
return perms;
}
持续跟进方法可以看到最终执行的sql语句是:
<!-- 通过用户名获得菜单权限 -->
<select id="selectMenuPermsByUserId" parameterType="Long" resultType="String">
select distinct m.perms
from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
left join sys_user_role ur on rm.role_id = ur.role_id
left join sys_role r on r.role_id = ur.role_id
where m.status = '0' and r.status = '0' and ur.user_id = #{userId} and m.menu_type != 'M'
</select>
通过这个sql语句可以知道,最终往用户里存入的是基于用户角色配置的所有的sys_menu下的所有接口(m.menu_type != 'M'),将这个set集合封装到了用户对象中并保存在redis中
到此我们先复盘一下

调用hasPermi方法的时候获取的loginUser里已经包含了所有的接口的权限标识符,并且是一个set集合
然后调用hasPermissions方法进行判断 👈 👇
4.hasPermissions方法
/**
* 判断是否包含权限
*
* @param permissions 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
private boolean hasPermissions(Set<String> permissions, String permission){
return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}
提示
判断用户拥有的这个set集合里是否包含了,
- 如果包含了
- 证明可以请求,
- 如果不包含
- 证明没有权限访问接口
到此,所有的分析就基本上结束了 🎉 🎉
3️⃣ 3.改BUG
通过上述的分析,我们知道
business:business:gainbussiness
那么我们来看一下商机捞取应该是什么样的
我们在mysql里执行一下sql语句
SELECT * FROM `sys_menu` WHERE menu_name LIKE '%捞%'
看到如下内容:

我们可以看到商机捞取对应的权限标识是
business:business:gain
我们就该我们商机捞取上的权限标识与数据库中的权限标识对应即可
/**
*批量捞取
*/
@PreAuthorize("@ss.hasPermi('business:business:gain')")
@Log(title = "批量捞取", businessType = BusinessType.UPDATE)
@PutMapping("/gain")
public AjaxResult gain(@RequestBody AssignmentVo assignmentVo) {
return AjaxResult.success(tbBusinessService.gain(assignmentVo.getIds(),assignmentVo.getUserId()));
}