安全框架SpringSecurity入门
# 安全框架 Spring Security 入门
Spring Security 是一个强大的安全框架,广泛用于保护基于 Spring 的应用程序。它提供了全面的安全服务,包括认证、授权、攻击防护等。下面我将为你详细介绍 Spring Security 的主要知识点,帮助你更好地理解和使用这个框架。
《Spring Security 官网》 (opens new window)
# 概述
Spring Security 是 Spring 生态系统中的一个重要组成部分,用于为 Java 应用程序提供安全性。它支持多种认证和授权机制,并且可以轻松地与 Spring Boot 集成。
# 核心概念
# 认证(Authentication)
- 定义:验证用户的身份。
- 实现:
- 用户名/密码:最常见的认证方式。
- OAuth2/OpenID Connect:用于第三方认证。
- JWT (JSON Web Tokens):无状态的认证方式。
# 授权(Authorization)
- 定义:验证用户是否有权限访问特定资源。
- 实现:
- 角色:基于用户的角色进行授权。
- 权限:基于用户的权限进行授权。
- 表达式:使用 SpEL 表达式进行细粒度的授权控制。
# 攻击防护
- CSRF (Cross-Site Request Forgery):防止跨站请求伪造攻击。
- Session Fixation:防止会话固定攻击。
- 点击劫持 (Clickjacking):防止点击劫持攻击。
# 认证机制
# 内存认证
如上例所示,使用 InMemoryUserDetailsManager
进行内存认证。
# 数据库认证
使用 JdbcUserDetailsManager
从数据库中加载用户信息:
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
return new JdbcUserDetailsManager(dataSource);
}
2
3
4
# 自定义认证
实现 UserDetailsService
接口来自定义认证逻辑:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库或其他数据源加载用户信息
User user = getUserFromDatabase(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
AuthorityUtils.createAuthorityList(user.getRoles()));
}
private User getUserFromDatabase(String username) {
// 实现从数据库获取用户信息的逻辑
return new User(username, "password", "ROLE_USER");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 授权机制
# 角色授权
使用 hasRole
方法进行基于角色的授权:
http
.authorizeRequests(authorize -> authorize
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()
);
2
3
4
5
6
# 权限授权
使用 hasAuthority
方法进行基于权限的授权:
http
.authorizeRequests(authorize -> authorize
.antMatchers("/admin/**").hasAuthority("ADMIN_ACCESS")
.antMatchers("/user/**").hasAuthority("USER_ACCESS")
.anyRequest().authenticated()
);
2
3
4
5
6
# 表达式授权
使用 SpEL 表达式进行细粒度的授权控制:
http
.authorizeRequests(authorize -> authorize
.antMatchers("/admin/**").access("hasRole('ADMIN') and hasIpAddress('192.168.1.1')")
.antMatchers("/user/**").access("hasRole('USER') and authentication.name.startsWith('u')")
.anyRequest().authenticated()
);
2
3
4
5
6
# 攻击防护
# CSRF 防护
默认情况下,Spring Security 会启用 CSRF 防护。可以通过以下方式禁用或自定义:
http
.csrf(csrf -> csrf
.disable() // 禁用 CSRF 防护
);
2
3
4
# Session 固定防护
Spring Security 默认启用了 Session 固定防护,可以通过以下方式自定义:
http
.sessionManagement(session -> session
.sessionFixation().migrateSession() // 会话迁移
);
2
3
4
# 点击劫持防护
通过设置 HTTP 头来防止点击劫持攻击:
http
.headers(headers -> headers
.frameOptions().sameOrigin() // 允许同源 iframe
);
2
3
4
# 其他功能
# 安全日志
记录安全相关的日志信息,便于调试和监控:
logging.level.org.springframework.security=DEBUG
# 密码编码
使用 BCryptPasswordEncoder
对密码进行编码:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
2
3
4
# 配置示例
# 添加依赖
在 pom.xml
或 build.gradle
中添加 Spring Security 依赖:
Maven:
<!-- 实现对 Spring Security 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2
3
4
5
# application.yml 配置文件(可选)
在 application.yml
(opens new window) 中,添加 Spring Security 配置,如下:
spring:
# Spring Security 配置项,对应 SecurityProperties 配置类
security:
# 配置默认的 InMemoryUserDetailsManager 的用户账号与密码。
user:
name: user # 账号
password: user # 密码
roles: ADMIN # 拥有角色
2
3
4
5
6
7
8
参数解析:
在
spring.security
配置项,设置 Spring Security 的配置,对应 SecurityProperties (opens new window) 配置类。默认情况下,Spring Boot
UserDetailsServiceAutoConfiguration
自动化配置类,会创建一个内存级别的
InMemoryUserDetailsManager
Bean 对象,提供认证的用户信息。- 这里,我们添加了
spring.security.user
配置项,UserDetailsServiceAutoConfiguration
会基于配置的信息创建一个用户 User (opens new window) 在内存中。 - 如果,我们未添加
spring.security.user
配置项,UserDetailsServiceAutoConfiguration 会自动创建一个用户名为"user"
,密码为 UUID 随机的用户 User (opens new window) 在内存中。
- 这里,我们添加了
# 登录情况解析
- 未登录,会被 Spring Security 拦截到登录界面
- 如果没有自定义登录界面,默认会使用 DefaultLoginPageGeneratingFilter (opens new window) 类,生成默认登录界面
- 登录完成后,因为 Spring Security 会记录被拦截的访问地址,浏览器会自动跳转
# SecurityConfig
SecurityConfig
配置类,增加@EnableGlobalMethodSecurity
注解,开启对 Spring Security 注解的方法,进行权限验证。
创建一个配置类来设置安全规则:
- 创建 SecurityConfig (opens new window) 配置类,继承 WebSecurityConfigurerAdapter (opens new window) 抽象类,实现 Spring Security 在 Web 场景下的自定义配置。
- 可以通过重写
WebSecurityConfigurerAdapter
的方法,实现自定义的 Spring Security 的配置。 - 重写
#configure(AuthenticationManagerBuilder auth)
方法,实现 AuthenticationManager (opens new window) 认证管理器 - 重写
#configure(HttpSecurity http)
方法,主要配置 URL 的权限控制
# 实现 AuthenticationManager (opens new window) 认证管理器
代码示例
// 重写 configure(AuthenticationManagerBuilder auth) 方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.
// <X> 使用内存中的 InMemoryUserDetailsManager
inMemoryAuthentication()
// <Y> 不使用 PasswordEncoder 密码编码器
.passwordEncoder(NoOpPasswordEncoder.getInstance())
// <Z> 配置 admin 用户
.withUser("admin").password("admin").roles("ADMIN")
// <Z> 配置 normal 用户
.and()
.withUser("normal").password("normal").roles("NORMAL");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
代码解析
<X>
处,调用AuthenticationManagerBuilder#inMemoryAuthentication()
方法,使 用内存级别的InMemoryUserDetailsManager Bean
对象,提供认证的用户信息。- Spring 内置了两种
UserDetailsManager
实现:InMemoryUserDetailsManager
,在application.yml
中是一致的- ``JdbcUserDetailsManager`,基于 JDBC 的 JdbcUserDetailsManager
- 实际项目中,我们更多采用调用
AuthenticationManagerBuilder#userDetailsService(userDetailsService)
方法,使用自定义实现的UserDetailsService
实现类,更加灵活且自由的实现认证的用户信息的读取。
- Spring 内置了两种
<Y>
处,调用AbstractDaoAuthenticationConfigurer#passwordEncoder(passwordEncoder)
,设置PasswordEncoder
密码编码器。- 在这里,为了方便,我们使用
NoOpPasswordEncoder
。实际上,等于不使用 PasswordEncoder,不配置的话会报错。 - 生产环境下,推荐使用
BCryptPasswordEncoder
。更多关于 PasswordEncoder 的内容,推荐阅读《该如何设计你的PasswordEncoder??》) (opens new window) 文章。
- 在这里,为了方便,我们使用
<z>
处,配置了「admin/admin」和「normal/normal」两个用户,分别对应ADMIN
和NORMAL
角色。
# 配置 URL 的权限控制
代码示例
// 重写 configure(HttpSecurity http) 方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 配置请求地址的权限
.authorizeRequests()
.antMatchers("/test/demo").permitAll() // 所有用户可访问
.antMatchers("/test/admin").hasRole("ADMIN") // 需要 ADMIN 角色
.antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')") // 需要 NORMAL 角色。
// 任何请求,访问的用户都需要经过认证
.anyRequest().authenticated()
.and()
// 设置 Form 表单登陆
.formLogin()
// .loginPage("/login") // 登陆 URL 地址
.permitAll() // 所有用户可访问
.and()
// 配置退出相关
.logout()
// .logoutUrl("/logout") // 退出 URL 地址
.permitAll(); // 所有用户可访问
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
代码解析
<X>
处,调用HttpSecurity#authorizeRequests()
方法,开始配置 URL 的权限 控制。下面,是配置权限控制会使用到的方法:#(String...antPatterns)
方法,配置匹配的 URL 地址,基于 Ant 风格路径表达式,可传入多个。- 【常用】
#permitAll()
方法,所有用户可访问。 - 【常用】
#denyAll()
方法,所有用户不可访问。 【常用】#authenticated()
方法,登录用户可访问。 #anonymous()
方法,无需登录,即匿名用户可访问。#rememberMe()
方法,通过 remember me 登录的用户可访问。#fullyAuthenticated()
方法,非 remember me 登录的用户可访问。#hasIpAddress(String ipaddressExpression)
方法,来自指定 IP 表达式 的用户可访问。- 【常用】
#hasRole(String role)
方法,拥有指定角色的用户可访问。 - 【常用】
#hasAnyRole(String...roles)
方法,拥有指定任一角色的用户 可访问。 - 【常用】
#hasAuthority(String authority)
方法,拥有指定权限(authority
)的用户可访问。 - 【常用】
#hasAuthority(String...authorities)
方法,拥有指定任一权限(authority
)的用户可访问。 - 【最牛】
#access(String attribute)
方法,当Spring EL 表达式
的执行结果为true
时,可以访问。
<Y>
处,调用HttpSecurity#formLogin()
方法,设置 Form 表单登录。- 如果想要自定义登录页面,可以通过
#loginPage(String loginPage)
方法,来进行设置。 - 不配置时使用默认的登录界面。
- 如果想要自定义登录页面,可以通过
<Z>
处,调用HttpSecurity#logout()
方法,配置退出相关。- 如果想要自定义退出页面,可以通过
#logoutUrl(String logoutUrl)
方法,来进行设置。 - 不配置时使用默认的退出界面。
- 如果想要自定义退出页面,可以通过
# 完整示例
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启对 Spring Security 注解的方法,进行权限验证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 配置请求地址的权限
.authorizeRequests()
.antMatchers("/test/demo").permitAll() // 所有用户可访问
.antMatchers("/test/admin").hasRole("ADMIN") // 需要 ADMIN 角色
.antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')") // 需要 NORMAL 角色。
// 任何请求,访问的用户都需要经过认证
.anyRequest().authenticated()
.and()
// 设置 Form 表单登陆
.formLogin()
// .loginPage("/login") // 登陆 URL 地址
.permitAll() // 所有用户可访问
.and()
// 配置退出相关
.logout()
// .logoutUrl("/logout") // 退出 URL 地址
.permitAll(); // 所有用户可访问
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.
// 使用内存中的 InMemoryUserDetailsManager
inMemoryAuthentication()
// 不使用 PasswordEncoder 密码编码器
.passwordEncoder(NoOpPasswordEncoder.getInstance())
// 配置 admin 用户
.withUser("admin").password("admin").roles("ADMIN")
// 配置 normal 用户
.and().withUser("normal").password("normal").roles("NORMAL");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 总结
Spring Security 是一个强大且灵活的安全框架,适用于各种类型的 Java 应用程序。通过上述介绍,你可以了解其核心概念、基本配置、认证和授权机制以及常见的攻击防护措施。希望这些内容能帮助你更好地掌握 Spring Security,并在实际项目中应用它。