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

【PaaS安全】RBAC与OAuth2.0集成 RBAC与OAuth2.0的集成绝非简单的技术叠加,而是构建PaaS安全基座的系统工程。随着云原生生态的演进,这一组合将持续吸收ABAC、SASE等新兴理念,最终形成自适应、可观测的全域安全架构。对于架构师而言,需在便捷性与安全性之间寻找平衡——正如安全领域的经典格言:“最完美的权限系统,是让合法用户无感,让攻击者无从下手。 阅读详情

😎 知识点概览

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

本章节为【学成在线】项目的 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令牌。

网关校验 redis 中 user_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",
	"课程图片查询"
);

2)将该用户所属的某个角色与权限 A 关联起来

INSERT INTO xc_permission (id,role_id,menu_id) VALUES(
	"8947692177635409931",
	20,
	"903459378655395851" # 权限A的ID
)

3)再次查询该用户对有的权限

image-20200601144328736

从上图中可以看到,我们为用户所属的角色下添加了一个课程图片查询的权限,那么用户也会同时拥有了该权限。

0x03 用户中心查询用户权限

需求分析

认证服务请求用户中心查询用户信息,用户需要将用户基本信息和用户权限一同返回给认证服务。

本小节实现查询用户所拥有的权限,并将用户权限信息添加到的指定对象中返回给认证服务。

以上需求需要修改如下接口:

@GetMapping("/getuserext")
public XcUserExt getUserext(@RequestParam("username") String username);

DAO

在用户中心服务中编写 dao,实现根据用户id查询权限。

1、定义 XcMenuMapper.java

在 com.xuecheng.ucenter.dao 包下定义:

@Mapper
public interface XcMenuMapper {
    public List<XcMenu> selectPermissionByUserId(String userid);
}

2、XcMenuMapper.xml

在 resources 下创建 com.xuecheng.ucenter.dao 然后定义 XcMenuMapper.xml

这里要注意的一个点就是,在r esources 创建 com.xuecheng.ucenter.dao 时需要逐个包来创建,如果直接复制整串来创建,idea会将他识别为同一个目录,导致mapperxml文件无法被扫描到,例如下图

image-20200602103546011

这里mapper.xml文件也可以存放的位置也可以是自定义的包名,但是要在启动类配置mapperscan

<?xml version="1.0" encoding="UTF‐8" ?>
<!DOCTYPE mapper PUBLIC "‐//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis‐3‐
mapper.dtd" >
<mapper namespace="com.xuecheng.ucenter.dao.XcMenuMapper" >
    <select id="selectPermissionByUserId"
            resultType="com.xuecheng.framework.domain.ucenter.XcMenu" parameterType="java.lang.String" >
        SELECT
        id,
        CODE,
        p_id pId,
        menu_name menuName,
        url,
        is_menu isMenu,
        LEVEL,
        sort,
        STATUS,
        icon,
        create_time createTime,
        update_time updateTiem
        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 = #{id}
        )
        )
    </select>
</mapper>

其它 Dao 采用 spring data jpa 编写如下:

image-20200601144613066

Service

修改 UserService 的 getUserExt 方法,查询用户权限。

//根据账号查询用户的信息,返回用户扩展信息
public XcUserExt getUserExt(String username){
    XcUser xcUser = this.findXcUserByUsername(username);
    if(xcUser == null){
        return null;
    } 
    //根据用户id查询用户权限
    String userId = xcUser.getId();
    List<XcMenu> xcMenus = xcMenuMapper.selectPermissionByUserId(userId);
    XcUserExt xcUserExt = new XcUserExt();
    BeanUtils.copyProperties(xcUser,xcUserExt);
    //用户的权限
    xcUserExt.setPermissions(xcMenus);
    return xcUserExt;
}

0x04 认证服务查询用户权限

修改认证服务的 UserDetailServiceImpl ,查询用户的权限,并拼接权限串,将原来硬编码权限代码删除,代码如
下:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    @Autowired
    ClientDetailsService clientDetailsService;

    //用户中心服务客户端
    @Autowired
    UserClient userClient;

    @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则会抛出异常

        //从数据库查询用户正确的密码,Spring Security会去比对输入密码的正确性
        String password = userext.getPassword();
        //指定用户的权限,从数据库中获取
        List<XcMenu> permissions = userext.getPermissions();
        if(permissions == null) {
            permissions = new ArrayList<>();
        }

        List<String> permissionsCode = new ArrayList<>();
        //遍历权限对象中的code字段
        permissions.forEach(item -> permissionsCode.add(item.getCode()));
        //将权限串中间以逗号分隔
        String user_permission_string = StringUtils.join(permissionsCode.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;
    }
}

这里要注意的是,设置到 userDetails 对象的权限信息为权限的代码标识,也就是 UserJwt 对象的 code字段,需要将所有的权限代码遍历出来然后拼接成字符串,如下代码

//指定用户的权限,从数据库中获取
List<XcMenu> permissions = userext.getPermissions();
List<String> permissionsCode = new ArrayList<>();
for (XcMenu xcMenu : permissions ){
    String code = xcMenu.getCode();
    permissionsCode.add(code);
}
//将权限串中间以逗号分隔
String user_permission_string = StringUtils.join(permissionsCode.toArray(), ",");

0x05 测试

1、执行登录,在 redis 中查看 jwt 令牌,使用 jwt 测试程序解析 jwt 令牌中是否包括用户的权限 。

测试登录,拿到令牌

image-20200602114307895

根据令牌,我们到redis里找到该令牌对应的 access_token

image-20200602114407430

2、使用新的jwt令牌测试方法授权

成功的访问课程图片的接口

image-20200602121703349

四、前端集成认证授权

0x01 需求分析

