SpringSecurity+jwt+captcha登录认证授权总结

SpringSecurity+jwt+captcha登录认证授权总结

版本信息:

springboot 3.2.0、springSecurity 6.2.0、mybatis-plus 3.5.5

认证授权思路和流程:
未携带token,访问登录接口:

1、用户登录携带账号密码

2、请求到达自定义Filter,自定义Filter(如JwtAuthenticationTokenFilter)继承OncePerRequestFilter(此Filter只会进行一次过滤,在请求返回时,不会再进行调用),在SecurityConfig中配置,将自定义Filter添加到过滤器链中并加在UsernamePasswordAuthenticationFilter过滤器之前

3、自定义Filter逻辑

3.1、查询到用户未携带token,直接放行,进入到后面认证流程

3.2、将用户信息封装成Authentication对象,调用authticate()方法进行验证,一直到DaoAuthenticationProvider中,会调用UserDetailService的loadUserByUserName()方法;自定义UserDetailServiceImpl继承UserDetailService接口;重写其loadUserByUserName()方法;查到用户后,封装成UserDetail对象返回

4、调用passwordEncoder的验证方法进行用户信息的认证(一般使用BCryptPasswordEncoder,创建此Bean并放入容器中)

5、认证通过后,使用JWT创建token并放到redis中;返回token到前端

携带token访问:

1、请求到达自定义Filter中,获取token,先验证token的正确性,从token中获取到userId,根据userId从redis中获取用户信息,将用户信息封装成Authentication对象,放进SecurityContextHolder中,后面的过滤器会在SecurityContextHolder中获取用户的信息

2、请求到达FilterSecurityInterceptor,在springSecurity中默认使用FilterSecurityInterceptor来进行权限校验;FilterSecurityInterceptor会从SecurityContextHolder中获取Authentication,然后获取其中的权限信息,判断当前用户是否拥有当前资源的访问权限;

3、通过权限判断,获取资源返回给前端;

登录认证流程图:
基于RBAC的授权控制:
RBAC概念:

Role-Based Access Control,中文意思是:基于角色(Role)的访问控制。这是一种广泛应用于计算机系统和网络安全领域的访问控制模型。

简单来说,就是通过将权限分配给角色,再将角色分配给用户,来实现对系统资源的访问控制。一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成"用户-角色-权限"的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般是多对多的关系

模型:

在数据库中主要体现用户表角色表菜单表用户角色关联表角色菜单关联表五个模型:

sql 复制代码
CREATE TABLE "mySchema"."t_user"
(
"id" INT IDENTITY(1, 1) NOT NULL,
"username" VARCHAR(100) NOT NULL,
"password" VARCHAR(100) NOT NULL,
"email" VARCHAR(100) NOT NULL,
"phone" VARCHAR(100),
UNIQUE("id"),
UNIQUE("username"),
UNIQUE("email"),
UNIQUE("phone"),
NOT CLUSTER PRIMARY KEY("id")) STORAGE(ON "MAIN", CLUSTERBTR) ;

COMMENT ON TABLE "mySchema"."t_user" IS '用户表';
COMMENT ON COLUMN "mySchema"."t_user"."id" IS '用户id';
COMMENT ON COLUMN "mySchema"."t_user"."username" IS '用户名';
COMMENT ON COLUMN "mySchema"."t_user"."password" IS '密码';
COMMENT ON COLUMN "mySchema"."t_user"."email" IS '用户邮箱';
COMMENT ON COLUMN "mySchema"."t_user"."phone" IS '电话';


CREATE TABLE "mySchema"."sys_role"
(
"id" BIGINT IDENTITY(1, 1) NOT NULL,
"name" VARCHAR(128) DEFAULT NULL,
"role_key" VARCHAR(100) DEFAULT NULL,
"status" CHAR(1) DEFAULT '0',
"del_flag" INT DEFAULT 0,
"create_by" BIGINT DEFAULT NULL,
"create_time" DATETIME(6) DEFAULT NULL,
"update_by" BIGINT DEFAULT NULL,
"update_time" DATETIME(6) DEFAULT NULL,
"remark" VARCHAR(500) DEFAULT NULL,
NOT CLUSTER PRIMARY KEY("id")) STORAGE(ON "MAIN", CLUSTERBTR) ;

