课程项目设计--spring security--用户管理功能--宿舍管理系统--springboot后端

news2024/11/16 5:50:08
写在前面:
还要实习,每次时间好少呀,进度会比较慢一点
本文主要实现是用户管理相关功能。
前文项目建立

文章目录

  • 验证码功能
    • 验证码配置
    • 验证码生成工具类
    • 添加依赖
    • 功能测试
    • 编写controller接口
    • 启动项目
  • security配置
    • 拦截器配置
      • 验证码拦截器
    • jwt拦截器
    • 思考
  • 用户登录
    • jwt管理
    • 验证
  • 用户注销

验证码功能

验证码采用的是hutool工具的验证码
hutool官方地址

工具模板采用有来开源组织

验证码配置

yml配置

CaptchaConfig:
  #  验证码缓存过期时间(单位:秒)
  ttl: 120l
  # 验证码内容长度
  length: 4
  # 验证码宽度
  width: 120
  # 验证码高度
  height: 40
  # 验证码字体
  font-name: Verdana
  # 验证码字体大小
  fontSize: 20

配置类

/**
 * EasyCaptcha 配置类
 * 
 * @author haoxr
 * @since 2023/03/24
 */
@ConfigurationProperties(prefix = "easy-captcha")
@Configuration
@Data
public class CaptchaConfig {

    // 验证码类型
    private CaptchaTypeEnum type = CaptchaTypeEnum.ARITHMETIC;

    // 验证码缓存过期时间(单位:秒)
    @Value("${captcha.ttl}")
    private long ttl;

    // 内容长度
    @Value("${captcha.length}")
    private int length;
    // 宽度
    @Value("${captcha.width}")
    private int width;
    // 验证码高度
    @Value("${captcha.height}")
    private int height;

    // 验证码字体
    @Value("${captcha.font-name}")
    private String fontName;

    // 字体风格
    private Integer fontStyle = Font.PLAIN;

    // 字体大小
    @Value("${captcha.font-size}")
    private int fontSize;

}

验证码生成工具类

@Component
@RequiredArgsConstructor
public class EasyCaptchaProducer {
    private final CaptchaConfig captchaConfig;

    public Captcha getCaptcha() {
        Captcha captcha;
        int width = captchaConfig.getWidth();
        int height = captchaConfig.getHeight();
        int length = captchaConfig.getLength();
        String fontName = captchaConfig.getFontName();

        switch (captchaConfig.getType()) {
            case ARITHMETIC -> {
                captcha = new ArithmeticCaptcha(width, height);
                captcha.setLen(2);
            }
            case CHINESE -> {
                captcha = new ChineseCaptcha(width, height);
                captcha.setLen(length);
            }
            case CHINESE_GIF -> {
                captcha = new ChineseGifCaptcha(width, height);
                captcha.setLen(length);
            }
            case GIF -> {
                captcha = new GifCaptcha(width, height);//最后一位是位数
                captcha.setLen(length);
            }
            case SPEC -> {
                captcha = new SpecCaptcha(width, height);
                captcha.setLen(length);
            }
            default -> throw new RuntimeException("验证码配置信息错误!正确配置查看 CaptchaTypeEnum ");
        }
        captcha.setFont(new Font(fontName, captchaConfig.getFontStyle(), captchaConfig.getFontSize()));
        return captcha;
    }


}

添加依赖

        <!-- Java8 之后JavaScript引擎nashorn被移除导致验证码解析报错-->
        <dependency>
            <groupId>org.openjdk.nashorn</groupId>
            <artifactId>nashorn-core</artifactId>
            <version>${nashorn.version}</version>
        </dependency>