截至目前认证授权服务端的功能已基本完成,本章实现前端集成认证授权功能。

前端集成认证授权功能需要作如下工作:

1、前端页面校验用户的身份,如果用户没有登录则跳转到登录页面

2、前端请求资源服务需要在 http header 中添加 jwt 令牌,资源服务根据 jwt 令牌完成授权。

哪些功能需要前端请求时携带 JWT ?

用户登录成功请求资源服务都需要携带 jwt 令牌,因为资源服务已经实现了 jwt 认证,如果校验头部没有 jwt 则会认为身份不合法。

0x02 教学管理中心

本节实现教学管理中心(xc-ui-pc-teach)实现身份校验,其它前端参考教学管理中心实现

配置虚拟主机

教学管理前端访问微服务统一在访问地址前添加 /api 前缀并经过网关转发到微服务。

配置 teacher.xuecheng.com 虚拟主机。

#前端教学管理
upstream teacher_server_pool{
	server 127.0.0.1:12000 weight=10;
} 
#文件服务
upstream filesystem_server_pool{
	server 127.0.0.1:22100 weight=10;
} 
#媒资服务
upstream media_server_pool{
	server 127.0.0.1:31400 weight=10;
}
# 学成网教学管理中心
server {
    listen 80;
    server_name teacher.xuecheng.com;
    #个人中心
    location / {
        proxy_pass http://teacher_server_pool;
    } 
    location /api {
        proxy_pass http://api_server_pool;
    } 
    location /filesystem {
        proxy_pass http://filesystem_server_pool;
    }
    #媒资管理
    location ^~ /api/media/ {
        proxy_pass http://media_server_pool/media/;
    } 
    #认证
    location ^~ /openapi/auth/ {
        proxy_pass http://auth_server_pool/auth/;
    }
}

修改 hosts 文件 C:\Windows\System32\drivers\etc\hosts

127.0.0.1 teacher.xuecheng.com

身份校验

教学管理中心(xc-ui-pc-teach)是单页面应用,我们在路由变化时校验用户的身份,校验失败将跳转到登录页面。

校验方法如下:

  • 如果成功从 sessionStorage 和 cookie 获取当前用户则继续访问

  • 如果 sessionStorage 中无当前用户,cookie 中有当前用户则请求服务端获取 jwt,如果成功则继续访问。

  • 以上两种情况都不满足则跳转到登录页面。

1、在 main.js 中添加路由监控代码,如下

router.beforeEach((to, from, next) => {
    if(openAuthenticate){
        // console.log(to)
        // console.log(from)
        //***********身份校验***************
        let activeUser
        let uid
        try{
            activeUser = utilApi.getActiveUser()
            uid = utilApi.getCookie("uid")
        }catch(e){
            //alert(e)
        } 
        if(activeUser && uid && uid == activeUser.uid) {
            next();
        }else if(to.path =='/login' || to.path =='/logout'){
            next();
        }else if(uid){
            //请求获取jwt
            systemApi.getjwt().then((res)=>{
                if(res.success){
                    let jwt = res.jwt;
                    let activeUser = utilApi.getUserInfoFromJwt(jwt)
                    if(activeUser){
                        utilApi.setUserSession("activeUser",JSON.stringify(activeUser))
                    } 
                    next();
                }else{
                    //跳转到统一登陆
                    window.location = "http://ucenter.xuecheng.com/#/login?returnUrl="+Base64.encode(window.location)
                }
            })
        }else{
            //跳转到统一登陆
            window.location = "http://ucenter.xuecheng.com/#/login?returnUrl="+Base64.encode(window.location)
        }
    }else{
        next();
    }
});

配置 xc-ui-pc-teach/config/sysConfig.js 开启认证授权的配置

var sysConfig = {
    openAuthenticate: true,
    openAuthorize: true
}

module.exports = sysConfig

2、在 base/api/system.js 中添加 getjwt 方法

/*获取jwt令牌*/
export const getjwt= () => {
    return http.requestQuickGet('/openapi/auth/userjwt')
}

3、在utils.js中添加 如下方法

getActiveUser: function(){
    let uid = this.getCookie("uid")
    if(uid){
        let activeUserStr = this.getUserSession("activeUser");
        return JSON.parse(activeUserStr);
    }else{
        this.delUserSession("activeUser")
    }
},
    //获取jwt令牌
    getJwt : function(){
        let activeUser = this.getActiveUser()
        if(activeUser){
            return activeUser.jwt
        }
    },
        //解析jwt令牌,获取用户信息
        getUserInfoFromJwt : function (jwt) {
            if(!jwt){
                return ;
            } 
            var jwtDecodeVal = jwtDecode(jwt);
            if (!jwtDecodeVal) {
                return ;
            } 
            let activeUser={}
            //console.log(jwtDecodeVal)
            activeUser.utype = jwtDecodeVal.utype || '';
            activeUser.username = jwtDecodeVal.name || '';
            activeUser.userpic = jwtDecodeVal.userpic || '';
            activeUser.userid = jwtDecodeVal.userid || '';
            activeUser.authorities = jwtDecodeVal.authorities || '';
            activeUser.uid = jwtDecodeVal.jti || '';
            activeUser.jwt = jwt;
            return activeUser;
        },

4、测试

1)启动学习中心前端、教学管理前端、认证服务、用户中心服务、网关、Eureka

  • 进入首页

  • 点击“教学提供方”,此时由于没有登录自动跳转到登录页面

image-20200603075015366

2)输入账号和密码登录

登录成功,跳转到教学管理页面

image-20200603075038706

携带JWT授权

1、前端携带JWT请求

根据需求,在使用 axios 进行 http 请求前向 header 中加入 jwt 令牌

在 main.js 中添加