COMMENT ON TABLE "mySchema"."sys_role" IS '角色表';
COMMENT ON COLUMN "mySchema"."sys_role"."id" IS '角色id';
COMMENT ON COLUMN "mySchema"."sys_role"."name" IS '角色名称';
COMMENT ON COLUMN "mySchema"."sys_role"."role_key" IS '角色权限字符串';
COMMENT ON COLUMN "mySchema"."sys_role"."status" IS '角色状态(0正常, 1停用)';
COMMENT ON COLUMN "mySchema"."sys_role"."del_flag" IS '删除标志(0未删除,1已删除)';
COMMENT ON COLUMN "mySchema"."sys_role"."remark" IS '备注';


CREATE TABLE "mySchema"."sys_menu"
(
"id" BIGINT IDENTITY(2, 1) NOT NULL,
"menu_name" VARCHAR(64) DEFAULT 'NULL' NOT NULL,
"path" VARCHAR(200) DEFAULT NULL,
"component" VARCHAR(50) DEFAULT NULL,
"visible" CHAR(1) DEFAULT '0',
"status" CHAR(1) DEFAULT '0',
"perms" VARCHAR(100) DEFAULT NULL,
"icon" VARCHAR(100) DEFAULT '#',
"create_by" BIGINT DEFAULT NULL,
"create_time" DATETIME(6) DEFAULT CURRENT_TIMESTAMP,
"update_by" BIGINT DEFAULT NULL,
"update_time" DATETIME(6) DEFAULT CURRENT_TIMESTAMP,
"delete_f1ag" INT DEFAULT 0,
"remark" VARCHAR(500) DEFAULT NULL,
UNIQUE("id"),
NOT CLUSTER PRIMARY KEY("id")) STORAGE(ON "MAIN", CLUSTERBTR) ;

COMMENT ON TABLE "mySchema"."sys_menu" IS '菜单表';
COMMENT ON COLUMN "mySchema"."sys_menu"."menu_name" IS '菜单名';
COMMENT ON COLUMN "mySchema"."sys_menu"."path" IS '路由地址';
COMMENT ON COLUMN "mySchema"."sys_menu"."component" IS '组件路径';
COMMENT ON COLUMN "mySchema"."sys_menu"."visible" IS '菜单状态(0显示1隐藏)';
COMMENT ON COLUMN "mySchema"."sys_menu"."status" IS '菜单状态(0正常1停用)';
COMMENT ON COLUMN "mySchema"."sys_menu"."perms" IS '权限标识';
COMMENT ON COLUMN "mySchema"."sys_menu"."icon" IS '菜单图标';
COMMENT ON COLUMN "mySchema"."sys_menu"."delete_f1ag" IS '是否删除(0未删除1已删除)';
COMMENT ON COLUMN "mySchema"."sys_menu"."remark" IS '备注';


CREATE TABLE "mySchema"."sys_user_role"
(
"user_id" BIGINT DEFAULT 0 NOT NULL,
"role_id" BIGINT DEFAULT 0 NOT NULL,
NOT CLUSTER PRIMARY KEY("user_id", "role_id")) STORAGE(ON "MAIN", CLUSTERBTR) ;

COMMENT ON TABLE "mySchema"."sys_user_role" IS '用户角色表';
COMMENT ON COLUMN "mySchema"."sys_user_role"."user_id" IS '用户id';
COMMENT ON COLUMN "mySchema"."sys_user_role"."role_id" IS '角色id';


CREATE TABLE "mySchema"."sys_role_menu"
(
"role_id" BIGINT DEFAULT 0 NOT NULL,
"menu_id" BIGINT DEFAULT 0 NOT NULL,
NOT CLUSTER PRIMARY KEY("role_id", "menu_id")) STORAGE(ON "MAIN", CLUSTERBTR) ;

