微服务[学成在线] day18:基于oauth2实现RBAC认证授权、微服务间认证实现

本文介绍了基于OAuth2实现的RBAC(Role-Based Access Control)认证授权,详细阐述了用户授权业务流程,包括JWT令牌包含权限、动态查询用户权限、前端集成认证授权以及微服务间的认证实现。通过方法授权、数据库权限模型和Feign拦截器,确保了细粒度的权限管理和微服务间的安全通信。
摘要由CSDN通过智能技术生成

😎 知识点概览

为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。

本章节为【学成在线】项目的 day18 的内容

  • 基于方法的权限校验
  • 基于 RBAC 进行用户权限配置以及动态查询。
  • 根据教师所属的公司来实现课程信息查询的细粒度授权。也就是 A 公司的老师只能查询到 A 公司下的课程。
  • 使用 Feign 拦截器实现获取前端请求中的 header 信息,并将 header 中带有的 jwt 令牌向下传递,实现微服务间的远程调用的认证授权。

目录

内容会比较多,可以根据目录进行按需查阅。

一、用户授权业务流程

用户授权的业务流程如下:

image-20200601102646580

业务流程说明如下:

1、用户认证通过,认证服务向浏览器 cookie 写入 token( 身份令牌)

2、前端携带 token 请求用户中心服务获取jwt令牌,前端获取到jwt令牌解析,并存储在sessionStorage

3、前端携带 cookie 中的身份令牌及jwt令牌访问资源服务

前端请求资源服务需要携带两个 token,一个是 cookie 中的身份令牌,一个是 http header中的 jwt,前端请求资源服务前在 http header上添加 jwt 请求资源

4、网关校验 token 的合法性

用户请求必须携带身份令牌和jwt令牌。

网关校验 redisuser_token 的有效期,已过期则要求用户重新登录。

5、资源服务校验 jwt 的合法性并进行授权

资源服务校验 jwt 令牌,完成授权,拥有权限的方法正常执行,没有权限的方法将拒绝访问。

二、基于方法授权

0x01 需求分析

方法授权要完成的是 资源服务 根据 jwt 令牌完成对方法的授权,具体流程如下:

1、生成 Jwt 令牌时在令牌中写入用户所拥有的权限

我们给每个权限起个名字,例如某个用户拥有如下权限:

course_find_list:课程查询

course_pic_list:课程图片查询

2、在资源服务方法上添加注解 @PreAuthorize,并指定此方法所需要的权限

@PreAuthorize 注解是由Spring Security 提供的一个权限校验注解

例如下边是课程管理接口方法的授权配置,它就表示要执行这个方法需要拥有 course_find_list 权限。

@PreAuthorize("hasAuthority('course_find_list')")
@Override
public QueryResult<CourseInfo> findCourseList(@PathVariable("page") int page,
                                              @PathVariable("size") int size,
                                              CourseListRequest courseListRequest)

3、当请求有权限的方法时正常访问

4、当请求没有权限的方法时则拒绝访问

0x02 jwt令牌包含权限

修改认证服务的 UserDetailServiceImpl 类,下边的代码中 permissionList 列表中存放了用户的权限,并且将权限标识按照中间使用逗号分隔的语法组成一个字符串,最终提供给 Spring security

核心的代码如下

//指定用户的权限,这里暂时硬编码
List<String> permissionList = new ArrayList<>();
permissionList.add("course_base_list");
permissionList.add("course_pic_list");

//将权限串中间以逗号分隔
String user_permission_string = StringUtils.join(permissionList.toArray(), ",");
//设置用户信息到userDetails对象
UserJwt userDetails = new UserJwt(
    username,
    password,
    AuthorityUtils.commaSeparatedStringToAuthorityList(user_permission_string));

在上述的代码当中,通过向 permissionList 添加标识来对用户的进行授权,这里我们暂时对用户的权限的内容进行硬编码,后面的章节中用户的权限信息会从数据库中获取。