import axios from 'axios'
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求向header添加jwt
    let jwt = utilApi.getJwt()
    if(jwt){
        config.headers['Authorization'] = 'Bearer '+jwt
    } 
    return config;
}, function (error) {
    return Promise.reject(error);
});

2、测试 http 请求是否携带jwt

进入教学管理中心,点击我的课程,观察 request header 中是否有 Authorization 信息

image-20200603075158919

3、测试授权效果

当访问一个没有权限的方法时是否报错?

测试方法:

在课程计划查询方法上添加授权注解,表示当前用户需要拥有course_teachplan_list权限方可正常访问。

@PreAuthorize("hasAuthority('course_teachplan_list')")
@Override
public TeachplanNode findTeachplanList(@PathVariable("courseId") String courseId) {
    return courseService.findTeachplanList(courseId);
}

进入我的课程,点击课程计划,观察响应结果为 10002错误。

image-20200603075236015

4、提示权限不足

当权限不足首页要给出提示,实现思路是使用axios的拦截,在执行后校验响应结果,如果是10002 代码的错误则提示用户 ```“权限不足”,如果是10001` 代码则强制登录。

在 main.js 中添加

// 响应拦截
axios.interceptors.response.use(data => {
  console.log("data=",data)
  if(data && data.data){
    if(data.data.code && data.data.code =='10001'){
      //需要登录
      // router.push({
      //   path: '/login',
      //   query: {returnUrl: Base64.encode(window.location)}
      // })
      window.location = "http://ucenter.xuecheng.com/#/login?returnUrl="+ Base64.encode(window.location)
    }else if(data.data.code && data.data.code =='10002'){
      Message.error('您没有权限操作该选项');
      return
    }else if(data.data.code && data.data.code =='10003'){
      Message.error('认证被拒绝,请重新登录重试!');
      return
    }
  }
  return data
})

测试:

执行一个没有权限的操作,提示如下

image-20200603101409637

0x03 一些问题

用户前端是如何解密JWT令牌的? 公钥是否会暴露在前端?

五、细粒度授权

0x01 需求分析

什么是细粒度授权?

细粒度授权也叫数据范围授权,即不同的用户所拥有的操作权限相同,但是能够操作的数据范围是不一样的。

一个例子:

用户 A 和 用户 B 都是教学机构,他们都拥有 “我的课程” 权限,但是两个用户所查询到的数据是不一样的。
本项目有哪些细粒度授权?

比如:

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

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

如何实现细粒度授权?

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

0x02 我的课程细粒度授权

需求分析

1、我的课程查询,细粒度授权过程如下:

  • 获取当前登录的用户Id

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

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

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

2、修改课程管理服务 “我的课程” 的功能,根据 公司 Id 查询课程,思路如下:

  • 修改Dao,支持根据公司Id 查询课程。

  • 修改Service,将公司Id传入Dao。

  • 修改Controller,获取当前用户的公司Id,传给Service。

3、数据模型分析如下:

1)课程表

在 xc_course 数据库的 course_base 表中添加 company_id 字段,来表示此课程的归属

image-20200603102939596

2)用户企业表

在 xc_user 数据库的 xc_company_user 表中记录了用户的归属公司信息

image-20200603102958164

通过 xc_company_user 表可得到 用户 的所属公司 Id。

如何查询某个用户的课程?

1、确定用户的 Id

2、根据用户的 Id 查询用户归属的公司。

3、根据 公司Id 查询该公司下的课程信息

一个例子:

/*确定用户的id:49*/
/*根据用户Id查找所属公司*/
/*根据公司查询所拥有的课程*/
SELECT * FROM xc_course.course_base WHERE company_id IN (
	SELECT company_id FROM xc_user.xc_company_user WHERE user_id = '49'
)

API

定义我的课程查询接口如下:

@ApiOperation("查询指定公司下的所有课程")
public QueryResponseResult<CourseInfo> findCourseListByCompany(
    int page,
    int size,
    CourseListRequest courseListRequest
);

DAO

在 CourseMapper 下新增一个 findCourseListByCompany 方法

@Mapper
public interface CourseMapper {
   //根据课程id查询课程信息
   CourseBase findCourseBaseById(String id);

   /**
    * 分页查询课程数据
    * @param courseListRequest 查询条件
    * @return
    */
   Page<CourseBase> findCourseList(CourseListRequest courseListRequest);

   /**
    * 分页查询指定公司下的课程数据
    * @param courseListRequest 查询条件
    * @return
    */
   Page<CourseInfo> findCourseListByCompany(CourseListRequest courseListRequest);

}

修改 CourseMapper.xml 的查询课程列表,添加 companyId 条件。

<select id="findCourseListByCompany" resultType="com.xuecheng.framework.domain.course.ext.CourseInfo"
        parameterType="com.xuecheng.framework.domain.course.request.CourseListRequest">
    SELECT
    course_base.*,
    (SELECT pic FROM course_pic WHERE courseid = course_base.id) pic
    FROM
    course_base
    where 1=1
    <if test="companyId!=null and companyId!=''">
        and course_base.company_id = #{companyId}
    </if>
</select>

Service

修改 CourseService 的 findCourseList 方法,添加 companyId 参数,并且传给 dao.

/**
     * 分页查询指定公司下的课程信息
     */
public QueryResponseResult findCourseListByCompany(String companyId ,int pageNum, int size, CourseListRequest courseListRequest){
    if(pageNum<=0){
        pageNum = 0;
    }
    if(size<=0){
        size = 20;
    }
    PageHelper.startPage(pageNum,size);  //设置分页参数

    //设置公司信息到查询条件内
    courseListRequest.setCompanyId(companyId);
    Page<CourseInfo> courseList = courseMapper.findCourseListByCompany(courseListRequest);
    QueryResult queryResult = new QueryResult();
    queryResult.setList(courseList.getResult());
    queryResult.setTotal(courseList.getTotal());
    return new QueryResponseResult(CommonCode.SUCCESS,queryResult);
}

