第一章 完善用户相关信息

第一章 完善用户相关信息

用户注册与登录

  • 数据库表设计:用户表、用户信息表
  • 相关接口(API):获取RSA公钥、用户注册、用户登录

数据库表设计及相关实体类设计

用户表

Untitled

用户信息表

Untitled

  • 根据这两张数据库表创建对应的实体类

基于JWT的用户token验证

先来看看基于session的用户身份验证的特点

  • 验证过程:服务端验证浏览器携带的用户名和密码,验证通过后生成用户凭证保存在服务端(session),浏览器再次访问时,服务端查询session,实现登录状态保持
  • 缺点:随着用户的增多,服务端压力增大;若浏览器cookie被攻击者拦截,容易受到跨站请求伪造攻击;分布式系统下扩展性不强

基于token的用户身份验证

  • 验证过程:服务端验证浏览器携带的用户名和密码,验证通过后生成用户令牌(token)并返回给浏览器,浏览器再次访问时携带token,服务端校验token并返回相关数据
  • 优点:token不储存在服务器,不会造成服务器压力;token可以存储在非cookie中,安全性高;分布式系统下扩展性强

什么是 JWT ?

  • JWT:全称是JSONWeb Token,JWT是一个规范,用于在空间受限环境下安全传递“声明”。
  • JWT的组成:JWT分成三部分,第一部分是头部(header),第二部分是载荷(payload),第三部分是签名(signature)
  • JWT优点:跨语言支持、便于传输、易于扩展

JWT 的内部有什么 ?

  • JWT头部:声明的类型、声明的加密算法(通常使用SHA256)
  • JWT载荷:存放有效信息,一般包含签发者、所面向的用户、接受方、过期时间、签发时间以及唯一身份标识
  • JWT签名:主要由头部、载荷以及秘钥组合加密而成

用户关注与动态提醒

用户关注

  • 数据库表设计:用户关注表、用户关注分组表
  • 相关接口(API):关注用户、关注列表、粉丝列表、分页查询用户

数据库表设计及相关实体类设计

用户关注分组表

Untitled

  • 先新增3条数据

Untitled

用户关注表

Untitled

动态提醒(重点)

  • 数据库表设计:用户动态表
  • 相关接口(API):用户发布动态、用户查询订阅内容的动态
  • 设计模式:订阅发布模式

数据库表设计及相关实体类设计

用户动态表

Untitled

订阅发布模式

Untitled

订阅发布模式 与 观察者模式的区别

Untitled

实现动态提醒的工具

  • RocketMQ:纯java编写的开源消息中间件,特点是:高性能、低延迟、分布式事务
  • Redis:高性能缓存工具,数据存储在内存中,读写速度非常快
  • RocketMQ 相关工具类及配置实现

用户权限控制(重点)

  • 权限控制是什么:控制用户对系统资源(URI)的操作
  • 前端的权限控制:对页面或页面元素的权限控制
  • 前端的权限控制:对页面或页面元素的权限控制

B站会员等级权限

Untitled

  • 访问权限:哪些页面可以访问、哪些页面元素可见等等
  • 操作权限:如页面按钮是否可点击、是否可以增删改查等等
  • 接口与数据权限:接口是否可以调用、接口具体字段范围等等

RBAC权限控制模型

  • RBAC权限控制模型(Role-Based Access Control):基于角色的权限控制
  • RBAC模型的层级:RBAC0、RBAC1、RBAC2、RBAC3
  • 关键词:用户、角色、资源、权限、操作
    • 通过权限对资源操作的相关绑定,再通过角色绑定相关的资源,就能变相地让角色拥有资源和操作的相关权限
    • 最后通过用户和角色的相关绑定,来达到用户可以对资源和操作进行权限控制的目的

项目实现 RBAC权限控制模型

Untitled

  • 用户:注册用户
  • 角色:Lv0~Lv6 会员
  • 权限:视频投稿、发布动态、各种弹幕功能等等
  • 资源:页面、页面元素
  • 操作:点击、跳转、增删改查等等

相关数据库表设计

  • 数据库表设计:角色表用户角色关联表元素操作权限表角色元素操作权限关联表页面菜单权限表角色页面菜单权限关联表

Untitled

Spring AOP

  • 概念:Spring AOP是一种约定流程的编程
  • 关键词:约定(AOP的核心)
  • 典型的例子:数据库事务包括打开数据库,设置属性,执行sql语句,没有异常则提交事务,有异常则回滚事务,最后关闭数据库连接
    • 打开打开数据库和设置属性,再到异常回滚没有异常则提交,这些都是固定(约定)的流程,而执行sql语句包含的功能有很多种,不是一个固定的操作
    • Spring AOP就类似于把这些固定的流程抽出来,做成一个约定的流程,以及在我们约定的流程当中,插入我们个性化的操作的一种工具或一种框架

SpringAOP术语

  • 连接点(join point):对应的被拦截的对象
  • 切点(point cut):通过正则或指示器的规则来适配连接点
  • 切面(aspect):可以定义切点、各类通知和引入的内容
  • 通知(advice):,分为前置通知(before)、后置通知(after)、事后返回通知(afterReturning)、异常通知(afterThrowing)
  • 织入(weaving):为原有服务(service)对象生成代理对象,然后将与切点匹配的连接点拦截,并将各类通知加入约定流程
  • 目标对象(target):被代理对象

基于接口的权限控制

ApiLimitedRole.java

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Component
public @interface ApiLimitedRole {

    /**
     * 角色权限代码列表
     *
     * @return
     */
    String[] limitedRoleCodeList() default {};

}

ApiLimitedRoleAspect.java

/**
 * 切面类
 *
 * @author xiexu
 * @create 2022-06-17 07:37
 */