全部代码如下

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   
    //取出身份,如果身份为空说明没有认证
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    //没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret
    //开始认证client_id和client_secret
    if(authentication==null){
   
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
        if(clientDetails!=null){
   
            //密码
            String clientSecret = clientDetails.getClientSecret();
            return new User(username,clientSecret,AuthorityUtils.commaSeparatedStringToAuthorityList(""));
        }
    }
    if (StringUtils.isEmpty(username)) {
   
        return null;
    }

    //请求ucenter查询用户
    XcUserExt userext = userClient.getUserext(username);
    if(userext == null) return null; //如果获取到的用信息为空,则返回null,spring security则会抛出异常

    //设置用户的认证和权限信息
    userext.setUsername("itcast");
    userext.setPassword(new BCryptPasswordEncoder().encode("123"));
    userext.setPermissions(new ArrayList<XcMenu>());  //这里授权部分还没完成,所以先填写静态的

    if(userext == null){
   
        return null;
    }

    //从数据库查询用户正确的密码,Spring Security会去比对输入密码的正确性
    String password = userext.getPassword();

    //指定用户的权限,这里暂时硬编码
    List<String> permissionList = new ArrayList<>();
    permissionList.add("course_base_list");
    permissionList.add("course_pic_list");

    //将权限串中间以逗号分隔
    String user_permission_string = StringUtils.join(permissionList.toArray(), ",");
    //设置用户信息到userDetails对象
    UserJwt userDetails = new UserJwt(
        username,
        password,
        AuthorityUtils.commaSeparatedStringToAuthorityList(user_permission_string));
    //用户id
    userDetails.setId(userext.getId());
    //用户名称
    userDetails.setName(userext.getName());
    //用户头像
    userDetails.setUserpic(userext.getUserpic());
    //用户所属企业id
    userDetails.setCompanyId(userext.getCompanyId());

    //返回用信息给到Spring Security进行处理
    return userDetails;
}

重启认证服务工程,使用 postman 完成登录,获取到用户令牌

image-20200601114112301

redis 中找到该用户令牌对应的 jwt 令牌。

image-20200601114257044

使用 jwt 的测试程序查看 此令牌的内容。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ToOi66ex-1595567460983)(https://qnoss.codeyee.com/20200704_MTg=/image4.png)]

可以看到 authorities 属性中为用户的权限。

0x03 方法授权实现

1、资源服务添加权限控制

要想在资源服务使用方法授权,首先在 资源服务 配置授权控制,流程如下

1)添加 spring-cloud-starter-oauth2 依赖。

2)拷贝授权配置类 ResourceServerConfig

3)拷贝公钥。

2、方法上添加注解

通常情况下,程序员编写在资源服务的 controller 方法时会使用注解指定此方法的权限标识。

为什么不在 Service 或者 Dao上定义?因为 Service 和 Dao的方法有可能是公用的,而 Controller 通常都是最外层的,所以不会涉及到被其他服务依赖的情况。

下面我们在 获取课程的图片删除课程图 的接口中使用 @PreAuthorize 注解进行权限的设置,试下以下功能

  • 访问 getCoursePic 需要授权 course_pic_list 权限
  • 访问 deleteCoursePic 需要授权 course_pic_delete 权限

而我们要注意的是,我们在前面的认证当中,只为用分配了 course_pic_list 的权限,配置完后我们来进行测试。

/**
     * 根据课程id获取该课程的课程图片信息
     * @param courseId
     * @return 由于这里每个课程只有一个图片,所以只返回一个 CoursePic 对象
     */
@PreAuthorize("hasAuthority('course_pic_list')")
@Override
@GetMapping("/coursepic/get/{courseId}")
public CoursePic getCoursePic(@PathVariable("courseId") String courseId) {
   
    return courseService.getCoursePic(courseId);
}

/**
     * 删除课程图片信息
     * @param courseId
     * @return
     */

@PreAuthorize("hasAuthority('course_pic_delete')")
@Override
@DeleteMapping("/coursepic/delete")
public ResponseResult deleteCoursePic(@RequestParam("courseId") String courseId) {
   
    return courseService.deleteCoursePic(courseId);
}

3、在资源服务(这里是课程管理)的 ResourceServerConfig 类上添加注解,激活方法上添加授权注解

