【SpringBoot3】Spring Security 常用配置总结

注:本文基于Spring Boot 3.2.1 以及 Spring Security 6.2.1

相关文章

【SpringBoot3】Spring Security 核心概念
【SpringBoot3】Spring Security 常用注解
【SpringBoot3】Spring Security 详细使用实例(简单使用、JWT模式)
【SpringBoot3】Spring Security使用mybatis-plus存储用户角色权限,实现动态权限处理

一、Spring Security 常用配置总结

1、记住我 rememberMe

1)配置HttpSecurity,如下代码

java 复制代码
// 开启rememberMe,登录表单增加 <input type="checkbox" name="remember-me">
http.rememberMe(Customizer.withDefaults());

2)登录表单页面增加勾选参数,在登录时勾选即可

注意参数名remember-me是默认的,也可以通过配置修改

复制代码
<input type="checkbox" name="remember-me">

登录成功后,会生成一个cookie(remember-me)

3)配置rememberMe其他参数

  • rememberMeParameter,表单参数名称
  • rememberMeCookieName,记录在浏览器的cookieName
  • tokenValiditySeconds,有效时间,单位秒
java 复制代码
http.rememberMe(rm -> rm
        .rememberMeParameter("rememberMe")
        .rememberMeCookieName("rememberMeCookieName")
        .key("rememberMe")
        .tokenValiditySeconds(1800));

2、退出处理

java 复制代码
// 退出时,设置session无效
http.logout(logout -> logout.invalidateHttpSession(true));

3、持久化登录令牌

持久化登录令牌是记住我功能的补充,可以将登录的token存储在数据库中。

参考 JdbcTokenRepositoryImpl 可以得知需要创建一张表 persistent_logins

本例使用 mybatis-plus + mysql 存储

1)创建持久化令牌表 persistent_logins

sql 复制代码
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)创建实体和Mapper

cpp 复制代码
@Data
@TableName("persistent_logins")
public class UserToken {
    private String username;

    private String series;

    @TableField("token")
    private String tokenValue;
    @TableField("last_used")
    private Date date;
}

@Mapper
public interface UserTokenMapper extends BaseMapper<UserToken> {
}

3)创建自定义持久化类 MyPersistentTokenRepositoryImpl 实现接口 PersistentTokenRepository

java 复制代码
@Service
public class MyPersistentTokenRepositoryImpl implements PersistentTokenRepository {
    @Resource
    private UserTokenMapper userTokenMapper;

    @Override
    public void createNewToken(PersistentRememberMeToken token) {
        UserToken userToken = BeanUtil.toBean(token, UserToken.class);
        userTokenMapper.insert(userToken);
    }

    @Override
    public void updateToken(String series, String tokenValue, Date lastUsed) {
        LambdaUpdateWrapper<UserToken> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.eq(UserToken::getSeries, series)
                .set(UserToken::getTokenValue, tokenValue)
                .set(UserToken::getDate, lastUsed);
        userTokenMapper.update(updateWrapper);
    }

    @Override
    public PersistentRememberMeToken getTokenForSeries(String seriesId) {
        UserToken userToken = userTokenMapper.selectOne(new LambdaQueryWrapper<UserToken>().eq(UserToken::getSeries, seriesId));
        PersistentRememberMeToken meToken = BeanUtil.toBean(userToken, PersistentRememberMeToken.class);
        return meToken;
    }

    @Override
    public void removeUserTokens(String username) {
        userTokenMapper.delete(new LambdaQueryWrapper<UserToken>().eq(UserToken::getUsername, username));
    }
}

4)在 HttpSecurity 中配置使用

java 复制代码
@Resource
private MyPersistentTokenRepositoryImpl tokenRepository;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    
    // --- 忽略其他代码 --- //
    
    http.rememberMe(rm -> rm
            .rememberMeParameter("rememberMe")
            .rememberMeCookieName("rememberMeCookieName")
            .key("rememberMe")
            // 配置持久化类
            .tokenRepository(tokenRepository)
            .tokenValiditySeconds(1800));
     
     // --- 忽略其他代码 --- //
}

5)登录,勾选记住我

打开数据库查看,存储正常

4、并发登录控制,后登录踢掉前面登录

设置每个账户最大的session数量

java 复制代码
// 每个账户最大的session数量
http.sessionManagement(sm->sm.maximumSessions(1));

5、主动踢人下线

