【SpringBoot】之Security进阶使用

发布时间:2023年12月22日

??🎉🎉欢迎来到我的CSDN主页!🎉🎉

🏅我是君易--鑨,一个在CSDN分享笔记的博主。📚📚

🌟推荐给大家我的博客专栏《SpringBoot开发之Security系列》。🎯🎯

🎁如果感觉还不错的话请给我关注加三连吧!🎁🎁


前言

? ? ? ? 上一期的博客中我们一起了解了什么是Security,以及在SpringBoot中集成Security使用,以及一些的基础用法和一些案例演示。今天的这期博客基于上期博客进一步完善使用,请仔细阅读。

一、Security联合数据库使用(登陆功能)

1. 创建所需表

? ? ? ? 在我们的数据库中国创建好我们所需要的表,有用户信息表、角色信息表、模块信息表(权限信息表)、用户角色表、角色模块表这五张表。用于后续的权限设置

表名说明
sys_user用户信息表
sys_role角色信息表
sys_module模块信息表(权限信息表)
sys_user_role用户角色表
sys_role_module角色模块表

?表之间的关系说明

? ? ? ? ?我们可以对用户信息表中的字段进行修改,只保留关键字段,也可以不修改。(我这里只保留了四个字段)

2. 配置文件?

?????????当然我们要连接数据库需要我们进行导入Mybatis-Plus的依赖以及数据库的依赖。

? ? ? ? 当然还需要我们的生成器等等一些配置类?

?MySQLGenerator.java

package com.yx.security.config;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Slf4j
public class MySQLGenerator {

    private final static String URL = "jdbc:mysql://localhost:3306/bookshop";
    private final static String USERNAME = "root";
    private final static String PASSWORD = "root123";

    private final static DataSourceConfig.Builder DATA_SOURCE_CONFIG =
            new DataSourceConfig.Builder(URL, USERNAME, PASSWORD);

    public static void main(String[] args) {
        FastAutoGenerator.create(DATA_SOURCE_CONFIG)
                .globalConfig(
                        (scanner, builder) ->
                                builder.author(scanner.apply("请输入作者名称?"))
                                        .outputDir(System.getProperty("user.dir") + "\\src\\main\\java")
                                        .commentDate("yyyy-MM-dd")
                                        .dateType(DateType.TIME_PACK)
                                        .enableSwagger()
                )
                .packageConfig((builder) ->
                        builder.parent("com.zking.security")
                                .entity("pojo")
                                .service("service")
                                .serviceImpl("service.impl")
                                .mapper("mapper")
                                .xml("mapper.xml")
                                .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "\\src\\main\\resources\\mapper"))
                )
                .injectionConfig((builder) ->
                        builder.beforeOutputFile(
                                (a, b) -> log.warn("tableInfo: " + a.getEntityName())
                        )
                )
                .strategyConfig((scanner, builder) ->
                        builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                                .addTablePrefix("tb_", "t_", "lay_", "meeting_", "sys_", "t_medical_")
                                .entityBuilder()
                                .enableChainModel()
                                .enableLombok()
                                .enableTableFieldAnnotation()
                                .controllerBuilder()
                                .enableRestStyle()
                                .enableHyphenStyle()
                                .build()
                )
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();
    }

    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }

}

? ? ? ? 别忘记了根据项目信息进行修改?

?yml文件配置

server:
    port: 8080
spring:
    freemarker:
        # 设置freemarker模板后缀
        suffix: .ftl
        # 设置freemarker模板前缀
        template-loader-path: classpath:/templates/
        enabled: true
    datasource:
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/bookshop

生成对应的表

? ? ? ? 我们这就只生成用户信息表。

?

? ? ? ? mapper记得打对应的注解,以及在项目启动类中加上扫描mapper的注解

3. 代码编写修改

??IndexController.java
package com.yx.security.controller;

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {


    @RequestMapping("/")
    public String toLogin(){
        return "login";
    }

    @RequestMapping("/index")
    public String toIndex(){
        return "index";
    }


}
index.flt
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h1>用户登录</h1>
<form action="/userLogin" method="post">
    <p>
        <label>用户:<input type="text" name="username"/></label>
    </p>
    <p>
        <label>密码:<input type="password" name="password"/></label>
    </p>
    <input type="submit" value="登录"/>
    <p>${msg!""}</p>