//激活方法上的PreAuthorize注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

0x04 方法授权测试

重启课程管理服务,测试上边两个方法。

使用 postman 测试,测试前执行登录,并且将 jwt 令牌(access_token)添加到 header

发送GET请求到以下链接

http://www.xuecheng.com/api/course/coursepic/get/4028e58161bd22e60161bd23672a0001

1、用户拥有 course_pic_list 权限,可以正常访问获取课程图片的接口 。

image-20200601115936697

2、下面我们来测试删除课程图片的接口

发送给 DELETE 请求到 http://www.xuecheng.com/api/course/coursepic/delete?courseId=4028e58161bd22e60161bd23672a0001

由于用户没有查询课程列表方法的权限,所以无法正常访问,其它方法可以正常访问。

image-20200601133025369

控制台报错:

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

说明:如果方法上没有添加授权注解 spring security 将不进行授权控制,只要 jwt 令牌合法则可以正常访问

3、异常处理

上边当没有权限访问时资源服务,应该返回下边的错误代码:

UNAUTHORISE(false,10002,"权限不足,无权操作!")

进入资源服务(我们测试的是课程管理),新建一个 exception 包,在包下创建一个 CustomExceptionCatch ,并继承 common 工程中的 ExceptionCatch 。

添加异常类 AccessDeniedException.class 与错误代码 10002 的 对应关系,使用 @ControllerAdvice 注解添加一个全局的异常处理,并继承我们在 common 工程中定义的 ExceptionCatch ,使用 static {} 向 builder 里面添加自定义的异常处理代码。

package com.xuecheng.manage_course.exception;

import com.xuecheng.framework.exception.ExceptionCatch;
import com.xuecheng.framework.model.response.CommonCode;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;

@ControllerAdvice
public class CustomExceptionCatch extends ExceptionCatch {
   
    static {
   
        //除了CustomException以外的异常类型及对应的错误代码在这里定义,,如果不定义则统一返回固定的错误信息
        builder.put(AccessDeniedException.class, CommonCode.UNAUTHORISE);
    }
}

再次测试,结果如下:

image-20200601134010887

0x05 小结

基于方法授权步骤:

1、ResourceServerConfig 类上添加注解,如下:

//激活方法上的PreAuthorize注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)  

2、在 Controller 为需要校验权限的方法上添加授权注解

@PreAuthorize("hasAuthority('权限名称')")

3、如果方法上不添加授权注解则表示此方法不需要权限即可访问。

三、动态查询用户权限

0x01 需求分析

截至目前在测试授权时使用的权限数据是静态数据,正常情况的流程是:

1、管理员给用户分配权限,权限数据写到数据库中。

2、认证服务在进行用户认证时从数据库读取用户的权限数据(动态数据)
本节实现动态权限数据。

0x02 权限数据模型

数据模型结构

打开 xc_user 数据库,找到下边的表:

image-20200601141058705

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

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

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

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

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

xc_permission 表可以更名为 xc_permission_role 或者 xc_menu_role 会容易理解

数据模型的使用

本项目教学阶段不再实现权限定义及用户权限分配的功能,但是基于权限数据模型(5张数据表)及现有数据,要求学生在数据库中操作完成给用户分配权限、查询用户权限等需求。

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

步骤:

  • 确定用户的id

  • 查询用户所拥有的角色

  • 查询用户所属的 角色 所拥有的权限

例子:

# 根据查到的权限ID(menu_id)查询所对应的权限的详细信息
SELECT * FROM xc_menu WHERE id IN(
	# 根据用户角色ID取出该角色所拥有的权限
	SELECT menu_id FROM xc_permission WHERE role_id IN(
        	# 获取指定用户所拥有角色的id
			SELECT role_id FROM xc_user_role where user_id = 49
	)
)

image-20200601144244164

2、向已拥有角色分配权限

1)新增一个 权限 A

INSERT INTO xc_menu (id,code,p_id,menu_name) VALUES (
	"903459378655395851", # 权限A的ID
	"course_pic_list",
	"903459378655395841",
	"课程图片查询"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值