@Aspect
@Order(1)
@Component
public class ApiLimitedRoleAspect {

    @Autowired
    private UserSupport userSupport;

    @Autowired
    private UserRoleService userRoleService;

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.imooc.bilibili.annotation.ApiLimitedRole)")
    public void check() {
    }

    /**
     * 前置通知
     *
     * @param joinPoint
     * @param apiLimitedRole
     */
    @Before("check() && @annotation(apiLimitedRole)")
    public void doBefore(JoinPoint joinPoint, ApiLimitedRole apiLimitedRole) {
        Long userId = userSupport.getCurrentUserId();
        // 用户具有哪些角色
        List<UserRole> userRoleList = userRoleService.getUseRoleByUserId(userId);
        // 角色权限代码列表
        String[] limitedRoleCodeList = apiLimitedRole.limitedRoleCodeList();
        // 将角色权限代码列表转换成set集合
        Set<String> limitedRoleCodeSet = new HashSet<>();
        for (String s : limitedRoleCodeList) {
            limitedRoleCodeSet.add(s);
        }
        // 用户角色列表转换成set集合
        Set<String> roleCodeSet = new HashSet<>();
        for (UserRole userRole : userRoleList) {
            roleCodeSet.add(userRole.getRoleCode());
        }
        // 对用户角色列表 和 角色权限代码列表 取交集,roleCodeSet存放的就是他们的交集
        roleCodeSet.retainAll(limitedRoleCodeSet);
        // 交集指的就是用户角色列表里面有包含,角色权限代码列表里面也有包含,说明用户有某个角色是不允许被访问的
        if (roleCodeSet.size() > 0) {
            throw new ConditionException("权限不足!");
        }
    }

}

UserMomentsApi.java

@RestController
public class UserMomentsApi {

    @Autowired
    private UserMomentsService userMomentsService;

    @Autowired
    private UserSupport userSupport;

    /**
     * 用户发布动态
     * AuthRoleConstant.ROLE_LV0表示等级0是没有权限进行发布动态的
     *
     * @param userMoment
     * @return
     */
    @ApiLimitedRole(limitedRoleCodeList = {AuthRoleConstant.ROLE_LV0})
    @PostMapping("/user-moments")
    public JsonResponse<String> addUserMoments(@RequestBody UserMoment userMoment) throws Exception {
        Long userId = userSupport.getCurrentUserId();
        userMoment.setUserId(userId);
        userMomentsService.addUserMoments(userMoment);
        return JsonResponse.success();
    }

}

基于数据的权限控制

DataLimited.java

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Component
public @interface DataLimited {
    
}

DataLimitedAspect.java

/**
 * 切面类
 *
 * @author xiexu
 * @create 2022-06-17 07:37
 */
@Aspect
@Order(1)
@Component
public class DataLimitedAspect {

    @Autowired
    private UserSupport userSupport;

    @Autowired
    private UserRoleService userRoleService;

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.imooc.bilibili.annotation.DataLimited)")
    public void check() {
    }

    /**
     * 前置通知
     *
     * @param joinPoint
     */
    @Before("check()")
    public void doBefore(JoinPoint joinPoint) {
        Long userId = userSupport.getCurrentUserId();
        // 用户具有哪些角色
        List<UserRole> userRoleList = userRoleService.getUseRoleByUserId(userId);
        // 用户角色列表转换成set集合
        Set<String> roleCodeSet = new HashSet<>();
        for (UserRole userRole : userRoleList) {
            roleCodeSet.add(userRole.getRoleCode());
        }
        // 把切到的方法里面的参数获取到,也就是addUserMoments(@RequestBody UserMoment userMoment)方法的userMoment参数
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg instanceof UserMoment) {
                UserMoment userMoment = (UserMoment) arg;
                String type = userMoment.getType();
                // 用户角色为lv0的情况下,只有传参"0"给type字段,其他数字参数都报错
                if (roleCodeSet.contains(AuthRoleConstant.ROLE_LV0) && !"0".equals(type)) {
                    throw new ConditionException("参数异常!");
                }
                // 用户角色为lv1的情况下,只有传参"0"或者"1"给type字段,其他数字参数都报错
                if (roleCodeSet.contains(AuthRoleConstant.ROLE_LV1) && (!"1".equals(type) || !"0".equals(type))) {
                    throw new ConditionException("参数异常!");
                }
            }
        }
    }

}

UserMomentsApi.java

@RestController
public class UserMomentsApi {

    @Autowired
    private UserMomentsService userMomentsService;

    @Autowired
    private UserSupport userSupport;

    /**
     * 用户发布动态
     * AuthRoleConstant.ROLE_LV0表示等级0是没有权限进行发布动态的
     *
     * @param userMoment
     * @return
     * @ApiLimitedRole 接口权限控制
     * @DataLimited 数据权限控制
     */
    @ApiLimitedRole(limitedRoleCodeList = {AuthRoleConstant.ROLE_LV0})
    @DataLimited
    @PostMapping("/user-moments")
    public JsonResponse<String> addUserMoments(@RequestBody UserMoment userMoment) throws Exception {
        Long userId = userSupport.getCurrentUserId();
        userMoment.setUserId(userId);
        userMomentsService.addUserMoments(userMoment);
        return JsonResponse.success();
    }

}

存在的问题

之前我们做的登录token模块,用户在退出登录之后,如果token还没有过期失效,那么这个时候用户还是可以拿着token去访问系统的资源,这样是不合理的。

用户退出之后,应该不能再访问系统的资源了。基于这种情况,我们引入双token机制。

数据库表设计及相关实体类设计

Untitled

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

猿小羽

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

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

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

打赏作者

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

抵扣说明:

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

余额充值