1)配置 SessionRegistry

java 复制代码
/**
 * 通过 SessionRegistry 可以获取到当前登录的所有用户
 * @return
 */
@Bean
public SessionRegistry sessionRegistry(){
    return new SessionRegistryImpl();
}

在 HttpSecurity 中配置

java 复制代码
// 每个账户最大的session数量,配置 sessionRegistry
http.sessionManagement(sm->sm.maximumSessions(1).sessionRegistry(sessionRegistry()));

2)在controller中获取在线用户,并展示

java 复制代码
@GetMapping("/index")
public String index(Model model) {
    // 获取所有登录过的用户
    List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
    // 移除不存在session的用户
    List<Object> list = allPrincipals.stream().filter(p -> {
        List<SessionInformation> allSessions = sessionRegistry.getAllSessions(p, false);
        return !allSessions.isEmpty();
    }).toList();
    model.addAttribute("users", list);
    return "index";
}

在 index.html中显示

html 复制代码
<table>
    <tr>
        <th>用户名</th>
        <th>操作</th>
    </tr>
    <tr th:each="user:${users}">
        <td th:text="${user.username}"></td>
        <td><a th:href="@{/kickout(username=${user.username})}">下线</a></td>
    </tr>
</table>

3)增加踢人接口

java 复制代码
@GetMapping("/kickout")
public String kickout(String username) {
    List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
    for (Object principal : allPrincipals) {
        List<SessionInformation> allSessions = sessionRegistry.getAllSessions(principal, false);
        User user = (User) principal;
        if (user.getUsername().equals(username)) {
            //将所有已登录的 session 都失效
            allSessions.forEach(SessionInformation::expireNow);
        }
    }
    return "redirect:/index";
}

6、允许跨域处理

什么是跨域

CORS跨域,全称是"跨域资源共享"(Cross-Origin Resource Sharing),是一种基于HTTP标头的机制,它允许服务器指示除其自身之外的任何来源(域、方案或端口),浏览器应允许从中加载资源。这种机制允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

当浏览器从一个源(域)向另一个源(域)发出请求时,由于安全原因,浏览器会限制这种跨源请求。但是,CORS通过服务器和浏览器的协商,允许某些跨源请求得以通过。具体来说,浏览器会先向服务器发送一个"预检"请求,检查服务器是否允许实际请求。如果服务器允许,那么浏览器就会发送实际的跨域请求。

CORS需要浏览器和服务器同时支持,目前几乎所有的浏览器都支持CORS,但IE浏览器不能低于IE10版本。实现CORS的关键在于服务器,只要服务器实现了CORS接口,就可以实现跨域通信。

Spring Security 配置允许跨域

配置 HttpSecurity

java 复制代码
// 开启跨域
http.cors(Customizer.withDefaults())

7、跨域攻击防护

什么是CSRF

CSRF,全称是跨站请求攻击(Cross-Site Request Forgery),是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。简单来说,攻击者通过一些技术手段欺骗用户的浏览器去访问一个用户之前已经认证过的站点,并运行一些操作,如发邮件、发消息,甚至进行财产操作(如转账和购买商品)等。由于浏览器之前已经认证过,被访问的站点会误以为是真正的用户操作而去运行。这种攻击利用了Web中用户身份认证验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。因此,CSRF攻击可以被理解为攻击者盗用了用户的身份,以用户的名义发送恶意请求。

为了防范CSRF攻击,Web应用程序可以采取一些措施,如使用验证码、检查请求来源、使用CSRF令牌等。这些措施可以增加攻击的难度,降低攻击成功的可能性。同时,用户也应该保持警惕,不要随意点击不明链接或下载未知来源的文件,以保护自己的隐私和财产安全。

Spring Security 处理 CSRF

默认情况下,Spring Security 会自动启用 CSRF 保护,这包括在表单中包含 CSRF 令牌(token)并在处理请求时验证这个令牌。

java 复制代码
// 开启csrf 保护
http.csrf(Customizer.withDefaults());

在登录表单中,会自动生成参数_csrf ,如下所示:

参考

相关推荐
缺点内向34 分钟前
Java:创建、读取或更新 Excel 文档
java·excel
带刺的坐椅1 小时前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看2 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程3 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t3 小时前
ZIP工具类
java·zip
lang201509283 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan4 小时前
第10章 Maven
java·maven
百锦再4 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说5 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多5 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring