Spring Security 入门:概念、认证与授权配置
本文介绍 Spring Security 框架的核心功能、架构设计及配置方式。涵盖快速入门案例,包括环境准备与依赖整合。详细讲解认证操作,如自定义登录页面、关闭 CSRF 拦截、数据库认证、密码加密及记住我功能。同时阐述授权机制,包括注解使用(JSR250、Spring 表达式、Secured)和标签使用,帮助开发者构建安全的 Java Web 应用。

本文介绍 Spring Security 框架的核心功能、架构设计及配置方式。涵盖快速入门案例,包括环境准备与依赖整合。详细讲解认证操作,如自定义登录页面、关闭 CSRF 拦截、数据库认证、密码加密及记住我功能。同时阐述授权机制,包括注解使用(JSR250、Spring 表达式、Secured)和标签使用,帮助开发者构建安全的 Java Web 应用。

Spring Security 是一个功能强大且高度可配置的安全框架,用于为 Java 应用程序提供身份验证和授权功能。以下是对它的详细介绍:
@Configuration 和 @EnableWebSecurity 注解来创建一个 Spring Security 配置类,然后在类中定义各种安全配置方法。@PreAuthorize、@PostAuthorize 等注解来定义访问权限,确保只有授权的用户才能访问相应的方法。Spring Security 是 Spring 采用 AOP 思想,基于 servlet 过滤器实现的安全框架。它提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于 Spring 的应用程序的事实上的标准。
Spring Security 是一个框架,致力于为 Java 应用程序提供身份验证和授权。像所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足定制需求的能力。
特征
我们准备一个 SpringMVC+Spring+jsp 的 Web 环境,然后在这个基础上整合 SpringSecurity。
首先创建 Web 项目
添加相关的依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
添加相关的配置文件
Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.bobo.service"></context:component-scan>
</beans>
SpringMVC 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.bobo.controller"></context:component-scan>
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
log4j.properties 文件
log4j.rootCategory=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>Archetype Created Web Application</display-name>
<!-- 初始化 spring 容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- post 乱码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 前端控制器 -->
<servlet>
<servlet-name>dispatcherServletb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation 不是必须的,如果不配置 contextConfigLocation,springmvc 的配置文件默认在:WEB-INF/servlet 的 name+"-servlet.xml" -->
<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>dispatcherServletb</servlet-name>
<!-- 拦截所有请求 jsp 除外 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
添加 Tomcat 的插件 启动测试
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8082</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
添加相关的依赖
spring-security-core.jar 核心包,任何 SpringSecurity 的功能都需要此包
spring-security-web.jar:web 工程必备,包含过滤器和相关的 web 安全的基础结构代码
spring-security-config.jar:用于 xml 文件解析处理
spring-security-tablibs.jar:动态标签库
<!-- 添加 SpringSecurity 的相关依赖 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
web.xml 文件中配置 SpringSecurity
<!-- 配置过滤器链 springSecurityFilterChain 名称固定 -->
<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>
添加 SpringSecurity 的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- SpringSecurity 配置文件 -->
<!--
auto-config:表示自动加载 SpringSecurity 的配置文件
use-expressions:表示使用 Spring 的 EL 表达式
-->
<security:http auto-config="true" use-expressions="true">
<!--
拦截资源
pattern="/**" 拦截所有的资源
access="hasAnyRole('ROLE_USER')" 表示只有 ROLE_USER 这个角色可以访问资源
-->
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"></security:intercept-url>
</security:http>
<!-- 认证用户信息 -->
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<!-- 设置一个账号 zhangsan 密码 123 {noop} 表示不加密 具有的角色是 ROLE_USER-->
<security:user name="zhangsan" authorities="ROLE_USER" password="{noop}123"></security:user>
<security:user name="lisi" authorities="ROLE_USER" password="{noop}123456"></security:user>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
将 SpringSecurity 的配置文件引入到 Spring 中
启动测试访问
如何使用我们自己写的登录页面呢?
<%-- Created by IntelliJ IDEA. --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>登录页面</h1>
<form action="/login" method="post">
账号:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
修改相关的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- SpringSecurity 配置文件 -->
<!--
auto-config:表示自动加载 SpringSecurity 的配置文件
use-expressions:表示使用 Spring 的 EL 表达式
-->
<security:http auto-config="true" use-expressions="true">
<!-- 匿名访问登录页面-->
<security:intercept-url pattern="/login.jsp" access="permitAll()"/>
<!--
拦截资源
pattern="/**" 拦截所有的资源
access="hasAnyRole('ROLE_USER')" 表示只有 ROLE_USER 这个角色可以访问资源
-->
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')" />
<!--
配置认证的信息
-->
<security:form-login login-page="/login.jsp"
login-processing-url="/login"
default-target-url="/home.jsp"
authentication-failure-url="/error.jsp"
/>
<!-- 注销 -->
<security:logout logout-url="/logout"
logout-success-url="/login.jsp" />
</security:http>
<!-- 认证用户信息 -->
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<!-- 设置一个账号 zhangsan 密码 123 {noop} 表示不加密 具有的角色是 ROLE_USER-->
<security:user name="zhangsan" authorities="ROLE_USER" password="{noop}123"></security:user>
<security:user name="lisi" authorities="ROLE_USER" password="{noop}123456"></security:user>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
访问 home.jsp 页面后会自动跳转到自定义的登录页面,说明这个需求是实现了
但是当我们提交了请求后页面出现了如下的错误
为什么系统默认的登录页面提交没有 CRSF 拦截的问题呢
我自定义的认证页面没有这个信息怎么办呢?两种方式:
关闭 CSRF 拦截
登录成功~
使用 CSRF 防护
在页面中添加对应 taglib
我们访问登录页面
登录成功
前面的案例我们的账号信息是直接写在配置文件中的,这显然是不太好的,我们来介绍小如何实现和数据库中的信息进行认证
添加相关的依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
添加配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/logistics?characterEncoding=utf-8&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.bobo.service"></context:component-scan>
<!-- SpringSecurity 的配置文件 -->
<import resource="classpath:spring-security.xml" />
<context:property-placeholder location="classpath:db.properties" />
<bean>
<property name="url" value="${jdbc.url}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean>
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>
<bean>
<property name="basePackage" value="com.bobo.mapper" />
</bean>
</beans>
需要完成认证的 service 中继承 UserDetailsService 父接口
实现类中实现验证方法
package com.bobo.service.impl;
import com.bobo.mapper.UserMapper;
import com.bobo.pojo.User;
import com.bobo.pojo.UserExample;
import com.bobo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper mapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 根据账号查询用户信息
UserExample example = new UserExample();
example.createCriteria().andUserNameEqualTo(s);
List<User> users = mapper.selectByExample(example);
if(users != null && users.size() > 0){
User user = users.get(0);
if(user != null){
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// 设置登录账号的角色
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
UserDetails userDetails = new org.springframework.security.core.userdetails.User(
user.getUserName(),"{noop}"+user.getPassword(),authorities
);
return userDetails;
}
}
return null;
}
}
最后修改配置文件关联我们自定义的 service 即可
在 SpringSecurity 中推荐我们是使用的加密算法是 BCryptPasswordEncoder
首先生成秘闻
修改配置文件
去掉 {noop}
用户的状态包括 是否可用,账号过期,凭证过期,账号锁定等等。
我们可以在用户的表结构中添加相关的字段来维护这种关系
在表单页面添加一个 记住我 的按钮.
在 SpringSecurity 中默认是关闭 RememberMe 功能的,我们需要放开
这样就配置好了。
记住我的功能会方便大家的使用,但是安全性却是令人担忧的,因为 Cookie 信息存储在客户端很容易被盗取,这时我们可以将这些数据持久化到数据库中。
CREATE TABLE `persistent_logins` (
`username` VARCHAR (64) NOT NULL,
`series` VARCHAR (64) NOT NULL,
`token` VARCHAR (64) NOT NULL,
`last_used` TIMESTAMP NOT NULL,
PRIMARY KEY (`series`)
) ENGINE = INNODB DEFAULT CHARSET = utf8
注意设置了过期时间,到期后并不是删除表结构中的数据,而是客户端不会在携带相关信息了,同时删除掉数据库中的数据 记住我 也会失效
开启注解的支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<context:component-scan base-package="com.bobo.controller"></context:component-scan>
<mvc:annotation-driven></mvc:annotation-driven>
<!--
开启权限控制注解支持
jsr250-annotations="enabled" 表示支持 jsr250-api 的注解支持,需要 jsr250-api 的 jar 包
pre-post-annotations="enabled" 表示支持 Spring 的表达式注解
secured-annotations="enabled" 这个才是 SpringSecurity 提供的注解
-->
<security:global-method-security
jsr250-annotations="enabled"
pre-post-annotations="enabled"
secured-annotations="enabled"
/>
</beans>
jsr250 的使用
添加依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
控制器中通过注解设置
package com.bobo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.security.RolesAllowed;
@Controller
@RequestMapping("/user")
public class UserController {
@RolesAllowed(value = {"ROLE_ADMIN"})
@RequestMapping("/query")
public String query(){
System.out.println("用户查询....");
return "/home.jsp";
}
@RolesAllowed(value = {"ROLE_USER"})
@RequestMapping("/save")
public String save(){
System.out.println("用户添加....");
return "/home.jsp";
}
@RequestMapping("/update")
public String update(){
System.out.println("用户更新....");
return "/home.jsp";
}
}
Spring 表达式的使用
package com.bobo.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.security.RolesAllowed;
@Controller
@RequestMapping("/order")
public class OrderController {
@PreAuthorize(value = "hasAnyRole('ROLE_USER')")
@RequestMapping("/query")
public String query(){
System.out.println("用户查询....");
return "/home.jsp";
}
@PreAuthorize(value = "hasAnyRole('ROLE_ADMIN')")
@RequestMapping("/save")
public String save(){
System.out.println("用户添加....");
return "/home.jsp";
}
@RequestMapping("/update")
public String update(){
System.out.println("用户更新....");
return "/home.jsp";
}
}
SpringSecurity 提供的注解
package com.bobo.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/role")
public class RoleController {
@Secured("ROLE_USER")
@RequestMapping("/query")
public String query(){
System.out.println("用户查询....");
return "/home.jsp";
}
@Secured("ROLE_ADMIN")
@RequestMapping("/save")
public String save(){
System.out.println("用户添加....");
return "/home.jsp";
}
@RequestMapping("/update")
public String update(){
System.out.println("用户更新....");
return "/home.jsp";
}
}
异常处理
新增一个错误页面,然后在 SpringSecurity 的配置文件中配置即可
当然你也可以使用前面介绍的 SpringMVC 中的各种异常处理器处理
前面介绍的注解的权限管理可以控制用户是否具有这个操作的权限,但是当用户具有了这个权限后进入到具体的操作页面,这时我们还有进行更细粒度的控制,这时注解的方式就不太适用了,这时我们可以通过标签来处里
添加 SpringSecurity 的标签库
<%-- Created by IntelliJ IDEA. --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>欢迎光临...</h1>
<security:authentication property="principal.username" />
<security:authorize access="hasAnyRole('ROLE_USER')" >
<a href="#">用户查询</a><br>
</security:authorize>
<security:authorize access="hasAnyRole('ROLE_ADMIN')" >
<a href="#">用户添加</a><br>
</security:authorize>
<security:authorize access="hasAnyRole('ROLE_USER')" >
<a href="#">用户更新</a><br>
</security:authorize>
<security:authorize access="hasAnyRole('ROLE_ADMIN')" >
<a href="#">用户删除</a><br>
</security:authorize>
</body>
</html>
页面效果

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online