用户授权(控制用户访问资源权限)

RBAC

如何实现授权?业界通常基于RBAC实现授权。

RBAC分为两种方式:

基于角色的访问控制(Role-Based Access Control)

基于资源的访问控制(Resource-Based Access Control)

角色的访问控制(Role-Based Access Control)是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:

根据上图中的判断逻辑,授权代码可表示如下:

if(主体.hasRole("总经理角色id")){
查询工资
}

如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断用户的角色是否是总经理或部门经理”,修改代码如下:

if(主体.hasRole("总经理角色id") ||  主体.hasRole("部门经理角色id")){
    查询工资
}

 

 

根据上边的例子发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可扩展性差。

基于资源的访问控制(Resource-Based Access

Control)是按资源(或权限)进行授权,比如:用户必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:

根据上图中的判断,授权代码可以表示为:

if(主体.hasPermission("查询工资权限标识")){
    查询工资
}

优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改授权代码,系统可扩展性强。

资源服务授权流程

本项目在资源服务内部进行授权,基于资源的授权模式,因为接口在资源服务,通过在接口处添加授权注解实现授权。

1、首先配置nginx代理

Java
   http {
    server_names_hash_bucket_size 64;
    ...
   
   #前端开发服务
  upstream uidevserver{
    server 127.0.0.1:8601 weight=10;
  }
   server {
        listen       80;
        server_name  teacher.51xuecheng.cn;
        #charset koi8-r;
        ssi on;
        ssi_silent_errors on;
        #access_log  logs/host.access.log  main;
        #location / {
         #   alias   D:/itcast2022/xc_edu3.0/code_1/dist/;
         #   index  index.html index.htm;
        #}
        location / {
            proxy_pass   http://uidevserver;
        }

        location /api/ {
                proxy_pass http://gatewayserver/;
        }
        
        
   }

加载nginx 配置。

2、在资源服务集成Spring Security

在需要授权的接口处使用@PreAuthorize("hasAuthority('权限标识符')")进行控制

下边代码指定/course/list接口需要拥有xc_teachmanager_course_list 权限。

 

设置了@PreAuthorize表示执行此方法需要授权,如果当前用户请求接口没有权限则抛出异常

org.springframework.security.access.AccessDeniedException: 不允许访问

3、在统一异常处理处解析此异常信息

@ResponseBody
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public RestErrorResponse exception(Exception e) {

   log.error("【系统异常】{}",e.getMessage(),e);
   e.printStackTrace();
   if(e.getMessage().equals("不允许访问")){
      return new RestErrorResponse("没有操作此功能的权限");
   }
   return new RestErrorResponse(CommonError.UNKOWN_ERROR.getErrMessage());


}

4、重启资源服务进行测试

使用教学机构用户登录系统

这里使用t1用户登录,账号:t1、密码:111111

登录成功,点击“教学机构”

 

 当用户没有权限时页面提示:没有操作此功能的权限。

 

5.3 授权相关的数据模型

如何给用户分配权限呢?

首先要学习数据模型,本项目授权相关的数据表如下:

说明如下:

xc_user:用户表,存储了系统用户信息,用户类型包括:学生、老师、管理员等

xc_role:角色表,存储了系统的角色信息,学生、老师、教学管理员、系统管理员等。

xc_user_role:用户角色表,一个用户可拥有多个角色,一个角色可被多个用户所拥有

xc_menu:模块表,记录了菜单及菜单下的权限

xc_permission:角色权限表,一个角色可拥有多个权限,一个权限可被多个角色所拥有

本项目教学阶段不再实现权限定义及用户权限分配的功能,权限分配的界面原型如下图所示:

  

 

本项目要求掌握基于权限数据模型(5张数据表),要求在数据库中操作完成给用户分配权限、查询用户权限等需求。

1、查询用户所拥有的权限

步骤:

查询用户的id

查询用户所拥有的角色

查询用户所拥有的权限

例子:

SELECT * FROM xc_menu WHERE id IN(
    SELECT menu_id FROM xc_permission WHERE role_id IN(
        SELECT role_id FROM xc_user_role WHERE user_id = '49'
    )
)

2、给用户分配权限

1)添加权限

查询用户的id

查询权限的id

查询用户的角色,如果没有角色需要先给用户指定角色

向角色权限表添加记录

2)删除用户权限

本项目是基于角色分配权限,如果要删除用户的权限可以给用户换角色,那么新角色下的权限就是用户的权限;如果不换用户的角色可以删除角色下的权限即删除角色权限关系表相应记录,这样操作是将角色下的权限删除,属于该角色的用户都将删除此权限。

5.4 查询用户权限

使用Spring Security进行授权,首先在生成jwt前会查询用户的权限,如下图:

 

接下来需要修改UserServiceImpl和PasswordAuthServiceImpl从数据库查询用户的权限。

1、定义mapper接口