Controller

修改 CourseController 的 findCourseList,向 service 传入 companyId

这里先使用静态数据测试使用。

/**
     * 查询指定公司下的所有课程
     * @param page 页码
     * @param size 数量
     * @param courseListRequest 查询参数
     * @return QueryResponseResult
     */
@GetMapping("/coursebase/company/list/{page}/{size}")
@Override
public QueryResponseResult findCourseListByCompany(
    @PathVariable("page") int page,
    @PathVariable("size") int size,
    CourseListRequest courseListRequest
){
    String companyId = "1";
    return courseService.findCourseListByCompany(companyId, page, size, courseListRequest);
}

课程查询前端修改

修改 xc-ui-pc-teach/src/module/course/api/course.js 中查询课程的API 与我们前面构建的 controller 对应

//我的课程列表
export const findCourseList = (page,size,params) => {
//使用工具类将json对象转成key/value
  let queries = querystring.stringify(params)
  return http.requestQuickGet(apiUrl+"/course/coursebase/company/list/"+page+"/"+size+"?"+queries)
}

测试

进入我的课程,查看数据是否正确。

由于我们在 controller 中暂时将 company 的固定的写为了1,所以预期的结果应该是查询到 company 为1的所有课程,测试结果如下。

image-20200603141305638

测试结果如下,得到了预期的结果

image-20200603141347386

0x03 获取当前信息

要想实现只查询自己的课程信息,则需要获取当前用户所属的企业id。

1、认证服务在用户认证通过将用户所属公司id等信息存储到jwt令牌中。

2、用户请求到达资源服务后,资源服务需要取出header中的jwt令牌,并解析出用户信息。

JWT令牌包括企业Id

资源服务在授权时需要用到用户所属企业 ID,需要实现认证服务生成的JWT令牌中包括用户所属公司 id 信息。

查看认证服务 UserDetailServiceImpl 代码如下:

......
//用户id
userDetails.setId(userext.getId());
//用户名称
userDetails.setName(userext.getName());
//用户头像
userDetails.setUserpic(userext.getUserpic());
//用户类型
userDetails.setUtype(userext.getUtype());
//用户所属企业id
userDetails.setCompanyId(userext.getCompanyId());
return userDetails;
......

通过上边代码的分析得知,认证服务调用远程调用 ucenter 服务的 getUserext 接口获取用户信息,并将 userext 中的信息存储到jwt令牌中,所以在在 getUserext 接口中返回的 userext 对象中需要包括了 companyId 公司ID等信息 。

getUserExt 的代码如下

/**
     * 根据用户名获取用户权限的实现
     * @param username 用户名
     * @return
     */
@Override
public XcUserExt getUserExt(String username) {
    //查询用户信息
    XcUser xcUser = this.findXcUserByUsername(username);
    if(xcUser ==null) return null;

    XcUserExt xcUserExt = new XcUserExt();
    BeanUtils.copyProperties(xcUser,xcUserExt);

    //根据用户id查询用所属公司
    String xcUserId = xcUser.getId();
    XcCompanyUser xcCompanyUser = xcCompanyUserRepository.findByUserId(xcUserId);
    if(xcCompanyUser!=null){
        String companyId = xcCompanyUser.getCompanyId();
        xcUserExt.setCompanyId(companyId);
    }

    //获取用户的所有权限
    List<XcMenu> xcMenus = xcMenuMapper.selectPermissionByUserId(xcUserId);
    xcUserExt.setPermissions(xcMenus);

    //返回XcUserExt对象
    return xcUserExt;
}

getUserExt 是在用户进行认证时被调用的,由 ucenter-auth 服务向 ucenter 服务调用该接口,获取用户的信息,然后将得到的这些信息 打包为 UserJwt 对象交由 Spring Security 进行校验,校验通过后会将该JWT令牌到认证服务中,校验 Spring Security 返回的 JWT令牌完整性后写入到 redis。

解析令牌中的信息

1、JWT解析工具类

1、在 Oauth2Util 工具类中,从 header 中取出JWT令牌,并解析 JWT 令牌的内容。

public class Oauth2Util {
    public static Map<String,String> getJwtClaimsFromHeader(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        
        //取出头信息
        String authorization = request.getHeader("Authorization");
        if (StringUtils.isEmpty(authorization) || authorization.indexOf("Bearer") < 0) {
            return null;
        }
        
        //从Bearer后边开始取出token
        String token = authorization.substring(7);
        Map<String,String> map = null;
        try {
            //解析jwt
            Jwt decode = JwtHelper.decode(token);
            //得到 jwt中的用户信息
            String claims = decode.getClaims();
            //将jwt转为Map
            map = JSON.parseObject(claims, Map.class);
        } catch (Exception e) {
            e.printStackTrace();
        } 
        return map;
    }
}

2、在 XcOauth2Util 工具类中,将解析的 JWT 内容封装成 UserJwt 对象返回。

public class XcOauth2Util {
    public UserJwt getUserJwtFromHeader(HttpServletRequest request){
        Map<String, String> jwtClaims = Oauth2Util.getJwtClaimsFromHeader(request);
        if(jwtClaims == null || StringUtils.isEmpty(jwtClaims.get("id"))){
            return null;
        } 
        UserJwt userJwt = new UserJwt();
        userJwt.setId(jwtClaims.get("id"));
        userJwt.setName(jwtClaims.get("name"));
        userJwt.setCompanyId(jwtClaims.get("companyId"));
        userJwt.setUtype(jwtClaims.get("utype"));
        userJwt.setUserpic(jwtClaims.get("userpic"));
        return userJwt;
    } 
    @Data
    public class UserJwt{
        private String id;
        private String name;
        private String userpic;
        private String utype;
        private String companyId;
    }
}
2、获取当前用户