COMMENT ON TABLE "mySchema"."sys_role_menu" IS '角色菜单关联表';
COMMENT ON COLUMN "mySchema"."sys_role_menu"."role_id" IS '角色id';
COMMENT ON COLUMN "mySchema"."sys_role_menu"."menu_id" IS '菜单id';

权限流程:

用户表、角色表、菜单表、部门表、用户角色关联表、角色菜单关联表;数据权限分为5种:个人、全部、本部门、指定部门、本部门及以下

链路:

1、用户注册,指定部门;创建角色,给角色赋菜单权限、数据权限;给用户赋予角色;

2、用户登录,通过用户的所有角色,获取所有的菜单权限和最大数据权限;菜单数据直接展示在页面;所有数据都有创建人,则数据的部门id是创建人的部门id;

3、在访问数据接口时,通过mybatis的拦截器对用户的数据进行过滤(拼接sql),返回给前端;

captcha图片验证码:

实现思路:

1、图片验证码获取,并将验证码验证码存放起来(单机应用可放在session中,分布式应用放在redis中)

2、当用户登录时,携带验证码以及验证码的key,进入到后端从redis中获取值进行验证

java 复制代码
<!-- 谷歌kaptcha验证码依赖 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

代码思路:

1、生成验证码

java 复制代码
public Result<CaptchaVO> getCaptcha() throws IOException {
    // 生成文字验证码
    String content = defaultKaptcha.createText();
    // 生成图片验证码
    ByteArrayOutputStream outputStream = null;
    BufferedImage image = defaultKaptcha.createImage(content);
    outputStream = new ByteArrayOutputStream();
    ImageIO.write(image, "jpg", outputStream);
    // 对字节数组Base64编码
    String str = "data:image/jpeg;base64,";
    String base64Img = str + Base64.getEncoder().encodeToString(outputStream.toByteArray()).replace("\n", "").replace("\r", "");
    CaptchaVO captchaVO = captchaService.cacheCaptcha(content);
    captchaVO.setBase64Img(base64Img);
    return Result.success(captchaVO);
}

2、存储验证码

java 复制代码
@Service
public class CaptchaService {

    private Long timeout = 300L;

    @Autowired
    private RedisUtils redisUtils;
    
    private final String CAPTCHA_KEY_PREFIX = "captcha:verification:";

    public CaptchaVO cacheCaptcha(String captcha){
        //生成一个随机标识符
        String randomStr = UUID.randomUUID().toString();
        //缓存验证码并设置过期时间
        String captchaKey = CAPTCHA_KEY_PREFIX.concat(randomStr);
        redisUtils.set(captchaKey, captcha, timeout, TimeUnit.SECONDS);
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaKey(captchaKey);
        captchaVO.setExpire(timeout);
        captchaVO.setCaptcha(captcha);
        return captchaVO;
    }
}

3、校验验证码

java 复制代码
Object captcha = redisUtils.get(reqVO.getCaptchaKey());
if (Objects.isNull(captcha)) {
    return "验证码已过期";
}
if (!captcha.equals(reqVO.getCaptcha())) {
    return "验证码错误";
}
相关推荐
ZeroToOneDev1 小时前
Java(泛型和JUnit)
java·开发语言·笔记
迪尔~2 小时前
Apache POI中通过WorkBook写入图片后出现导出PDF文件时在不同页重复写入该图片问题,如何在通过sheet获取绘图对象清除该图片
java·pdf·excel
现在,此刻3 小时前
leetcode 11. 盛最多水的容器 -java
java·算法·leetcode
DKPT3 小时前
Java设计模式之开闭原则介绍与说明
java·设计模式·开闭原则
hyy27952276844 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
布朗克1684 小时前
Spring Boot项目通过Feign调用三方接口的详细教程
java·spring boot·feign
Arva .4 小时前
Spring基于XML的自动装配
xml·java·spring
帅得不敢出门6 小时前
Android Framework定制长按电源键关机的窗口
android·java·framework
fatfishccc6 小时前
循序渐进学 Spring (上):从 IoC/DI 核心原理到 XML 配置实战
xml·java·数据库·spring·intellij-idea·ioc·di
小厂永远得不到的男人6 小时前
一篇文章搞懂 java 反射
java·后端