Java
public interface XcMenuMapper extends BaseMapper<XcMenu> {
    @Select("SELECT    * FROM xc_menu WHERE id IN (SELECT menu_id FROM xc_permission WHERE role_id IN ( SELECT role_id FROM xc_user_role WHERE user_id = #{userId} ))")
    List<XcMenu> selectPermissionByUserId(@Param("userId") String userId);
}

2、修改PasswordAuthServiceImpl

修改UserServiceImpl类的getUserPrincipal方法,查询权限信息

Java
//查询用户身份
public UserDetails getUserPrincipal(XcUserExt user){
    String password = user.getPassword();
    //查询用户权限
    List<XcMenu> xcMenus = menuMapper.selectPermissionByUserId(user.getId());
    List<String> permissions = new ArrayList<>();
    if(xcMenus.size()<=0){
        //用户权限,如果不加则报Cannot pass a null GrantedAuthority collection
        permissions.add("p1");
    }else{
        xcMenus.forEach(menu->{
            permissions.add(menu.getCode());
        });
    }
    //将用户权限放在XcUserExt中
    user.setPermissions(permissions);

    //为了安全在令牌中不放密码
    user.setPassword(null);
    //将user对象转json
    String userString = JSON.toJSONString(user);
    String[] authorities = permissions.toArray(new String[0]);
    UserDetails userDetails = User.withUsername(userString).password(password).authorities(authorities).build();
    return userDetails;

}

5.5 授权测试

以上实现了认证时从数据库查询用户的权限,下边进行用户授权测试。

重启认证服务,使用内容管理课程列表查询为例,代码如下:

 

 

用户拥有xc_teachmanager_course_list权限方可访问课程查询接口。

以用户stu1为例,当它没有此权限时页面报“没有此操作的权限”错误

将xc_teachmanager_course_list权限分配给用户。

1)首先找到当前用户的角色

2)找到xc_teachmanager_course_list权限的主键

3)在角色权限关系表中添加记录

分配完权限需要重新登录

由于用户分配了xc_teachmanager_course_list权限,用户具有访问课程查询接口的权限。

5.6 细粒度授权

5.6.1 什么是细粒度授权

什么是细粒度授权?

细粒度授权也叫数据范围授权,即不同的用户所拥有的操作权限相同,但是能够操作的数据范围是不一样的。一个例子:用户A和用户B都是教学机构,他们都拥有“我的课程”权限,但是两个用户所查询到的数据是不一样的。

本项目有哪些细粒度授权?

比如:

我的课程,教学机构只允许查询本教学机构下的课程信息。

我的选课,学生只允许查询自己所选课。

如何实现细粒度授权?

细粒度授权涉及到不同的业务逻辑,通常在service层实现,根据不同的用户进行校验,根据不同的参数查询不同的数据或操作不同的数据。

5.6.2 教学机构细粒度授权

教学机构在维护课程时只允许维护本机构的课程,教学机构细粒度授权过程如下:

1)获取当前登录的用户身份

2)得到用户所属教育机构的Id

3)查询该教学机构下的课程信息

最终实现了用户只允许查询自己机构的课程信息。

根据公司Id查询课程,流程如下:

1)教学机构用户登录系统,从用户身份中取出所属机构的id

在用户表中设计了company_id字段存储该用户所属的机构id.

2)接口层取出当前登录用户的身份,取出机构id

3) 将机构id传入service方法。

4) service方法将机构id传入Dao方法,最终查询出本机构的课程信息。

代码实现如下:

Java
@ApiOperation("课程查询接口")
@PreAuthorize("hasAuthority('xc_teachmanager_course_list')")//拥有课程列表查询的权限方可访问
@PostMapping("/course/list")
public PageResult<CourseBase> list(PageParams pageParams, @RequestBody QueryCourseParamsDto queryCourseParams){
    //取出用户身份
    XcUser user = SecurityUtil.getUser();
    //机构id
    String companyId = user.getCompanyId();
    return courseBaseInfoService.queryCourseBaseList(Long.parseLong(companyId),pageParams,queryCourseParams);
}

Service方法如下:

Java
@Override
public PageResult<CourseBase> queryCourseBaseList(Long companyId,PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto) {

 //构建查询条件对象
 LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>();
 //机构id
 queryWrapper.eq(CourseBase::getCompanyId,companyId);
 ....

5.6.3 教学机构细粒度授权测试

使用一个教学机构的用户登录项目,并且此用户具有查询课程的权限。

手机修改数据库指定用户归属到一个机构中,涉及以下数据表:

xc_company为机构表

xc_company_user为机构用户关系表

xc_user表中有company_id字段。

我们准备了t1 用户作为此次测试的用户,使用此用户登录系统:

提前在查询课程列表接口处打上断点。

经过测试可以正常取出用户所属的机构id

 

 

跟踪持久层日志发现已将机构id传入dao方法,拼装sql语句,查询本机构的课程信息

 

  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拉普兰德做的到吗?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值