简介

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。

Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正威力在于它可以容易地扩展以满足定制需求。

  • spring是非常流行和成功的Java应用开发框架,Spring Security正是Spring家族中的成员。Spring Security基于Spring框架,提供了一套Web应用安全性的完整解决方案。
  • 正如你可能知道的关于安全方面的两个主要区域是"认证"和"授权",一般来说,Web应用的安全性包括认证和授权两个部分,这两点也是Spring Security重要的核心功能。
  • 1 用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能够访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗的说就是系统认为用户能够登录。
  • 2 用户授权:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗的说就是系统判断用户是否有权限去做某些事情

示例搭建

创建springboot模块,并创建一个简单的restful接口

package com.shaoch.security.springsecurityhelloworld.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author shaoch
 * @date 2022/2/9 2:48 PM
 */
@RestController
public class HelloController {

  @GetMapping("hello")
  public String hello(){
    return "hello";
  }
}

pom文件,主要引入了web starter和security starter

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.shaoch.security</groupId>
    <artifactId>spring-security-helloworld</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security-helloworld</name>
    <description>spring-security-helloworld</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

这时只是引入了一个spring security的starter,通过浏览器访问restful接口,就能够看到spring security对接口进行了保护,它默认是对所有请求进行保护的

对Spring Boot项目进行集成测试 springboot集成springsecurity_spring

 那默认的用户名和密码是什么呢,用户名可以使用 user 密码是在启动的控制台中,会多出一条这样的信息,这样登陆后就能够访问restful接口了

对Spring Boot项目进行集成测试 springboot集成springsecurity_spring boot_02

这时可能会产生几个疑问

1、为什么只引入了一个spring security的starter,所有的请求就需要认证了呢?

2、在项目中没有设置登录页面,是怎么来的?

3、用户名和密码是怎么通过认证的,此时还没有设置数据源?

原理

spring security官方的架构解释:Architecture :: Spring Securityhttps://docs.spring.io/spring-security/reference/servlet/architecture.htmlSpringSecurity提供了30多个过滤器,默认情况下,springboot在对springsecurity进行自动化配置时,会创建一个名为SpringSecurityFilterChain的过滤器,并注入到spring容器中,这个过滤器将负责所有的安全管理,包括用户认证、授权等等。具体可以参考WebSecurityConfiguration的源码。

Below is a comprehensive list of Spring Security Filter ordering:
• ChannelProcessingFilter
• WebAsyncManagerIntegrationFilter
• SecurityContextPersistenceFilter
• HeaderWriterFilter
• CorsFilter
• CsrfFilter
• LogoutFilter
• OAuth2AuthorizationRequestRedirectFilter
• Saml2WebSsoAuthenticationRequestFilter
• X509AuthenticationFilter
• AbstractPreAuthenticatedProcessingFilter
• CasAuthenticationFilter
• OAuth2LoginAuthenticationFilter
• Saml2WebSsoAuthenticationFilter
• UsernamePasswordAuthenticationFilter
• OpenIDAuthenticationFilter
• DefaultLoginPageGeneratingFilter
• DefaultLogoutPageGeneratingFilter
• ConcurrentSessionFilter
• DigestAuthenticationFilter
• BearerTokenAuthenticationFilter
• BasicAuthenticationFilter
• RequestCacheAwareFilter
• SecurityContextHolderAwareRequestFilter
• JaasApiIntegrationFilter
• RememberMeAuthenticationFilter
• AnonymousAuthenticationFilter
• OAuth2AuthorizationCodeGrantFilter
• SessionManagementFilter
• ExceptionTranslationFilter
• FilterSecurityInterceptor
• SwitchUserFilter

通过源码debug就可以看到默认加载的过滤器,在idea找到springSecurityFilterChain方法,并下载源码,在这个方法中加入断点,重启项目时会直接进入断点,可以看到默认加载的15个过滤器,并且顺序跟官网罗列的顺序一致。

对Spring Boot项目进行集成测试 springboot集成springsecurity_用户名_03

spring security自动配置

通过官网可以看到,当在springboot中引入springsecurity的依赖时,会有一个自动配置,这个bean的名字是springSecurityFilterChain

对Spring Boot项目进行集成测试 springboot集成springsecurity_spring_04

自动配置的类的名字是SpringBootWebSecurityConfiguration

对Spring Boot项目进行集成测试 springboot集成springsecurity_用户名_05

 方法中代码的含义就是,对所有的请求开启认证,可以是通过表单和httpbasic认证。

自动配置的生效条件:

1 当web容器是servlet容器时会自动加载

2 注解@ConditionalOnDefaultWebSecurity中的两个条件

class DefaultWebSecurityCondition extends AllNestedConditions {

	DefaultWebSecurityCondition() {
		super(ConfigurationPhase.REGISTER_BEAN);
	}
     //在类路径中有这两个类时
	@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
	static class Classes {

	}
    // 在spring容器中没有这两个bean时
	@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
	static class Beans {

	}

这就是为什么只引入了spring security就会有请求认证验证的原因。

其中WebSecurityConfigurerAdapter是一个抽象类,在之后对springsecurity配置的扩展中需要继承这个类,如果对过滤器进行扩展需要继承SecurityFilterChain来进行扩展。

为什么会自动跳转到登录页面呢,主要是通过DefaultLoginPageGeneratingFilter实现的,流程如下

对Spring Boot项目进行集成测试 springboot集成springsecurity_spring_06

第三个问题,用户名和密码是怎么通过认证的,数据源验证是在哪里实现的?

1 查看SpringBootWebSecurityConfiguration#defaultSecurityFilterChain方法表单登录
2 处理登录为FormLoginConfigurer类中调用UsernamePasswordAuthenticationFilter这个类实例
3 查看类中UsernamePasswordAuthenticationFilter#attempAuthentication方法得知实际调用AuthenticationManager中authenticate方法
4 调用ProviderManager类中方法authenticate
5 调用了ProviderManager实现类中AbstractUserDetailsAuthenticationProvider类中方法
6 最终调用实现类DaoAuthenticationProvider类中方法比较,到这里就可以知道实现是基于InMemoryUserDetailsManager这个类,也就是内存的实现

对Spring Boot项目进行集成测试 springboot集成springsecurity_spring_07

 在SecurityProperties中可以看到@ConfigurationProperties注解,因此我们可以通过在yml中设置spring.security下的属性来自定义用户名和密码

自定义资源规则

一般在系统中访问资源会存在两种情况,一种是不需要权限能够直接访问的公共资源,另一种是需要认证和授权的受限资源,如下图所示

对Spring Boot项目进行集成测试 springboot集成springsecurity_用户名_08

 通过springsecurity如何进行设置呢?

springsecurity默认的配置是要验证所有的请求资源,我们需要去覆盖SpringBootWebSecurityConfiguration类中defaultSecurityFilterChain的默认设置,通过上面的总结可以知道,在什么情况下默认配置会生效

// 在spring容器中没有这两个bean时
	@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
	static class Beans {

	}

因此我们只需让此条件不满足既可以覆盖默认的资源验证规则。

package com.shaoch.security.springsecurityhelloworld.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @author shaoch
 * @date 2022/2/10 10:37 AM
 */
@Configuration
public class WebResourceSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // super.configure(http);
    http.authorizeRequests()
        .mvcMatchers("/login")
        .permitAll()               //放行login资源
        .mvcMatchers("/index")     //限制index资源
        .authenticated().and().formLogin(); // 开启请求权限的管理
  }
}