修改课程管理的 CourseController 类,将 companyId 的静态数据改为动态获取:

配置 CourseController 继承 BaseController ,这样我们就可以拿到一个用户请求时的 request 对象

public class CourseContorller extends BaseController implements CourseControllerApi {
	...
}

在 findCourseListByCompany 中调用前面我们定义的工具类,从用户的 header 信息中取出 JWT 令牌并且解析出用户的信息,拿到用户所属的公司 ID,实现细粒度的课程信息获取。

/**
     * 查询指定公司下的所有课程
     * @param page 页码
     * @param size 数量
     * @param courseListRequest 查询参数
     * @return QueryResponseResult
     */
@GetMapping("/coursebase/company/list/{page}/{size}")
@Override
public QueryResponseResult findCourseListByCompany(
    @PathVariable("page") int page,
    @PathVariable("size") int size,
    CourseListRequest courseListRequest
){
    //调用工具类取出用户信息
    XcOauth2Util xcOauth2Util = new XcOauth2Util();
    //从用户header中附带的jwt令牌取出用户信息
    XcOauth2Util.UserJwt userJwt = xcOauth2Util.getUserJwtFromHeader(request);
    //从用户信息获取该用户所属的公司id,根据该公司id查询该公司下的所有课程
    String companyId = userJwt.getCompanyId();
    return courseService.findCourseListByCompany(companyId, page, size, courseListRequest);
}

测试

使用不同的用户登录系统,测试细粒度权限控制效果。

预期结果:每个用户只查询自己所拥有的课程。

公司1的用户

image-20200603150544501

公司2的用户

image-20200603150423751

六、微服务间认证

0x01 需求分析

前边章节已经实现了用户携带身份令牌(JTI) 和 JWT(access_token) 令牌访问微服务,微服务获取 jwt 并完成授权。当微服务访问微服务,此时如果没有携带 JWT 则微服务会在授权时报错。

测试课程预览:

1、将课程管理服务和CMS全部添加授权配置

2、用户登录教学管理前端,进入课程发布界面,点击课程发布,观察课程管理服务端报错如下:

feign.FeignException: status 401 reading CmsPageClient#save(CmsPage); content:
{"error":"unauthorized","error_description":"Full authentication is required to access thisresource"}

分析原因:

由于课程管理访问 CMS 时没有携带 JWT 令牌导致。

解决方案:

微服务之间进行调用时需携带 JWT。

0x02 Feign 拦截器

微服务之间使用 feign 进行远程调用,采用 feign 拦截器实现远程调用携带 JWT。

在 common 工程添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring‐cloud‐starter‐openfeign</artifactId>
</dependency>

定于Feign拦截器

在 Common 工程定义拦截器如下:

package com.xuecheng.framework.interceptor;
public class FeignClientInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        try {
            //使用RequestContextHolder工具获取request相关变量
            ServletRequestAttributes attributes = (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
            if(attributes!=null){
                //取出request
                HttpServletRequest request = attributes.getRequest();
                Enumeration<String> headerNames = request.getHeaderNames();
                if (headerNames != null) {
                    while (headerNames.hasMoreElements()) {
                        String name = headerNames.nextElement();
                        String values = request.getHeader(name);
                        if(name.equals("authorization")){
                            //System.out.println("name="+name+"values="+values);
                            requestTemplate.header(name, values);
                        }
                    }
                }
            }
        }catch (Exception e) {
            e.printStackTace();
        }
    }
}

使用Feign拦截器

本例子中课程管理调用 cms 需要携带 jwt,所以需要在课程管理中定义 Feign 拦截器 bean,在启动类中定义 bean 如下:

@Bean
public FeignClientInterceptor feignClientInterceptor(){
    return new FeignClientInterceptor();
}

使用restTemplate访问其他服务接口被拦截的解决方案

在发布课程时,cms服务使用 restTeamlate 向数据模型URL发送请求获取数据,该操作涉及到调用课程管理服务的接口,由于课程管理服务开启了接口认证,所有没附带 JWT 令牌的请求都会被拒绝,如下图所示

image-20200603171728929

方案1

在课程管理服务中放行该URL的认证拦截,通常该接口是可以不需要认证的。

image-20200603172432245

在课程管理服务的 ResourceServerConfig 中配置 configure 方法,放行 /course/preview/model

@Value("${oauth2.urlMatchers}")
String urlMatchers;

//Http安全配置,对每个到达系统的http请求链接进行校验
@Override
public void configure(HttpSecurity http) throws Exception {
    if(urlMatchers.equals("")){
        //如果urlMatchers未指定,则所有url都需要授权后才能被访问
        http.authorizeRequests().anyRequest().authenticated();
    }else{
        //放行 urlMatchers 中指定的url条目, 未指定的url仍需授权后才能访问
        if(urlMatchers != null){
            String[] split = urlMatchers.split(",");
            http.authorizeRequests()
                //下边的路径放行
                .antMatchers(split).permitAll()
                .anyRequest().authenticated();
        }
    }
}

我这里需要放行的URL是从 appliaction.yml 中将 oauth2.urlMatchers 注入到变量 urlMatchers 内,内容如下

