Spring Security入门教程:Spring Security的拓展功能

除了spring security的基础的用法,我们还有一些他的拓展功能,这篇文章会给大家介绍一下。Spring security有哪些拓展功能,并且怎么去使用它才能发挥spring security的全部形态。

开启记住我功能

相信大家在网上冲浪的时候,肯定见过这个,记住我的功能。例如我们拿这次的网易邮箱来举例,大家在登录邮箱的时候,会发现这里有一个30天内免登录,它其实的原理就是一个记住我的功能。那么记住我选了之后呢,也就是说下次我们再进入浏览器,进入这个网页之后。我们就可以不输入用户名和密码,直接进入这个系统的登录页面。 。

那么spring security是默认是没有记住我这个功能的,我们仅仅需要一行代码就可以让security实现这个功能。

会话管理

挤出另外一个用户

大家看到这个标题可能有些不理解,但是我说一个场景大家就可以理解了。例如我们在使用QQ的时候,如果另外一个人从另外一台手机上登录QQ,我们这台的QQ就会被挤下线。在spring security中,这就叫会话管理,我们可以通过如下配置进行一个实现。

cpp 复制代码
package com.masiyi.springsecuritydemo.config;

import com.masiyi.springsecuritydemo.handler.MyAuthenticationFailureHandler;
import com.masiyi.springsecuritydemo.handler.MyAuthenticationSuccessHandler;
import com.masiyi.springsecuritydemo.handler.MyLogoutSuccessHandler;
import com.masiyi.springsecuritydemo.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.session.HttpSessionEventPublisher;

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {



    /**
     * 这里有两种方式 authorizeHttpRequests 和 authorizeRequests
     * @param http the {@link HttpSecurity} to modify
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .mvcMatchers("/user/register").permitAll()
                .anyRequest().authenticated()
                .and().formLogin()
                .successHandler(new MyAuthenticationSuccessHandler())
                .failureHandler(new MyAuthenticationFailureHandler())
                .and()
                .logout().logoutSuccessHandler(new MyLogoutSuccessHandler())
                .and().csrf().disable()
                .rememberMe() //开启记住我功能
                .and()
                .sessionManagement()  //开启会话管理
                .maximumSessions(1);  //设置会话并发数为 1 在SessionRegistryImpl中管理key,key存的是对象,必须要重写User的equals和hashcode方法才能判断用户相等
        ;
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new MyUserDetailService();
    }



    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());

    }

    /**
     * 指定加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 使用BCrypt加密密码
        return new BCryptPasswordEncoder();
    }
}

其中这段代码就是核心:

java 复制代码
  .and()
                .sessionManagement()  //开启会话管理
                .maximumSessions(1);  //设置会话并发数为 1 在SessionRegistryImpl中管理key,key存的是对象,必须要重写User的equals和hashcode方法才能判断用户相等

key存的是对象,必须要重写User的equals和hashcode方法才能判断用户相等,否则的话,spring security就认为不是同一个用户。至于为什么要重启hashcode跟equal方法,其实这就是JAVA基础的问题了,因为只有重写了这两个方法,然后这里面的属性如果相等,那么就证明这个引用对象是相等的。。

java 复制代码
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean accountNonExpired;
    private Boolean accountNonLocked;
    private Boolean credentialsNonExpired;
    private List<Role> roles = new ArrayList<>();

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

写完了这些配置之后,我们可以用两个浏览器模拟两个session。一个模拟器正常登陆之后,另外一个模拟器也正常登陆,这个时候我们返回第一个浏览器,刷新一下,就会发现第一个浏览器已经被挤出去了。

用redis存储session

但是我们刚刚写的东西它是基于内存的。那如果说我们的应用在发布的时候会重启服务,重启服务之后,那么内存里面的东西全部会清空。那么我们肯定会想办法把它持久化到一个数据库中,所以说现在数据库我们就选择用redis。而且市面上最常见的解决方案也是用redis去存储这个会话。

如果我们要实现这个功能,第一步先在pom文件中引入两个jar包。

java 复制代码
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

之后我们在properties文件中设置redis的连接地址。

java 复制代码
spring.redis.host=192.168.0.73
spring.redis.port=30197
spring.redis.password=E(cSuYdp76
spring.redis.database=2

最后我们只需要在配置类中按照如下配置即可。

java 复制代码
package com.masiyi.springsecuritydemo.config;

import com.masiyi.springsecuritydemo.handler.MyAuthenticationFailureHandler;
import com.masiyi.springsecuritydemo.handler.MyAuthenticationSuccessHandler;
import com.masiyi.springsecuritydemo.handler.MyLogoutSuccessHandler;
import com.masiyi.springsecuritydemo.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {


    private FindByIndexNameSessionRepository sessionRepository;


    @Autowired
    @Lazy
    public void SecurityConfig(FindByIndexNameSessionRepository sessionRepository) {
        this.sessionRepository = sessionRepository;
    }

    /**
     * 这里有两种方式 authorizeHttpRequests 和 authorizeRequests
     * @param http the {@link HttpSecurity} to modify
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .mvcMatchers("/user/register").permitAll()
                .anyRequest().authenticated()
                .and().formLogin()
                .successHandler(new MyAuthenticationSuccessHandler())
                .failureHandler(new MyAuthenticationFailureHandler())
                .and()
                .logout().logoutSuccessHandler(new MyLogoutSuccessHandler())
                .and().csrf().disable()
                .rememberMe() //开启记住我功能
                .and()
                .sessionManagement()  //开启会话管理
                .maximumSessions(1);  //设置会话并发数为 1 在SessionRegistryImpl中管理key,key存的是对象,必须要重写User的equals和hashcode方法才能判断用户相等
        ;
    }



    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry() {
        return new SpringSessionBackedSessionRegistry(sessionRepository);
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new MyUserDetailService();
    }



    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());

    }

    /**
     * 指定加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 使用BCrypt加密密码
        return new BCryptPasswordEncoder();
    }
}

开启 CSRF

CSRF(Cross-Site Request Forgery)跨站请求伪造,是一种网络安全攻击方式,攻击者通过伪造用户的请求,利用用户在其他网站已经登录的身份来执行非法操作。攻击者可以在用户不知情的情况下发送恶意请求,例如转账、更改密码等操作,从而造成用户的损失或泄露敏感信息。

举例来说,假设用户在银行网站A上已经登录并保持会话,攻击者在恶意网站B上放置了一个恶意链接。当用户访问恶意网站B时,网站B中的恶意代码会自动向银行网站A发送一个请求,比如转账请求。由于用户在银行网站A上已经登录,该请求会被银行网站A误认为是用户的合法操作,从而执行转账操作,造成损失。

而spring security开启CSRF也非常简单

java 复制代码
    /**
     * 这里有两种方式 authorizeHttpRequests 和 authorizeRequests
     * @param http the {@link HttpSecurity} to modify
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .mvcMatchers("/user/register").permitAll()
                .anyRequest().authenticated()
                .and().formLogin()
                .successHandler(new MyAuthenticationSuccessHandler())
                .failureHandler(new MyAuthenticationFailureHandler())
                .and()
                .logout().logoutSuccessHandler(new MyLogoutSuccessHandler())
                .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and()
                .rememberMe() //开启记住我功能
                .and()
                .sessionManagement()  //开启会话管理
                .maximumSessions(1);  //设置会话并发数为 1 在SessionRegistryImpl中管理key,key存的是对象,必须要重写User的equals和hashcode方法才能判断用户相等
        ;
    }

把前面的disable换成.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and()即可

跨域处理方案

跨域(Cross-Origin)指的是在浏览器中,一个网页的脚本试图访问另一个网页的内容时所涉及的安全限制。为了保护用户数据安全,浏览器会限制跨域。我们现在的开发模式是前后端分离。那么这个时候我们就会遇到跨域的问题。为前端的端口跟后端的端口不是一个端口。

常见的跨域处理方案包括:

  1. CORS(跨域资源共享):通过在服务器端设置响应头部信息,允许指定的源(域名、协议、端口)访问资源,从而实现跨域请求。可以通过配置Access-Control-Allow-Origin等响应头来控制跨域访问。

  2. JSONP(JSON with Padding):利用<script>标签的跨域特性,通过动态创建<script>标签来加载包含回调函数的JSON数据,从而实现跨域数据传输。

  3. 代理服务器:在服务器端设置代理,将跨域请求转发到目标服务器,然后将响应返回给客户端,绕过浏览器的跨域限制。

  4. 跨域资源嵌入(Cross-Origin Resource Inclusion,CORS):允许在不同域名下加载资源,如图片、样式表、脚本等,但不允许对资源进行操作。

而spring security中同样已经通过CORS解决了这样的问题。

java 复制代码
/**
     * 这里有两种方式 authorizeHttpRequests 和 authorizeRequests
     *
     * @param http the {@link HttpSecurity} to modify
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .mvcMatchers("/user/register").permitAll()
                .anyRequest().authenticated()
                .and().formLogin()
                .successHandler(new MyAuthenticationSuccessHandler())
                .failureHandler(new MyAuthenticationFailureHandler())
                .and()
                .logout().logoutSuccessHandler(new MyLogoutSuccessHandler())
                .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and()
                .cors() //跨域处理方案
                .configurationSource(configurationSource())
                .and()
                .rememberMe() //开启记住我功能
                .and()
                .sessionManagement()  //开启会话管理
                .maximumSessions(1)  //设置会话并发数为 1 在SessionRegistryImpl中管理key,key存的是对象,必须要重写User的equals和hashcode方法才能判断用户相等
        ;
    }
  /**
     *允许所有来源的请求(*)使用任意的请求头和请求方法,并且设置了最大缓存时间为 3600 秒。
     * @return
     */
    CorsConfigurationSource configurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
        corsConfiguration.setAllowedMethods(Arrays.asList("*"));
        corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
        corsConfiguration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

这段代码的作用是允许所有来源("")的请求访问该服务,并设置了允许的请求头和请求方法,以及缓存时间。通过这样的配置,可以解决跨域请求时浏览器的安全限制,实现跨域资源共享。

好了,本篇文章就讲完了,但是spring ecurity的拓展功能远不止如此。关于更多的拓展功能,这里就不在一一做介绍,这里只列出了我们平时工作中和学习中常用的一些拓展功能。

项目的地址就在 gitee.com/WangFuGui-M...

如果大家对这篇文章或者专栏有兴趣或者对大家有所帮助的话,欢迎关注点赞。加评论。 我们spring security的进阶专栏见。

相关推荐
hrrrrb3 分钟前
【Spring Security】Spring Security 密码编辑器
java·hive·spring
Victor3564 分钟前
Redis(58)如何配置和查看Redis的慢查询日志?
后端
Victor3566 分钟前
Redis(59)Redis的主从复制是如何实现的?
后端
豐儀麟阁贵6 分钟前
2.3变量与常量
java·开发语言
摇滚侠1 小时前
Spring Boot 3零基础教程,自动配置机制,笔记07
spring boot·笔记·后端
兮动人1 小时前
Eureka注册中心通用写法和配置
java·云原生·eureka
程序员爱钓鱼2 小时前
Go语言实战案例——进阶与部署篇:性能优化与 pprof 性能分析实践
后端·google·go
爱编程的小白L3 小时前
基于springboot志愿服务管理系统设计与实现(附源码)
java·spring boot·后端
聪明的笨猪猪5 小时前
Java Redis “持久化”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
聪明的笨猪猪6 小时前
Java Redis “核心基础”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试