注:本文基于
Spring Boot 3.2.1
以及Spring Security 6.2.1
Spring Security 6 的常用注解包括以下几种,通过这些注解可以更加方便的控制资源权限。
@Secured
:方法执行前检查,直接判断有没有对应的角色@PreAuthorize
:方法执行前检查,根据SpEL表达式执行结果判断是否授权@PostAuthorize
:方法执行后检查,根据SpEL表达式执行结果判断是否授权
要使用以前注解必须增加配置,开启校验功能
// 用于启用方法级别的安全支持
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@Secured
方法执行前检查,直接判断有没有对应的角色
示例代码:
@Secured({ "ROLE_USER" })
public void create(Contact contact);
@Secured({ "ROLE_USER", "ROLE_ADMIN" })
public void update(Contact contact);
@Secured({ "ROLE_ADMIN" })
public void delete(Contact contact);
@PreAuthorize
方法执行前检查,根据SpEL表达式执行结果判断是否授权
示例代码:
// 有角色
@PreAuthorize("hasRole('ROLE_ADMIN')")
// 有任一角色
@PreAuthorize("hasAnyRole({'ROLE_USER','ROLE_ADMIN'})")
// 有任一权限
@PreAuthorize("hasAnyAuthority({'user:search','user:edit'})")
其他用法
@PreAuthorize
参数是SpEL表达式,所以还可以有其他用法
1、方法参数值判断,@PreAuthorize("#age>10")
@GetMapping("/age")
@PreAuthorize("#age>10")
public String age(Integer age) {
return "Hello age "+ age;
}
2、调用bean的方法判断
1)创建Bean,判断是否有权限
@Component("au")
public class AuthUtils {
public boolean check(String role) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.noneMatch(role::equals)) {
throw new AccessDeniedException("User does not have the required permission");
}
return true;
}
}
2)在方法上使用,@PreAuthorize("@au.check('ROLE_USER')")
@GetMapping("/user_au")
@PreAuthorize("@au.check('ROLE_USER')")
public String user_au() {
return "Hello user_au";
}
和@PreAuthorize
配合使用的方法定义在 org.springframework.security.access.expression.SecurityExpressionOperations
中
完整代码示例
1、HttpSecurity 配置
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class BasicSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
// 放行登录页面
.requestMatchers("/login").permitAll()
// 拦截其他所有请求
.anyRequest().authenticated()
)
// 退出时,让session失效
.logout(logout -> logout.invalidateHttpSession(true))
// 配置登录页面 和 登录成功后页面
.formLogin(form -> form.loginPage("/login").permitAll()
.loginProcessingUrl("/login").defaultSuccessUrl("/index"));
http.exceptionHandling(e->e.accessDeniedPage("/noAuth"));
// 开启csrf 保护
http.csrf(Customizer.withDefaults());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user1 = User.withUsername("admin").password("{noop}123456").roles("ADMIN").build();
UserDetails user2 = User.withUsername("user").password("{noop}123456").authorities("user:edit","ROLE_USER").build();
return new InMemoryUserDetailsManager(user1,user2);
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
2、测试controller类,DemoController
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@GetMapping("/admin")
@Secured({"ROLE_ADMIN"})
public String admin() {
return "Hello admin";
}
@GetMapping("/user")
@Secured({"ROLE_ADMIN","ROLE_USER"})
public String user() {
return "Hello user";
}
@GetMapping("/admin2")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String admin2() {
return "Hello admin";
}
@GetMapping("/user2")
@PreAuthorize("hasAnyRole({'ROLE_USER','ROLE_ADMIN'})")
public String user2() {
return "Hello user";
}
@GetMapping("/user_perm")
@PreAuthorize("hasAnyAuthority({'user:search','user:edit'})")
public String user_perm() {
return "Hello user";
}
@GetMapping("/user_au")
@PreAuthorize("@au.check('ROLE_USER')")
public String user_au() {
return "Hello user_au";
}
@GetMapping("/age")
@PreAuthorize("#age>10")
public String age(Integer age) {
return "Hello age "+ age;
}
@RequestMapping("/all")
public String all() {
return "Hello all";
}
}
如果需要自定义认证和授权逻辑,可以实现 UserDetailsService 和 AuthenticationProvider 接口,并在配置类中注入这些自定义的 Bean。
这就是 Spring Security 的注解验证流程。通过合理地使用注解和配置类,可以轻松地实现基于角色的访问控制、方法级别的授权等安全功能。同时,Spring Security 还提供了丰富的扩展点,允许开发者根据具体需求进行定制。
Spring Security 常见扩展点
Spring Security 提供了许多扩展点,允许开发者根据具体需求进行定制和扩展。以下是一些常见的扩展点:
-
过滤器链(Filter Chain):
Spring Security 本质是一个过滤器链,开发者可以通过自定义过滤器来扩展其功能。例如,可以添加自定义的认证过滤器、授权过滤器或会话管理过滤器等。 -
认证机制(Authentication Mechanism):
可以自定义认证机制,包括用户信息的加载、密码的编码和校验等。通过实现AuthenticationProvider
接口,可以定义自己的认证逻辑。 -
授权决策(Authorization Decision):
开发者可以通过实现AccessDecisionManager
接口来自定义授权决策逻辑。这允许你根据业务逻辑来定制权限判断。 -
用户服务(User Service):
通过实现UserDetailsService
接口,可以自定义用户信息的加载方式。例如,你可以从数据库、LDAP 服务器或其他数据源中获取用户信息。 -
安全事件监听(Security Event Listeners):
Spring Security 提供了安全事件监听器,允许你监听认证、授权等事件,并根据事件执行相应的操作。 -
方法安全(Method Security):
除了使用注解外,你还可以通过配置GlobalMethodSecurityConfiguration
来全局启用方法级别的安全支持,并自定义方法安全的配置。 -
安全表达式语言(Security Expression Language):
Spring Security 使用了强大的安全表达式语言(SpEL),允许你在注解和配置中使用表达式来定义复杂的权限和角色要求。 -
会话管理(Session Management):
可以自定义会话的创建、存储、失效等逻辑,以满足特定的业务需求。 -
HTTP 安全配置(HTTP Security Configuration):
通过重写configure(HttpSecurity http)
方法,你可以自定义 HTTP 安全配置,包括 CSRF 保护、点击劫持保护、缓存控制等。 -
异常处理(Exception Handling):
可以自定义认证和授权失败时的异常处理逻辑,例如返回自定义的错误页面或错误信息。
这些扩展点使得 Spring Security 非常灵活,能够适应各种不同的安全需求。通过合理利用这些扩展点,开发者可以构建出强大而安全的应用程序。
参考
- https://docs.spring.io/spring-security/reference/index.html