oauth2:
  urlMatchers: /v2/api-docs,/swagger-resources/configuration/ui,/swagger-resources,/swagger-resources/configuration/security,/swagger-ui.html,/webjars/**,/course/coursepic/get/*
方案2

获取当前请求的 request 对象,从该对象中取出当前请求中 header 信息里面包含的 authorization 字段,该字段内带有了我们认证需要的 JWT 令牌信息。

//从dataUrl中获取页面模型数据
private Map getModelByPageId(String pageId){
    //查询页面信息
    CmsPageResult cmsPageResult = this.cmsPageQueryById(pageId);
    CmsPage cmsPage = cmsPageResult.getCmsPage();
    //页面不存在
    if(cmsPage == null){
        ExceptionCast.cast(CmsCode.CMS_PAGE_NOT_EXISTS);
    }
    //取出dataUrl
    String dataUrl = cmsPage.getDataUrl();
    if(StringUtils.isEmpty(dataUrl)){
        ExceptionCast.cast(CmsCode.CMS_GENRATEHTML_DATAURL_IS_NULL);
    }
    
    //通过获取当前请求的request对象来取出jwt认证信息,并且传递到下一个请求中
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    String jwt = request.getHeader("authorization");
    //使用LinkedMultiValueMap储存多个header信息
    LinkedMultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
    headers.add("authorization",jwt);
    HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(headers);
    
    //发送请求获取模型数据
    ResponseEntity<Map> forEntity = restTemplate.exchange(dataUrl, HttpMethod.GET,httpEntity,Map.class);
    Map body = forEntity.getBody();
    return body;
}
方案3

将 CMS 服务中调用 课程管理服务 的数据模型接口更改为使用 Feign 来进行远程调用,再结合前面配置的 Feign 拦截器实现 Header 信息的向下传递。

由于数据模型接口需要的参数是课程ID,但CmsPage对象中没有明确的给出该页面对应的课程ID,但是我们可以看到,页面的名称其实就是以课程ID所命名的,如下图

image-20200603182403327

所以我们可以从 pageName 中取出课程ID进行远程调用,代码如下

String[] pageNameSplit = cmsPageName.split("\\.");
String courseId = pageNameSplit[0];

这里要注意如果以 . 对字符串进行分割,需要在点号前面加两个斜杠,如 \\.

在之前的代码中,使用 RestTemplate 访问的数据模型接口返回的是一个 map 类型的数据,而如果采用远程调用的方式,拿到的是一个 CourseView 对象,这里我们可以使用 JSONObject.toJSONString 将对象转为字符串,再使用 parseObject 将JSON形式的字符串转换为 Map 对象,代码如下

JSONObject.parseObject(JSONObject.toJSONString(courseView), Map.class)

修改后的全部代码如下

//从dataUrl中获取页面模型数据
private Map getModelByPageId(String pageId){
    //查询页面信息
    CmsPageResult cmsPageResult = this.cmsPageQueryById(pageId);
    CmsPage cmsPage = cmsPageResult.getCmsPage();

    //页面不存在
    if(cmsPage == null){
        ExceptionCast.cast(CmsCode.CMS_PAGE_NOT_EXISTS);
    }
    String cmsPageName = cmsPage.getPageName();
    if(cmsPageName == null){
        ExceptionCast.cast(CmsCode.CMS_PAGE_NAME_NOT_EXISTS);
    }
    String[] pageNameSplit = cmsPageName.split("\\.");
    String courseId = pageNameSplit[0];
    CourseView courseView = courseManageClient.courseView(courseId);
    Map body = JSONObject.parseObject(JSONObject.toJSONString(courseView), Map.class);
    //        ResponseEntity<Map> forEntity = restTemplate.getForEntity(dataUrl,Map.class);
    //        Map body = forEntity.getBody();
    //        courseManageClient.courseView(cmsPage.)
    return body;
}

测试

执行课程发布,提示发布成功。

image-20200603191048332

七、提出一些问题

1、JWT时间目前是由 redis 来进行控制,那么 jwt令牌的实际过期时间是多久? 如何获取或者设置?

2、生成JWT的公钥和私钥都有哪些作用?

公钥:用于校验JWT令牌是否完整,以及解密JWT令牌中的用户信息

私钥:生成加密后的JWT令牌

八、待完善的一些功能

  • 为 swagger-ui 配置认证授权,使接口文档暴露在外部时需要进行登录认证,提高安全性。

😁 认识作者

作者:👦 LCyee ,全干型代码🐕

自建博客:https://www.codeyee.com

记录学习以及项目开发过程中的笔记与心得,记录认知迭代的过程,分享想法与观点。

优快云 博客:https://blog.youkuaiyun.com/codeyee

记录和分享一些开发过程中遇到的问题以及解决的思路。

欢迎加入微服务练习生的队伍,一起交流项目学习过程中的一些问题、分享学习心得等,不定期组织一起刷题、刷项目,共同见证成长。

基于 Spring Cloud 2021 、Spring Boot 2.7、 OAuth2 的 RBAC 权限管理系统 基于 Spring Cloud 2021 、Spring Boot 2.7、 OAuth2 的 RBAC 权限管理系统;基于数据驱动视图的理念封装 element-plus,即使没有 vue 的使用经验也能快速上手;提供对常见容器化支持 Docker、Kubernetes、Rancher2 支持;提供 lambda 、stream api 、webflux 的生产实践 立即下载

相关推荐

告别‘小爱同学’:用ESP32-S3和esp-sr SDK,从零打造一个离线语音助手(含完整代码)

本文详细介绍了如何利用ESP32-S3芯片和esp-sr SDK从零构建一个离线语音助手,涵盖硬件选型、声学设计、SDK配置及低功耗优化等关键步骤。通过实战案例和完整代码,帮助开发者实现自定义唤醒词和命令词,打造低成本、高隐私的本地语音控制解决方案。

weixin_29041195的博客 371

OAuth2在分布式微服务架构下基于角色的权限设计(RBAC)

在前两节的基础上,对权限控制作进一步分析的分析与设计。 RBAC(Role-Base Access Control,基于角色的访问控制) 本篇内容基于个人理解,不当之处,欢迎批评指正。 前两篇内容: 【图文详解】搭建 Spring Authorization Server + Resource + Client 完整Demo 【oauth2 客户端模式】Spring Authorization Server + Resource + Client 资源服务间的相互访问 1、OAuth2中用户访问的基.

土味儿 4508

<工控><PLC>汇川Easy521系列PLC与汇川SV630N伺服进行EtherCat通讯的相关配置及指令编写

本系列是关于PLC相关的博文,包括PLC编程、PLC与上位机通讯、PLC与下位驱动、仪器仪表等通讯、PLC指令解析等相关内容。PLC品牌包括但不限于西门子、三菱等国外品牌,汇川、信捷等国内品牌。除了PLC为主要内容外,PLC相关元器件如触摸屏(HMI)、交换机等工控产品,如果有值得记录的内容,也会添加进来。

用沸腾的热血,支付我们的人生吧! 2万+

spring-security-oauth2 (二十八) RBAC权限控制

一般的内部管理系统,权限比较复杂,通常权限都是动态配置的,也就是下面我们要讲的的rbac模型 RBAC 详细原理请度娘,这里只是涉及到企业权限的功能级权限,数据级权限还要我们自己控制(用户只能看见它相关的业务表数据) Spring Security中支持我们自定义的实现,就像这样调用,下面我们来代码实现 config.anyRequest().access("@rbacS...

codeing-tiger 4610

STM32嵌入式开发实战:如何用miniLZO压缩算法节省70%存储空间(附完整工程)

本文详细介绍了如何在STM32嵌入式开发中使用miniLZO无损压缩算法实现高达70%的存储空间节省。通过对比分析不同轻量级算法,重点讲解miniLZO的内存优化配置、分块压缩策略和完整工程实现,帮助开发者有效解决嵌入式系统中的存储瓶颈问题。

ss78901的博客 881

5分钟搞定kkFileView 4.4.0 Docker部署:小白也能上手的完整配置流程

本文提供了一份针对kkFileView 4.4.0版本的Docker部署完整指南。通过使用预构建的Docker镜像,即使是新手也能在5分钟内完成文件预览服务的搭建,涵盖从环境准备、镜像拉取、容器启动到服务验证的全流程,并分享了进阶配置与生产环境优化建议。

mango的博客 526

基于Oauth2授权码模式的SSO单点登录+基于RBAC权限模型的动态路由的前后端分离的权限认证系统

以房屋出租系统为背景,主要实现了oauth2的授权码模式,client信息入库,增强token,前后端分离架构,用户RBAC权限模型,前端动态路由等。

weixin_44010572的博客 2万+

IntelliJ IDEA 中git使用说明书

IntelliJ IDEA 中git使用说明书

junyang2012的博客 5389

Spring Cloud OAuth2授权服务器详解

Spring Cloud Security OAuth2 授权服务器允许自定义用户服务,以支持不同的用户存储方式,如数据库、缓存等。集成 Spring Cloud Bus:使用 Spring Cloud Bus 实现用户服务的分布式部署。集成 Spring Cloud Sleuth:使用 Spring Cloud Sleuth 对用户服务进行追踪和监控。集成 Spring Cloud Stream:使用 Spring Cloud Stream 实现用户服务的消息驱动。

MoneyHacksPro的博客 1117

推荐系统(十六):基于ESMM的商品召回/推荐系统

ESMM(Entire Space Multi-Task Model)是阿里巴巴提出的多任务学习框架,通过联合建模点击率(CTR)和点击转化率(CVR)解决传统CVR模型样本稀疏问题。其核心思想是将 CVR 定义为 CTR 与 CTCVR 的条件概率,模型包含两个子任务。

Jin_Kwok的博客 1265

wvp gb28181 pro 平台国标级连功能说明

国标28181不同平台之间支持两种连接方式,平级和上下级,WVP目前支持向上级级联。

zengliguang的专栏 3887

SSO、OAuth2、JWT、CAS、OpenID、LDAP、RBAC

单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统要实现SSO,需要构建以下两个主要内容:OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。

summer_fish的专栏 8351

内模控制(IMC)——理论、应用与实践

内模控制方法的核心思想是利用被控对象的数学模型(即内模)构建控制器,使得控制器可以“预知”被控系统的动态响应,从而在控制输入中提前补偿模型的固有特性和扰动影响。常见的内模控制结构中,将系统分为无延迟部分和扰动部分,并利用内部模型预测输出值,再通过反馈回路进行误差修正。utuimctufbtutui​mctuf​bt其中,uimctu_imc(t)ui​mct为内模预测得到的预补偿控制量,ufbtu_fb(t)uf​bt。

闲人编程的博客 2633

Spring Security OAuth2和RBAC权限授权

1、什么是RBAC? RBAC是指基于角色的权限访问控制,通过权限与角色相关联,用户可以通过成为适当角色的成员而得到这些角色的权限。简单来说,RBAC认为权限授权的过程可以抽象为: Who能否对What进行How的操作?并对这个逻辑表达式进行判断是否为True的求解过程,所以如果项目上需要设计实现权限管理模块,那必定要考虑RBAC的思想。 RBAC分3个组成部分:用户、角色和权限。 User(用...

weixin_39309402的博客 2645

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

【Spring Security OAuth2笔记系列】- Security控制授权- 基于数据库Rbac数据模型控制权限

基于数据库Rbac数据模型控制权限 前面都是讲的怎么在权限规则基本不变的情况下,怎么写代码控制权限; 这一节要实现内管系统的场景; 这些所有的信息都必须存在数据库中。因为变动频繁,员工离职、部门调动,新增权限等; 通用RBAC数据模型 Role-Based-Access Control 通常由三直系表,两张关系表 对于资源表:存储数据的表现是 某一个url的别名是菜单或则按钮...

代码有毒的博客 4452

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Spring Gateway+Security+OAuth2+RBAC 实现SSO统一认证平台

内容:完整集成了Spring Gateway, Spring Security, Nacos, OAuth2, RBAC, 手机验证码登录等SSO统一认证平台,全平台唯一,全平台最全,全面细致,已经帮你踩了很多坑,一看就会,可以本地运行,或者直接作为认证平台。 适合人群:准备搭建SSO平台的架构师,准备学习OAuth2和Spring Gateway结合的高级开发

东皋长歌的专栏 1639

springcloud gateway集成RBAC模型——实现用户权限判断

springcloud gateway集成jwt token后,基于RBAC模型实现用户权限管理及判断

tang_seven的博客 2266

Spring Cloud Alibaba微服务实战十九 - 集成RBAC授权

前面几篇文章我们一直是在实现SpringCloud体系中的认证功能模块,验证当前登录用户的身份;本篇文章我们来讲SpringCloud体系中的授权功能,验证你是否能访问某些功能。

smart_an的专栏 998

8-OAuth2.0实战:木谷博客系统RBAC权限设计

这节内容说一下木谷博客系统的权限设计,采用现在主流的权限模型RBAC,对应关系如下: 以上5张表都在mugu_auth_server这个库中 该部分的服务单独定义在user-boot这个模块中。 将角色、权限对应关系加载到Redis 木谷博客系统在认证中心颁发令牌的时候是将用户的角色保存到令牌中,客户端携带令牌访问服务经过网关层,网关将令牌解析取出角色,然后和资源所需的角色比对,如果包含了,则放行。 问题:**如何获取到资源的角色?**当然,木谷博客中的资源则是接口。 这里的方式比较粗暴,直接在项目启

码猿技术专栏 1608

基于go-micro微服务的实战-Gateway网关层的鉴权-rbac(六)

go-micro的认证授权rbac

toegg的博客 1229

微服务项目之认证服务

微服务项目之认证服务 一、Java中的权限框架 1.1 RBAC RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。 RBAC 认为授权实际上是Who 、What 、How 三元组之间的关系,也就是Who 对What 进行How 的操作,也就是“主体

w1073459332的博客 1197

SpringBoot2+Vue+D2-Admin版

该互联网实战项目是基于 Spring Boot 2+ MybatisPlus+SpringSecurity5+Element UI+Vue+可视化等技术栈开发的项目,采用多模块前后端分离开发。包括图形展示、权限管理、用户管理等功能,用于研发企业级中后台产品。【后端技术】技术 说明Spring Boot2 框架 开发的一站式解决方案Spring Security5  认证和授权框架MyBatisPlus3.3.x  基于 MyBatis 框架的快速研发框架MyBatisCode工具 生成 MyBatis 相关代码Jackson 提供了处理 JSON 数据的工具Lombok 简化对象封装工具 Druid   数据库连接池 【前端技术】Vue        互联网最火的前端框架Vue Router 路由框架Vuex 全局状态管理框架Axios 前端 HTTP 框架Element UI 前端 UI 框架D2-Admin 前端模板D2-CRUD框架D2-CRUD-PLUS框架Echarts 可视化技术主要用于研发企业级中后台产品。特性提炼自企业级中后台产品的交互语言和视觉风格。使用 TypeScript 开发,提供完整的类型定义文件。 链路开发和设计工具体系。【开发工具】IntelliJ IDEA 开发 IDESQLyog 数据库连接客户端Postman HTTP 请求工具【开发环境】工具 版本JDK 1.8MySQL 5.7

权限管理系统_SpringCloud,OAuth2的RBAC权限管理系统

这是一款基于Spring Boot、 Spring Cloud 、OAuth2 的RBAC 权限管理系统。github开源地址:https://gitee.com/cjlgb/cjlgb-cloud-platform基于 Spring Cloud Hoxton 、Spring Boot 2.2、 OAuth2 的RBAC权限管理系统基于数据驱动视图的理念封装 Ant Design Vue...

weixin_28710999的博客 866

rbac权限管理5张表_采用前后端分离的模式. 基于OAuth2 的RBAC权限管理微服务系统

一个Security作为安全框架,采用前后端分离的模式. 基于OAuth2 的RBAC权限管理微服务系统. Pro-Cloud后端采用springcloud alibaba架构,集成Sentinel从流量控制、熔断降级、Dubbo调用,系统负载等多个维度保护服务的稳定性。 注册中心、配置中心选型Nacos,为工程瘦身的同时加强各模块之间的联动。使用OAuth2,实现了多终端认证系统,可控制子系统的...

weixin_39768444的博客 3217

基于spring-security-oauth2实现oauth2

代码地址基于springboot2.1.5,springcloud Greenwich版本实现,该系列分为两个部分:分为内存实现,数据库实现。其中数据库实现采用RBAC权限角色管理。 首先声明oauth2是一种协议规范,spring-security-oauth2是对他的一种实现。其次,还有shiro实现,自己根据规范编写代码的实现方式。主流的qq,微信等第三方授权登录方式都是基于oauth2实现的。 oauth2的认证方式有授权码,简单,账户密码,客户端等方式,具体请自行百度不做过多的阐述。 本文基于.

BUG指挥课堂 508
上一篇: 微服务[学成在线] day17:基于Zuul网关实现路由转发、过滤器
下一篇: 微服务[学成在线] day19:分布式事务
cory_lin
博客等级 码龄7年 22粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值