Spring Security是基于Spring 和Spring MVC的声明式安全性框架,主要是为了给Java应用提供全面的安全解决方案,能够通过简洁的配置,实现了Web请求级别和方法调用级别的身份验证与访问授权。它本身已充分使用了Spring容器,Spring AOP及spring过滤器等技术
Spring Security的核心特性
1.多层次的保护机制:
Web请求级别:通过过滤器(Filter Chain)拦截HTTP请求,确保只有经过认证和授权的用户才能访问受保护的资源
方法调用级别:通过Spring AOP在方法执行前进行权限检查,确保只有具备相应权限的用户才能执行特定操作
2.灵活的认证和授权
支持多种认证方式
1.用户名密码验证
2.LDAP认证
3.OAuth2认证
提供基于角色,权限表达式等多种授权策略,满足不同场景的安全需求
Spring Security的开发需要导入Spring-security-wed和spring -security-config模块,Maven的导入方式如下:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${springsecurity.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${springsecurity.version}</version>
</dependency>
导入后,举一个简单实例了解Spring Security框架的大体使用,下面的实例使用Spring Security框架对index前缀的请求进行拦截和验证,开发包括两步:配置Spring Security的配置文件(这里的文件名为spring-security.xml)和配置web.xml文件
1.配置spring-security.xml
新建spring-security.xml文件,配置内容如下:
<!--默认的命名空间-->
<beans: beans xmshttp://www.springframework.org/schema/security
xmins: beans=http://www.springframework.org/schema/beans
xmins: xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<beans:bean id="passwordEncoder" <-- 密码加密器--> class="org.springframework.security.crypto.password. NoOpPassword Encoder" />
<http auto-config="true"><!--开启认证功能-->
<!--请求与权限-->
<intercept-ur1 pattern="/index*" access="hasRole ('ROLE_USER') " />
</http>
<authentication-manager><!--认证管理器—->
<authentication-provider><!--认证器-->
<user-service><!--内存用户与角色-->
<user name="wukong" password="1" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
具体实现:ROLE_USER角色的登录用户才可以访问以index为前缀的地址。如果当前用户没有登录,则会转到安全框架内置的登陆页面
2.web.xml的配置
将spring-security.xml添加到contextConfigLocation的上下文参数中,再配置名称为springSecurityFilterChain的过滤器及映射,配置片段如下:
<context-param>
<!--核心与安全配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.ml, classpath:spring-security.
xml</param-value>
</context-param>
<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>
以上配置完成后,启动服务器测试后访问系统主页,会自动弹出Spring Security框架自带的登录页面
配置认证管理器
通过这下面这两个标签配置认证管理器,用于管理用户的认证过程,确保只有经过身份验证的用户才能访问受保护的资源
- </authentication-manager>:用于配置认证管理器的标签,负责处理用户的认证请求
当用户尝试登陆时,认证管理器会接收认证请求,并交由配置的认证提供者<authentication-provider>进行实际的身份验证
- <authentication-provider>(子标签)配置具体的认证器,认证器负责验证用户身份
通过配置用户信息服务,认证提供者可以加载用户信息(用户名,密码,权限等),认证成功后,给用户分配权限(authorities),用于后续的授权检查
用户信息服务:
内存用户存储:直接在配置文件中定义用户信息
<authentication-manager>
<authentication-provider>
<user-service>
<!--配置内存中的用户-->
<user name="wukong" password="1" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
数据库用户存储:从数据库中读取用户信息
<authentication-manager>
<authentication-provider>
<beans :bean id="userDetailService"
class="com. osxm. daport. security UserDetailServiceImp1" />
<security:authentication-manager><!--认证管理器-->
‹security:authentication-provider user-service-ref="userDetails Service" />
</security: authentication-manager>
</authentication-provider>
</ authentication-manager>
除了配置内存用户和数据库查询用户外,也可以继承UserDetailsService接口实现自定义的用户服务,实现loadUserByUsername()方法通过用户名获取UserDetails类型的用户账号对象
java">//自定义用户账号服务
public class UserDetailServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername (String userName)
throws UsernameNotFoundException {
List<GrantedAuthority> authsList = new ArrayList<GrantedAuthority> () ;
authsList.add (new SimpleGrantedAuthority ("USER") ) ;
User userdetail = new User ("Wukong", "1", authsList) ;
return userdetail;
}
}
Spring Security密码加密
在存储用户密码时,直接存储明文密码非常不安全,容易被攻击者窃取,造成较严重的后果,Spring Security提供了密码加密机制,能够确保密码在存储时是加密的,即使数据库泄露,攻击者也无法直接获取用户的明文密码
Spring Security默认使用BCrypt加密算法,基于哈希的加密算法,这里演示使用BCryptPasswordEncoder加密器类进行加密,通过统一加密工厂PasswordEncoder-Factories获取加密器进行加密
如:这里对字符串“1”加密
java">//默认使用BCrypt算法
org.springframework.security.crypto.password.PasswordEncoder encoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
String encryptPassword = encoder.encode("1");
/*encoder.encode("1"):调用PasswordEncoder的encode方法,
对明文密码“1”进行加密,返回加密后的字符串*/
以上对字符串“1”加密后的结果为
java">{bcrypt} $2a$10$1k6FdrtCay.RwPBZ6YncPunoiaPMW1F5aMigenOKm. WJC8YA. 3vYG
此外,Spring还提供了BCrypt工具类,可以更方便的使用BCrypt算法进行加密,调用方法如下:
java">import org.mindrot.jbcrypt.BCrypt;
public class BCryptExample {
public static void main(String[] args) {
// 1. 原始密码
String originPassword = "123456";
// 2. 生成盐值并加密密码
String encryptPassword = BCrypt.hashpw(originPassword, BCrypt.gensalt());
System.out.println("加密后的密码: " + encryptPassword);
// 3. 验证密码
boolean isMatch = BCrypt.checkpw(originPassword, encryptPassword);
System.out.println("密码是否匹配: " + isMatch);
}
}
结果:
java">加密后的密码: $2a$10$5vR5v5zX5zX5zX5zX5zX5u
密码是否匹配: true
BCrypt加密的原理:
盐值(Salt):盐值是一个随机字符串,用于在加密过程中与密码结合,确保即使两个用户的密码相同,加密后的结果也不同
加密过程:BCrypt算法会将盐值与密码结合,经过多次哈希运算,生成了一个固定长度的加密字符串
另外除了不加密和bcrypt加密算法之外,Spring Security支持的加密算法及实现类还有以下
加密算法 | 实现类 | |
1 | bcrypt | BCryptPasswordEncoder |
2 | MD4 | Md4PasswordEncoder |
3 | MD5 | MessageDigestPasswordEncoder ("MD5") |
4 | noop | NoOpPasswordEncoder |
5 | pbkdf2 | Pbkdf2PasswordEncoder |
6 | scrypt | SCryptPasswordEncoder |
7 | SHA-1 | MessageDigestPasswordEncoder ("SHA-1") |
8 | SHA-256 | new MessageDigestPasswordEncoder("SHA-256") |
Spring Security方法层级授权
通过Spring Security方法层级授权我们可以对代码中具体方法的调用进行权限校验,确保只有满足条件的用户或角色才能执行该方法
方法层级控制能够直接针对类或方法定义访问规则,如:
只有管理员(ROLE_ADMIN)才能调用删除用户的方法
普通用户(ROLE_USER)只能查询自己的数据
Spring Security使用Spring AOP实现方法层级的权限控制,使用方式有三种:
- 使用<intercept-methods>标签对单个Bean的方法访问控制进行设定;
- 在类和方法中使用Spring安全注解或JSP-250注解进行细粒度控制
- 使用<protect-pointcut>配置权限控制方法的切点;
1.单个Bean方法保护的配置
在Bean的配置中使用<intercept-methods>标签对当前Bean中的方法进行权限控制,示例对UserServiceImpl中以get开头的方法进行权限拦截,要求调用者必须具有ROLE_USER角色:
<!--定义了一个userService的Bean,对应实现类UserServiceImpl-->
<beans:bean id="userService" class="com.osxm.daport.service.impl.UserServiceImpl">
<!--<intercpet-methods>表示对该Bean方法进行权限拦截-->
<intercept-methods> <!-- 拦截需要权限验证的方法 -->
<protect access="ROLE_USER" method="get*" />
</intercept-methods>
</beans:bean>
<protect>:定义权限规则
- method=”get*“:匹配所有以get开头的方法(如getUser,getProfile)
- access=”ROLE_USER“:只有具有ROLE_USER角色的用户才能调用这些方法
当用户尝试调用UserServiceImpl中任意以get开头的方法时,Spring Security会检查当前用户是否拥有ROLE_USER角色,如果未授权,将抛出AccessDeniedException
2.方法的安全注解
在类和方法中使用安全注解可以简化XML的配置,同时可以对方法进行更灵活的控制,Spring提供和支持的注解有三类:
JSR-250注解:
这是Java的标准安全注解,主要是@RelosAllowed
@RelosAllowed
该注解属于JSR-250注解,基于角色的简单权限控制,属于JavaEE标准的一部分,使用前需要导入了Java标准库Javax.annotation-api,使用Maven导入如下:
<dependency>
<groupId>javax. annotation</groupId>
<artifactId>javax.annotation-api</artifactid>
<version >1.3.2</version>
</dependency>
@RelosAllowed注解可以使用在类和方法中,在注解后加上授权的角色,标注在类中时,该类所有方法的执行都需要对应的角色,如果同时使用在类和方法中,则方法中的注解会覆盖类中的注解,演示使用该注解对List方法进行权限控制,代码示例如下:
java">@RolesAllowed ("ROLE_USER")//允许 ROLE_USER角色的用户访问
public List<User> list (User user) {
return userDao. list (user);
//调用userDao.list(user)查询用户列表,并返回结果
}
除了@RelosAllowed之外,JSR-250安全注解还有@PermitAll和@DenyAll
- @PermitAll允许任何角色访问,也就是不控制权限
- @DenyAll则是拒绝所有角色访问
- @PermitAll和@DenyAll同样可以使用在类和方法中,如果一个方法同时使用了这两个注解,则先定义的有效;如果同时使用在类中则是相反,后定义的生效
@Secured:
Spring提供的一般性安全注解
@Secured安全注解
@Secured注解在xml中先启动配置,用于启动@Secured注解支持
@Secured注解需要设置<global-method-security>标签的secured-annotations属性值为enabled才能开启
XML:
<global-method-security secured-annotations="enabled"/>
在类和方法中使用:
java">@Secured ("ROLE_USER")//只有拥有ROLE_USER角色的用户才能调用此方法
public List<User> list (User user) {
return userDao. list (user);
}
Spring表达式驱动注解(更强大的控制):
支持基于SpEL(Spring Expression Language)的复杂权限逻辑
- @PreAuthorize:在方法调用前检查权限
- @PostAuthorize::在方法调用后检查权限,如果没有权限则无法返回结果
- @PreFilter:在方法调用前对集合类型参数进行过滤
- @PostFilter:在方法执行后,对集合类型的返回结果进行过滤
该类型注解先在xml中启动配置
XML:
<global-method-security pre-post-annotations="enabled" />
@PreAuthorize和@PostAuthorize用于方法调用前后进行权限检查,@PreFilter和@PostFilter则是对集合类型参数及返回值进行过滤
@PreAuthorize使用场景之一就是登录账号只能查询自己的账号信息
java">//认证账号只能查询自己的信息
@PreAuthorize ("principal. username. equals (#username) ")
@Override
public User get (String username){
return userdao. get (username) ;
@PostAuthorize使用在方法执行完成后的权限检查,该注解不控制该方法是否执行,只控制方法执行后的结果返回,如果表达式的结果为false则抛出AccessDeniedException异常
在@PreFilter和@PostFilter注解的表达式中,使用内置表达式变量filterObject对集合进行过滤
@PreFilter注解:
在方法执行前,对输入参数(比如集合或数组)进行过滤,仅保留满足条件的元素
代码演示:只取偶数的ID,排除奇数的ID
java">@PreFilter("filterObject % 2 == 0") //只取偶数的ID,排除奇数的ID
public void delete (List< Integer> ids) {
for (Integer id : ids) {
userDao.delete(id);
}
}
filterObject是Spring Security提供的特殊变量,表示集合中的当前元素
@PostFilter注解:
在方法执行后,对返回值(比如集合或数组)进行过滤,仅保留满足条件的元素
java">//对返回结果过滤,只返回部门ID是1的用户
@PostFilter ("filterObject .deptId==1")
public List<User> list (User user) {
return userDao.list (user);
}
java">//授权对象
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("USER");
//授权列表
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority> () ;
authorities.add (grantedAuthority) ;//授权列表添加
//用户账号信息
UserDetails userDetails = new User("wukong","1",authorities);
UserDetailsService userDetailsService = new InMemoryUserDetailsManager
(userDetails) :
//DAO 类型认证器
DaoAuthenticationProvider provider = new DaoAuthenticationProvider ():
//设置认证器的用户信息服务
provider. setUserDetailsService (userDetailsService) ;
//设置认证器的加密器
provider. setPasswordEncoder (NoOpPasswordEncoder. getInstance ( )) ;
List<AuthenticationProvider > providers = new ArrayList<Authentication
Provider> () ;
providers. add (provider) ;
AuthenticationManager authenticationManager = new ProviderManager
(providers) ;
Spring Security的安全访问控制机制工作原理:
Spring Security的安全访问控制机制主要依赖Filter链实现,其核心原理是通过拦截HTTP请求,逐步执行一系列安全相关的过滤器(Filter),最终决定是否允许请求访问目标资源
当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain的Servlet过滤器,类型为org.springframework.security.web.FilterChainProxy,它实现了Javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过滤器链结构图:
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中的SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理
Spring Security功能的实现主要是由一系列过滤器链相互配合完成
下面介绍过滤器链中主要的几个过滤器及其作用:
1.SecurityContextPersistenceFilter:这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器)
会在请求开始时从配置好的SecurityContextRepository(如 HTTP Session)中加载SecurityContext ,并将其设置到SecurityContextHolder中
在请求结束时,将SecurityContextHolder中的SecurityContext保存回SecurityContextRepository,并清除SecurityContextHolder中的内容
举例以下场景
用户登陆后,访问需要认证的页面(如/profile),系统从Session中恢复用户的认证信息
用户注销后,系统清除Session中的认证信息,确保用户无法继续访问受保护资源
2.UsernamePasswordAuthenticationFilter:专门处理基于表单的登录请求
能够从请求中提取用户名和密码,然后调用AuthenticationManager进行认证,根据认证结果,调用AuthenticationSuccessHandler 或AuthenticationFailureHandler 处理成功或失败。
举例以下场景
用户通过表单提交用户名和密码进行登录,系统验证用户名和密码是否正确,并根据结果跳转到成功页面或失败页面
3.FilterSecurityInterceptor:负责对Web资源进行访问控制
从 SecurityContextHolder
中获取当前用户的 Authentication
对象,调用 AccessDecisionManager
进行授权决策,根据决策结果,决定是否允许访问目标资源
举例以下场景
- 管理员访问
/admin/dashboard
页面,系统检查用户是否具有ADMIN
角色,如果有则允许访问,否则返回 403 错误 - 普通用户尝试访问
/admin/dashboard
页面,系统返回 403 错误
4.ExceptionTranslationFilter :
能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。
举例以下场景
-
未登录用户访问受保护资源:
当用户未登录时访问受保护资源(如/profile
),系统捕获AuthenticationException
,并重定向到登录页。 -
未授权用户访问受保护资源:
当用户登录后访问未授权资源(如/admin/dashboard
),系统捕获AccessDeniedException
,并返回 403 错误。
Spring Security的执行流程如下:
1.用户提交用户名,密码被SecurityFilterChain中的UsernamePasswordAuthenticationFilter过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类
2,然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证
3.认证成功后,AuthenticationManager身份管理器返回一个被填充满了的信息(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例
4.SecurityContextHolder安全上下文容器将第3步填充了的信息Authentication,通过SecurityContextHolder.getContext().setAuthentication(...)方法,设置到其中
5.可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个List<AuthenticationProvider>列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成的