功能测试

        Captcha captcha = easyCaptchaProducer.getCaptcha();
        try (OutputStream ops = new FileOutputStream("d://captcha.jpg")) {
            captcha.out(ops);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(captcha.text());

测试结果
在这里插入图片描述
在这里插入图片描述

编写controller接口

@Tag(name = "01-认证中心")
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Slf4j
public class AuthController {

    private final EasyCaptchaService easyCaptchaService;

    @Operation(summary = "获取验证码")
    @GetMapping("/captcha")
    public Result<CaptchaResult> getCaptcha() {
        CaptchaResult captcha = easyCaptchaService.getCaptcha();
        return Result.success(captcha);
    }
}

启动项目

记住这里,这是你spring security 的密码
在这里插入图片描述

生成http

通过base64转图片的在线工具可以看到
在这里插入图片描述
说明编写成功了。

security配置

在上面我们默认的是spring security 自动的密码。我们现在需要自己设置密码。

spring security 框架捏,不太好说这玩意。挺忘记了。
不过spring boot3使用的是spring security6.0版本和以前的有很大差别,6.0通过配置bean来进行。所以也还好,反正都是从头学。
首先需要配置security的配置类

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {


    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    /**
     * 不走过滤器链的放行配置
     * 默认放行静态资源、登录接口、验证码接口、Swagger接口文档
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                .requestMatchers(
                        "/auth/captcha",
                        "/webjars/**",
                        "/doc.html",
                        "/swagger-resources/**",
                        "/swagger-ui/**",
                        "/ws/**"
                );
    }
}
    /**
     * 认证管理器
     *
     * @param authenticationConfiguration 认证配置
     * @return 认证管理器
     * @throws Exception 异常
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(requestMatcherRegistry ->
                        requestMatcherRegistry.requestMatchers(SecurityConstants.LOGIN_PATH).permitAll()
                                .anyRequest().authenticated())
                .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling(httpSecurityExceptionHandlingConfigurer ->
                        httpSecurityExceptionHandlingConfigurer
                                .authenticationEntryPoint(authenticationEntryPoint)
                                .accessDeniedHandler(accessDeniedHandler))
                .csrf(AbstractHttpConfigurer::disable);

        // 验证码校验过滤器
        http.addFilterBefore(new VerifyCodeFilter(), UsernamePasswordAuthenticationFilter.class);
        // JWT 校验过滤器
        http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenManager), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

这里还用到了2个拦截器

拦截器配置

验证码拦截器

需求:对登录请求进行拦截,如果是登录则需要先校验验证码是否正常,如果正确则放行。其他请求则直接放行。

public class VerifyCodeFilter extends OncePerRequestFilter {
    private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(SecurityConstants.LOGIN_PATH, "POST");

    public static final String VERIFY_CODE_PARAM_KEY = "verifyCode";
    public static final String VERIFY_CODE_KEY_PARAM_KEY = "verifyCodeKey";
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 如果是登录请求则校验验证码
        if (LOGIN_PATH_REQUEST_MATCHER.matches(request)){
            String code = request.getParameter(VERIFY_CODE_PARAM_KEY);
            String verifyCodeKey = request.getParameter(VERIFY_CODE_KEY_PARAM_KEY);

            // 由于这个不是bean,不能通过注入的方式获取,所以通过SpringUtil工具类获取
            RedisTemplate redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class);
            String cacheCode =  Convert.toStr(redisTemplate.opsForValue().get(SecurityConstants.VERIFY_CODE_CACHE_PREFIX + verifyCodeKey));
            if (cacheCode == null) {
                // 验证码过期
                ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_TIMEOUT);
                return;
            }
            if (!StrUtil.equals(cacheCode,code)) {
                // 验证码错误
                ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_ERROR);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
}

jwt拦截器

需求:处理登录请求以外的请求,每次需要验证jwt令牌,如果没问题则在该线程请求附加权限身份。

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(SecurityConstants.LOGIN_PATH, "POST");

    private final JwtTokenManager tokenManager;
    public JwtAuthenticationFilter(JwtTokenManager jwtTokenManager) {
        this.tokenManager = jwtTokenManager;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (!LOGIN_PATH_REQUEST_MATCHER.matches(request)) {
            String jwt = RequestUtils.resolveToken(request);
            if (StringUtils.hasText(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) {
                try {
                    Claims claims = this.tokenManager.parseAndValidateToken(jwt);
                    Authentication authentication = this.tokenManager.getAuthentication(claims);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                } catch (Exception e) {
                    ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID);
                }
            } else {
                ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID);
            }
        }
        chain.doFilter(request, response);
    }
}

思考

这2个拦截器一个需要登录一个除去登录,那么是不是可以放到一个拦截器里面去。各走各的。这样也明确一点。也不用2个拦截器找了。

如果改了记得改securityFilterChain

用户登录

需求:输入用户名和密码,验证用户身份。
需要写一个类继承UserDetails
在这里插入图片描述

另一个实现类继承SysUserService(SysUserDetailsService)
在这里插入图片描述
这2个一个是存储一个是查询。然后会自动和输入的username以及password进行比对
验证流程后面总结一个spring security的文。

SysUserDetailsService作用是查询该用户名的角色信息并返回UserDetails。

查询,调用SysUserService根据用户名查询所有的
在这里插入图片描述
由于认证信息需要角色信息和权限所以我们需要联表查询角色信息。
在依据角色信息查询权限。

        select u.id userId,
               u.name username,
               u.password,
               u.role,
               u.avatar,
               u.email,
               u.status,
               r.code
        from sys_user u
                 left join sys_user_role sur on u.id = sur.user_id
                 left join sys_role r on sur.role_id = r.id
        where u.name = #{username}
          AND u.deleted = 0

然后在依据角色查询权限
不过我感觉这个type硬编码挺严重的,也算学习一下这种mybatis里面枚举了。
如果没用角色则m.id = -1让其没权限。

<select id="listRolePerms" resultType="java.lang.String">
        select distinct m.perm
        from sys_menu m
        inner join sys_role_menu rm on m.id = rm.menu_id
        inner join sys_role r on r.id = rm.role_id
        where m.type = '${@com.yu.common.enums.MenuTypeEnum@BUTTON.getValue()}'
        and m.perm is not null
        <choose>
            <when test="roles!=null and roles.size()>0">
                and r.code in
                <foreach collection="roles" item="role" open="(" close=")" separator=",">
                    #{role}
                </foreach>
            </when>
            <otherwise>
                and m.id = -1
            </otherwise>
        </choose>
    </select>

controller验证,很明确的流程就是封装输入的,然后进行验证。失败了会报错返回。
成功则生成token将权限放入redis,将角色,用户名,id封装进jwt
然后进行返回。接下来查看jwtTokenManager.createToken

    @Operation(summary = "登录")
    @PostMapping("/login")
    public Result<LoginResult> login(
            @Parameter(description = "用户名", example = "admin") @RequestParam String username,
            @Parameter(description = "密码", example = "123456") @RequestParam String password
    ) {
        // 存储username和password
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                username.toLowerCase().trim(),
                password
        );
        // 验证用户名和密码
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        // 生成token
        String accessToken = jwtTokenManager.createToken(authentication);
        // 返回token
        LoginResult loginResult = LoginResult.builder()
                .tokenType("Bearer")
                .accessToken(accessToken)
                .build();
        return Result.success(loginResult);
    }
    @Schema(description ="登录响应对象")
    @Builder
    public static record LoginResult(
            @Schema(description = "访问token")
            String accessToken,

            @Schema(description = "token 类型",example = "Bearer")
            String tokenType,

            @Schema(description = "刷新token")
            String refreshToken,

            @Schema(description = "过期时间(单位:毫秒)")
            Long expires
    ) {
    }

jwt管理

采用hutool工具包进行jwt管理,以前用过java-jwt的,这次试试hutool。

    /**
     * 创建token
     *
     * @param authentication auth info
     * @return token
     */
    public String createToken(Authentication authentication) {
        SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();

        // 角色放入JWT的claims
        Set<String> roles = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority).collect(Collectors.toSet());

        // 权限数据多放入Redis
        Set<String> perms = userDetails.getPerms();
        redisTemplate.opsForValue().set(SecurityConstants.USER_PERMS_CACHE_PREFIX + userDetails.getUserId(), perms);

        Map<String, Object> claims = Map.of(
                JWTPayload.ISSUED_AT, DateTime.now(),
                JWTPayload.EXPIRES_AT, DateTime.now().offset(DateField.SECOND, tokenTtl),
                "jti", IdUtil.fastSimpleUUID(),
                "userId", userDetails.getUserId(),
                "username", userDetails.getUsername(),
                "authorities", roles);

        return JWTUtil.createToken(claims, getSecretKeyBytes());
    }

验证

http测试:
之前测试挺头疼的。
需要先发送验证码的。
然后去base64转图片(后面直接打印了结果了)
进行测试
在这里插入图片描述
成功
在这里插入图片描述
后面去vue3前端测了。用的是有来开源vue3-element-admin修改。
成功了!
在这里插入图片描述

用户注销

待续

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/909079.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一、pikachu之SQL注入

文章目录 一、SQL注入步骤二、数字型注入(post注入)三、字符型注入(get)四、搜索型注入五、XX型注入六、“insert/updata”注入 一、SQL注入步骤 寻找传参页面&#xff1b;判断是否存在注入点&#xff1b; 字符型注入&#xff1f;数字型注入&#xff1f; 判断字段的数量&#…

二、4.makefile、断言和位图内存池

在 Linux 中&#xff0c;文件分为属性和数据两部分&#xff0c;每个文件有三种时间&#xff0c;分别用于记录与文件属性和文件数据相关的时间&#xff0c;这三个时间分别是 atime、 mtime 、 ctime 。 atime&#xff0c;即 access time&#xff0c;和示访问文件数据部分时间&a…

SprintBoot Bean管理

SpringBoot中获取Bean对象 下面这段代码在测试类中进行&#xff0c;下面通过三种方式获取bean对象。 import org.springframework.context.ApplicationContext;// 注意一定要引入上面的依赖SpringBootTest class TliasWebManagementApplicationTests {Testvoid getBeanTest(){…

AI夏令营第三期 - 基于论文摘要的文本分类与关键词抽取挑战赛笔记

赛题&#xff1a;基于论文摘要的文本分类与关键词抽取 背景&#xff1a;高效的从海量医学文献中提取疾病诊断和治疗关键信息 任务&#xff1a;通过论文摘要判断论文是否为医学文献 样例 数据集&#xff1a;csv文件&#xff0c;字段&#xff1a;标题、作者、摘要、关键词 评价指…

uniapp 引入海康H5player实现视频监控的播放

uniapp直接调用海康H5player方法&#xff0c;只能在web浏览器页面正常播放&#xff0c;实机运行会因为找不到文件的相对路径而报错无法播放。因此需要通过web-view或iframe引入html的方式来实现实时视频监控的播放。具体步骤如下&#xff1a; 1、首先将海康h5player的相关文件…

服装定制小程序的秘诀

随着互联网的快速发展&#xff0c;越来越多的企业开始关注互联网商业模式的创新。其中&#xff0c;定制化服务成为了各行各业的关注焦点之一。尤其是在服装行业&#xff0c;定制化服装已经成为许多消费者的追求。而面对这一市场需求&#xff0c;如何创造成功的互联网新商业模式…

https非对称加密算法

非对称加密算法原理 在客户端公开公钥&#xff0c;服务端保存私钥 1.客户端第一次请求先请求443端口&#xff0c;从443端口下载公钥。 2.客户端将数据进行公钥算法进行加密&#xff0c;将秘文发送到服务端 服务端收到秘文后&#xff0c;通过私钥算法进行解密得到明文数据。…

【STM32】开发方式:寄存器、CMSIS、SPL、HAL、LL、RTOS

阅读本专栏其他文章&#xff0c;有助于理解本文。 文章目录 一、开发库选择1.1 概述1.2 CMSIS库1.3 SPL库1.4 HAL 库1.5 LL库1.6 寄存器开发 二、代码对比2.1 使用寄存器2.2 使用CMSIS库2.3 使用SPL库2.4 使用HAL库2.5 使用LL库2.6 使用RTOS 三、软件配置 一、开发库选择 1.1 …

【linux】使用rpm下载mysql

1/ 2/ 3/ 4/ 5/ 6/ 7/ 8/ 9/ 10/ 11/ 12/ 13/

JDBC回顾

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 JDBC回顾 前言一、JDBC1.JDBC是什么&#xff1f;2.如何使用&#xff1f;&#xff08;1&#xff09;注册驱动&#xff08;2&#xff09;获取连接&#xff08;3&#xff09;操作…

二叉树题目:二叉树的层序遍历

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;二叉树的层序遍历 出处&#xff1a;102. 二叉树的层序遍历 难度 4 级 题目描述 要求 给你二叉树的根结点 root \texttt{root} root&#xff0c;返…

c++ day1

作业: 1&#xff0e;整理思维导图 2.定义一个命名空间Myspace&#xff0c;包含以下函数:将一个字符串中的所有单词进行反转&#xff0c;并输出反转后的结果。例如&#xff0c;输入字符串 为"Hello World"&#xff0c;输出结果为"olleH dlroW"&#xff0c;并…

python:pyqt5 + cef 模仿 mdict 界面

PyQt5 安装参见&#xff1a;python&#xff1a;PyQt5 简单示例 cefpython 入门 参考: Python GUI: cefpython3的简单分析和应用 pip install cefpython3 cefpython3-66.1-py2.py3-none-win_amd64.whl (69.0 MB) Successfully installed cefpython3-66.1 cd \Python37\Lib\si…

prometheus blackbox_exporter安装

目录 一、准备工作1.1 安装或关闭以下服务1.2 本次安装环境 二、安装blackbox_exporter2.1 下载并解压2.2配置2.3测试 三、配置blackbox_exporter3.1配置blackbox.yml3.2 开启blackbox_exporter3.3配置prometheus.yml 四、其他4.1server returned HTTP status 400 Bad Request …

Docker 存储驱动解析:选择最适合你的存储方案

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

分类预测 | MATLAB实现NGO-DBN北方苍鹰优化深度置信网络多特征输入分类预测

分类预测 | MATLAB实现NGO-DBN北方苍鹰优化深度置信网络多特征输入分类预测 目录 分类预测 | MATLAB实现NGO-DBN北方苍鹰优化深度置信网络多特征输入分类预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 MATLAB实现NGO-DBN北方苍鹰优化深度置信网络多特征输入…

【QT】重写QAbstractLIstModel,使用ListView来显示多列数据

qt提供了几个视图来进行信息的列表显示&#xff0c;QListView可以用来显示继承QStractListModel的字符串列表中的字符串&#xff0c;默认的模型里面只包含一列的内容&#xff1a; 这里以qml为例子&#xff0c;先新建一个qml的项目&#xff0c;示例代码如下&#xff1a; 先创建一…

查看所有数据库各表容量大小

查看所有数据库各表容量大小 1. 查看所有数据库各表容量大小2.查看指定数据库容量大小3. 查看所有数据库容量大小 1. 查看所有数据库各表容量大小 select table_schema as 数据库, table_name as 表名, table_rows as 记录数, truncate(data_length/1024/1024, 2) as 数据容量…

Redis 持久化的手段有哪些 ?RDB 和 AOF 有什么区别 ?

目录 1. Redis 持久化的手段有哪些 2. RDB 和 AOF 有什么区别 2.1 RDB 持久化 2.2 AOF 持久化 2.2.1 AOF 持久化策略有哪些 3. 混合持久化是如何执行的&#xff08;了解&#xff09; 1. Redis 持久化的手段有哪些 Redis 持久化的手段有三种&#xff1a; 快照方式&#…

模型预测笔记(二):结合SMOTE来进行数据不均衡处理实操

文章目录 数据不均衡危害如何解决SMOTE原理代码效果 数据不均衡危害 在模型预测中&#xff0c;数据不均衡是指不同类别的样本数量差异很大。这种情况可能会对模型的性能和结果产生一些危害&#xff1a; 偏斜的预测结果&#xff1a;由于某些类别的样本数量较少&#xff0c;模型…