</form>
</body>
</html>

? ? ? ? 首先在实体类中实现一个接口——UserDetails类,实现之后会进行一个重新编写四个方法。?

? ? ? ? ?但是重写方法是返回的值是死的,应该是重数据库中获取的返回值,因此改为定义四个属性及添加指定字段到表中。

? ? ? ? ?数据库表中的字段注意数驼峰命名用下滑线隔开,默认值都是1.,authorities不是数据库中的字段是代表权限的,待会在类上会打上注释说明,字段会打上注释说明是数据库上的那些字段。

? ? ? ? 接下来就是实现功能的代码编写了,首先在实体接口实现类实现UserDetailsService,并且重新方法。

? ? ? ? ?对其配置类进行修改调整

? ? ? ? ?直接运行项目进行登陆

?演示

二、密码加密方式

1. 明文不加密

? ? ? ? ?如果存在多个密码加密方法,并都注释了@Bean注解,则可以用@Primary注解标记使用那个方法。

?2. 自定义MD5加密

????????创建自定义MD5加密类并实现PasswordEncoder

PasswordEncoder.java

public class CustomMd5PasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        //对密码进行 md5 加密
        String md5Password = DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
        System.out.println(md5Password);
        return md5Password;
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        // 通过md5校验
        System.out.println(rawPassword);
        System.out.println(encodedPassword);
        return encode(rawPassword).equals(encodedPassword);
    }
}

?????????修改SecurityConfig配置类,更换密码编码器

@Bean
public PasswordEncoder passwordEncoder(){
    // 自定义MD5加密方式:
    return new CustomMd5PasswordEncoder();
}

3.?BCryptPasswordEncoder密码编码器

????????BCryptPasswordEncoderSpring Security中一种基于bcrypt算法的密码加密方式。bcrypt算法是一种密码哈希函数,具有防止彩虹表攻击的优点,因此安全性较高。

????????使用BCryptPasswordEncoder进行密码加密时,可以指定一个随机生成的salt值,将其与原始密码一起进行哈希计算。salt值可以增加密码的安全性,因为即使两个用户使用相同的密码,由于使用不同的salt值进行哈希计算,得到的哈希值也是不同的。

????????在Spring Security中,可以通过在SecurityConfig配置类中添加以下代码来使用BCryptPasswordEncoder进行密码加密:

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

三、记住我功能实现(RememberMe )

????????在实际开发中,为了用户登录方便常常会启用记住我(Remember-Me)功能。如果用户登录时勾选了“记住我”选项,那么在一段有效时间内,会默认自动登录,免去再次输入用户名、密码等登录操作。该功能的实现机理是根据用户登录信息生成 Token 并保存在用户浏览器的 Cookie 中,当用户需要再次登录时,自动实现校验并建立登录态的一种机制。

Spring Security提供了两种 Remember-Me 的实现方式:

  • 简单加密 Token:用散列算法加密用户必要的登录系信息并生成 Token 令牌。

  • 持久化 Token:数据库等持久性数据存储机制用的持久化 Token 令牌。

1. 创建一张数据表存储信息

  • 创建数据库表 persistent_logins,用于存储自动登录信息

CREATE TABLE `persistent_logins` (
  `username` varchar(64) NOT NULL,
  `series` varchar(64) PRIMARY KEY,
  `token` varchar(64) NOT NULL,
  `last_used` timestamp NOT NULL
);

2.?修改SecurityConfig配置类

????????Remember-Me 功能的开启需要在configure(HttpSecurity http)方法中通过http.rememberMe()配置,该配置主要会在过滤器链中添加 RememberMeAuthenticationFilter 过滤器,通过该过滤器实现自动登录。

? ? ? ? 在配置类中添加一个方法

/**
	 * 配置持久化Token方式,注意tokenRepository.setCreateTableOnStartup()配置
	 */
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        // 设置为true要保障数据库该表不存在,不然会报异常哦
        // 所以第二次打开服务器应用程序的时候得把它设为false
        tokenRepository.setCreateTableOnStartup(false);
        return tokenRepository;
    }
    

? ? ? ? ?没创建表的可以将setCreateTableOmStartup的属性值该为true运行时会自动创建。

