likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 1

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

开启记住我功能

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

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

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

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

会话管理

挤出另外一个用户

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

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();
    }
}

其中这段代码就是核心:

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

key存的是对象,必须要重写User的equals和hashcode方法才能判断用户相等,否则的话,spring security就认为不是同一个用户。至于为什么要重启hashcode跟equal方法,其实这就是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包。

 <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的连接地址。


spring.redis.host=192.168.0.73
spring.redis.port=30197
spring.redis.password=E(cSuYdp76
spring.redis.database=2

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

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也非常简单

    /**
     * 这里有两种方式 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()即可

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

跨域处理方案

跨域(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解决了这样的问题。

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

/**
     * 这里有两种方式 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的进阶专栏见。

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