一、什么是 Spring Security?

Spring Security 是基于 Spring 框架的安全解决方案。它提供了全面的安全性功能,支持在 Web 请求级别和方法调用级别进行身份认证(Authentication)与授权(Authorization)。

在 Spring Framework 的基础上,Spring Security 充分利用了依赖注入(DI)和面向切面编程(AOP)功能,为应用系统提供声明式的安全访问控制。这不仅减少了为企业安全控制编写大量重复代码的工作,也是一个轻量级的安全框架,能够很好地与 Spring MVC 集成。

二、Spring Security 的核心功能有哪些?

  1. 认证(Authentication):验证某个用户是否为系统中的合法主体,即判断用户能否访问该系统。
  2. 授权(Authorization):验证某个用户是否有权限执行某个特定操作。

三、Spring Security 基于哪些技术实现?

核心实现技术:Filter、Servlet、AOP

众所周知,想要对 Web 资源进行保护,最好的办法莫过于 Filter;要想对方法调用进行保护,最好的办法莫过于 AOP。Spring Security 在进行用户认证以及授予权限时,通过各种各样的拦截器来控制权限的访问,从而实现安全。

Spring Security 功能的实现主要是由一系列过滤器链(Filter Chain)相互配合完成。

1686234202010181227256081610316777.png

学习重点建议:

  1. springSecurityFilterChain 中各个过滤器是如何创建的,只需了解即可,无需太过关注。
  2. 重点记忆 UsernamePasswordAuthenticationFilterExceptionTranslationFilterFilterSecurityInterceptor 这三个过滤器的作用及源码分析。
  3. 重点记忆认证过程中 AuthenticationAuthenticationManagerProviderManagerAuthenticationProviderUserDetailsServiceUserDetails 这些类的作用及源码分析。
  4. 重点记忆授权过程中 FilterInvocationSecurityMetadataSourceAccessDecisionManager 的作用。

四、框架的核心组件

  • SecurityContextHolder:提供对 SecurityContext 的访问。
  • SecurityContext:持有 Authentication 对象和其他可能需要的信息。
  • AuthenticationManager:认证管理器,其中可以包含多个 AuthenticationProvider
  • ProviderManagerAuthenticationManager 接口的实现类。
  • AuthenticationProvider:主要用来进行认证操作的类,调用其中的 authenticate() 方法去执行认证。
  • Authentication:Spring Security 方式的认证主体。
  • GrantedAuthority:对认证主体的应用层面授权,含当前用户的权限信息,通常使用角色表示。
  • UserDetails:构建 Authentication 对象必须的信息,可以自定义,可能需要访问数据库得到。
  • UserDetailsService:通过 username 构建 UserDetails 对象,通过 loadUserByUsername 根据 userName 获取 UserDetails 对象。

五、Spring Security 的工作流程

1686234202010181228439881242133496.png

六、认证流程

1686234202010181229490111317919020.png

七、授权流程

16862342020101812302175992577888.png

八、Spring Security 入门示例

1. 构建 Maven 项目,引入 Spring Security 相关依赖

168623420201018123101129475975440.png

pom.xml 配置文件主要部分:

<properties>  
  <spring.version>4.2.0.RELEASE</spring.version>  
</properties>  
<dependencies>  
  <dependency>  
    <groupId>junit</groupId>  
    <artifactId>junit</artifactId>  
    <version>4.11</version>  
    <scope>test</scope>  
  </dependency>  
  <dependency>  
    <groupId>org.springframework</groupId>  
    <artifactId>spring-beans</artifactId>  
    <version>${spring.version}</version>  
  </dependency>  
  <dependency>  
    <groupId>org.springframework</groupId>  
    <artifactId>spring-context</artifactId>  
    <version>${spring.version}</version>  
  </dependency>  
  <dependency>  
    <groupId>org.springframework</groupId>  
    <artifactId>spring-webmvc</artifactId>  
    <version>${spring.version}</version>  
  </dependency>  
  <dependency>  
    <groupId>org.springframework.security</groupId>  
    <artifactId>spring-security-web</artifactId>  
    <version>${spring.version}</version>  
  </dependency>  
  <dependency>  
    <groupId>org.springframework.security</groupId>  
    <artifactId>spring-security-config</artifactId>  
    <version>${spring.version}</version>  
  </dependency>  
  <dependency>  
    <groupId>jstl</groupId>  
    <artifactId>jstl</artifactId>  
    <version>1.2</version>  
  </dependency>  
</dependencies>

2. 配置 web.xml

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring-security.xml</param-value>
</context-param>

<servlet>
  <servlet-name>springmvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-mvc.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>springmvc</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

3. 书写 AdminController.java 类

@Controller
public class AdminController {
    @RequestMapping(value = {"/","/welcome**"}, method = RequestMethod.GET)
    public ModelAndView welcome(){
        ModelAndView welcome = new ModelAndView();
        welcome.addObject("title","Welcome");
        welcome.addObject("message","This is a Security Page");
        welcome.setViewName("hello");
        return welcome;
    }
    @RequestMapping(value = {"/","/admin**"}, method = RequestMethod.GET)
    public ModelAndView admin(){
        ModelAndView welcome = new ModelAndView();
        welcome.addObject("title","admin");
        welcome.addObject("message","This is Admin page");
        welcome.setViewName("admin");
        return welcome;
    }
}

4. 配置 spring-mvc.xml

<context:component-scan base-package="web.*"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

5. 配置 spring-security.xml

<security:http auto-config="true">
   <security:intercept-url pattern="/admin**" access="hasAnyRole('ROLE_USER')"/>
</security:http>
<security:authentication-manager>
    <security:authentication-provider>
        <security:user-service>
            <security:user name="admin" authorities="ROLE_USER" password="123456"/>
        </security:user-service>
    </security:authentication-provider>
</security:authentication-manager>
注意:<intercept-url pattern="/admin**" access="hasRole('ROLE_USER')"/> 这句配置中,Spring Security 4.0 以后版本都使用 hasRole('ROLE_USER') 取代原来的 ROLE_USER 写法。

6. 准备页面

admin.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%@page session="true" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
   <h2>title:${title}</h2>
   <h2>message:${message}</h2>
   <c:if test="${pageContext.request.userPrincipal.name != null }">
       <h2>welcome you ,${pageContext.request.userPrincipal.name}</h2>
       <a href="<c:url value='/j_spring_security_logout'/>">Logout</a>
   </c:if>
</body>
</html>

hello.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
     <h2>title:${title }</h2>
     <h2>message:${message }</h2>
</body>
</html>

7. 启动 Tomcat,访问项目

访问地址:http://localhost:8080/springsecurity/admin

初次访问提示登录
初次访问 http://localhost:8080/springsecurity/admin 会提示登录,这是 Spring Security 为我们提供的默认登录页面。

1686234202010181232413521176915289.png

用户名或者密码错误,登录失败

1686234202010181233345451556544101.png

登录成功,跳转欢迎页面

16862342020101812335640044803764.png

参考资料

说明

版本时效性说明:本文示例基于 Spring Framework 4.2.0 及早期 Spring Security 版本(XML 配置方式)。当前主流开发多采用 Spring Boot 搭配 Spring Security 5.x/6.x,推荐使用 Java Config 方式进行配置,且依赖版本管理更为独立。文中代码逻辑供学习原理参考,实际新项目请查阅官方最新文档。