3. 前端添加组件

    <input type="checkbox" name="remember-me"/>记住我<br/>

4. 配置配置类获取组件

 .rememberMe()
                // 指定 rememberMe 的参数名,用于在表单中携带 rememberMe 的值。
                //.rememberMeParameter("remember-me")
                // 指定 rememberMe 的有效期,单位为秒,默认2周。
                .tokenValiditySeconds(30)
                // 指定 rememberMe 的 cookie 名称。
                .rememberMeCookieName("remember-me-cookie")
                // 指定 rememberMe 的 token 存储方式,可以使用默认的 PersistentTokenRepository 或自定义的实现。
                .tokenRepository(persistentTokenRepository())
                // 指定 rememberMe 的认证方式,需要实现 UserDetailsService 接口,并在其中查询用户信息。
                .userDetailsService(userDetailsService)

演示效果

? ? ? ? 当我们删除网页重新访问主页时会直接进入不会需要登陆?,数据库中也会生成对应数据,以及网页会生成Cookie

? ? ? ? 当我们删除数据库表中的登陆信息数据时,重新刷新主页则访问不了?

四、CSRF防御

????????CSRFCross-Site Request Forgery,跨站请求伪造)是一种利用用户已登录的身份在用户不知情的情况下发送恶意请求的攻击方式。攻击者可以通过构造恶意链接或者伪造表单提交等方式,让用户在不知情的情况下执行某些操作,例如修改密码、转账、发表评论等。

为了防范CSRF攻击,常见的做法是在请求中添加一个CSRF Token(也叫做同步令牌、防伪标志),并在服务器端进行验证。CSRF Token是一个随机生成的字符串,每次请求都会随着请求一起发送到服务器端,服务器端会对这个Token进行验证,如果Token不正确,则拒绝执行请求。

1.页面代码加入_csrf隐藏域

 <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

? ? ? ?不添加默认开启了防止跨域攻击的功能,任何 POST 提交到后台的表单都要验证是否带有 _csrf 参数,一旦传来的 _csrf 参数不正确,服务器便返回 403 错误。

如果针对一些特定的请求接口,不需要进行CSRF防御,可以通过以下配置忽略:

http.csrf().ignoringAntMatchers("/upload"); // 禁用/upload接口的CSRF防御

2. SpringSecurity中如何使用CSRF

????????在Spring Security中,防范CSRF攻击可以通过启用CSRF保护来实现。启用CSRF保护后,Spring Security会自动在每个表单中添加一个隐藏的CSRF Token字段,并在服务器端进行验证。如果Token验证失败,则会抛出异常,从而拒绝执行请求。启用CSRF保护的方式是在Spring Security配置文件中添加.csrf()方法,例如:

? http.csrf().disable();是禁用CSRF

3. 测试

? ? ? ? ?我们复制一份登陆表单不携带csrf隐藏域进行测试

? ? ? ? 由上图所知,第一个登陆添加了csrf隐藏域能够成功访问主页,第二个登陆没有添加则无法访问。


.csrf()主要方法介绍:
方法说明
disable()关闭CSRF防御
csrfTokenRepository()设置CookieCsrfTokenRepository实例,用于存储和检索CSRF令牌。与HttpSessionCsrfTokenRepository不同,CookieCsrfTokenRepositoryCSRF令牌存储在cookie中,而不是在会话中。
ignoringAntMatchers()设置一组Ant模式,用于忽略某些请求的CSRF保护。例如,如果您想要忽略所有以/api/开头的请求,可以使用.ignoringAntMatchers("/api/**")
csrfTokenManager()设置CsrfTokenManager实例,用于管理CSRF令牌的生成和验证。默认情况下,Spring Security使用DefaultCsrfTokenManager实例来生成和验证CSRF令牌。
requireCsrfProtectionMatcher()设置RequestMatcher实例,用于确定哪些请求需要进行CSRF保护。默认情况下,Spring Security将对所有非GET、HEAD、OPTIONS和TRACE请求进行CSRF保护。

🎉🎉本期的博客分享到此结束🎉🎉

📚📚各位老铁慢慢消化📚📚

🎯🎯下期博客博主会带来新货🎯🎯

🎁三连加关注,阅读不迷路?!🎁

文章来源:https://blog.csdn.net/weixin_74352229/article/details/135137820
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。