跳到主要内容Spring Security 接入 SpringBoot 与前后端分离实战 | 极客日志Javajava
Spring Security 接入 SpringBoot 与前后端分离实战
Spring Security 在 Spring Boot 中的应用。内容包括核心概念、依赖引入、内存与数据库用户认证、BCrypt 密码加密、JWT 无状态认证流程及过滤器实现。此外还涵盖前后端分离场景下的登录授权、自定义安全处理器及权限控制策略,提供完整的安全防护方案。
利刃27 浏览 Spring Security 超详细使用教程
Spring Security 是一个强大且可扩展的框架,用于保护 Java 应用程序,尤其是基于 Spring 的应用。它提供了身份验证(验证用户身份)、授权(管理用户权限)和防护机制(如 CSRF 保护和防止会话劫持)等功能。
Spring Security 允许开发者通过灵活的配置实现安全控制,确保应用程序的数据和资源安全。通过与其他 Spring 生态系统的无缝集成,Spring Security 成为构建安全应用的理想选择。
核心概念
- 身份验证 (Authentication): 验证用户的身份(例如,用户名/密码)。
- 授权 (Authorization): 确定用户是否有权限访问特定资源。
- 安全上下文 (Security Context): 存储已认证用户的详细信息,应用程序中可以访问。
1. 准备工作
1.1 引入依赖
当我们引入 security 依赖后,访问需要授权的 url 时,会重定向到 login 页面(security 自己创建的),login 页面需要账号密码,账号默认是 user, 密码是随机的字符串,在 spring 项目的输出信息中。
1.1.1 JWT 相关依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<>0.12.6
runtime
version
</version>
<scope>
</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
1.1.2 Spring Security 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.3.4</version>
</dependency>
一般我们会创建一个 SecurityConfig 类,来管理所有与 security 相关的配置。(我们讲的是 security 5.7 版本之后的配置方法,之前的方法跟现在不太一样)
@Configuration
@EnableWebSecurity
public class SecurityConfig {}
下面的都要写到 SecurityConfig 类中。
1.2 用户认证的配置
基于内存的用户认证
通过 createUser, manager 把用户配置的账号密码添加到 spring 的内存中,InMemoryUserDetailsManager 类中有一个 loadUserByUsername 的方法通过账号(username)从内存中获取我们配置的账号密码,之后调用其他方法来判断前端用户输入的密码和内存中的密码是否匹配。
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
User.withDefaultPasswordEncoder()
.username("user")
.password("user123")
.roles("USER")
.build()
);
return manager;
}
当我们点进 InMemoryUserDetailsManager 中 可以发现它实现了 UserDetailsManager 和 UserDetailsPasswordService 接口,其中 UserDetailsManager 接口继承的 UserDetailsService 接口中就有 loadUserByUsername 方法。
基于数据库的用户认证
上面讲到,spring security 是通过 loadUserByUsername 方法来获取 User 并用这个 User 来判断用户输入的密码是否正确。所以我们只需要继承 UserDetailsService 接口并重写 loadUserByUsername 方法即可。
下面的样例我用的 mybatis-plus 来查询数据库中的 user, 然后通过当前查询到的 user 返回特定的 UserDetails 对象。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
User user = userMapper.selectOne(queryWrapper);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new UserDetailsImpl(user);
}
}
UserDetailsImpl 是实现了 UserDetails 接口的类。UserDetails 接口是 Spring Security 身份验证机制的基础,通过实现该接口,开发者可以定义自己的用户模型,并提供用户相关的信息,以便进行身份验证和权限检查。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDetailsImpl implements UserDetails {
private User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
}
1.3 基本的配置
下面这个是 security 的默认配置。我们可以修改并把它加到 spring 容器中,完成我们特定的需求。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/blog/**", "/public/**", "/about").permitAll()
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.rememberMe(Customizer.withDefaults());
http.csrf(csrf -> csrf.disable());
return http.build();
}
关于上面放行路径写法的一些细节问题:
如果在 application.properties 中配置的有 server.servlet.context-path=/api 前缀的话,在放行路径中不需要写 /api。
如果 @RequestMapping(value = "/test/") 中写的是 /test/, 那么放行路径必须也写成 /test/, (/test)是不行的,反之亦然。
如果 @RequestMapping(value = "/test") 链接 /test 后面要加查询字符的话(/test?type=0),不要写成 @RequestMapping(value = "/test/")。
上面都是一些细节问题,但是遇到 bug 的时候不容易发现。
1.4 常用配置
下面这个是我常用的配置,配置了 jwt,加密的类,过滤器启用 jwt,而不是 session。(jwt 的类会在下面讲到)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(CsrfConfigurer::disable)
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests((authz) -> authz
.requestMatchers("/login/", "/getPicCheckCode").permitAll()
.requestMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
2. 加密
Spring Security 提供了多种加密和安全机制来保护用户的敏感信息,尤其是在用户身份验证和密码管理方面。(我们只讲默认的 BCryptPasswordEncoder)
1. 密码加密的重要性
在应用程序中,用户密码是最敏感的数据之一。为了防止密码泄露,即使数据库被攻击者获取,密码也应以加密形式存储。加密可以保护用户的隐私,并在一定程度上增加安全性。
2. Spring Security 的加密机制
2.1 PasswordEncoder 接口
Spring Security 提供了 PasswordEncoder 接口,用于定义密码的加密和验证方法。主要有以下几种实现:
- BCryptPasswordEncoder:基于 BCrypt 算法,具有适应性和强大的加密强度。它可以根据需求自动调整加密的复杂性。
- NoOpPasswordEncoder:不执行加密,适用于开发和测试环境,不建议在生产环境中使用。
- Pbkdf2PasswordEncoder、Argon2PasswordEncoder 等:这些都是基于不同算法的实现,具有不同的安全特性。
2.2 BCrypt 算法
BCrypt 是一种安全的密码哈希算法,具有以下特点:
- 盐值(Salt):每次加密时都会生成一个随机盐值,确保相同的密码在每次加密时生成不同的哈希值。
- 适应性:通过增加计算复杂性(如工作因子),可以提高密码的加密强度。
3. 使用 PasswordEncoder
3.1 配置 PasswordEncoder
可以在 Spring Security 的配置类中定义 PasswordEncoder bean,例如:
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3.2 加密密码
在注册用户或更新密码时,可以使用 PasswordEncoder 来加密密码:
@Autowired
private PasswordEncoder passwordEncoder;
public void registerUser(String username, String rawPassword) {
String encryptedPassword = passwordEncoder.encode(rawPassword);
}
3.3 验证密码
在用户登录时,可以使用 matches 方法验证输入的密码与存储的加密密码是否匹配:
public boolean login(String username, String rawPassword) {
String storedEncryptedPassword =
return passwordEncoder.matches(rawPassword, storedEncryptedPassword);
}
3. 前后端分离
1. 用户认证流程
1.1 用户登录
- 前端:
- 用户在登录界面输入用户名和密码。
- 前端将这些凭证以 JSON 格式发送到后端的登录 API(例如
POST /api/login)。
- 后端:
- Spring Security 接收请求,使用
AuthenticationManager 进行身份验证。
- 如果认证成功,后端生成一个 JWT(JSON Web Token)或其他认证令牌,并将其返回给前端。
2. 使用 JWT 进行用户认证
2.1 前端存储 JWT
- 前端收到 JWT 后,可以将其存储在
localStorage 或 sessionStorage 中,以便后续请求使用。
2.2 发送受保护请求
- 在发送需要认证的请求时,前端将 JWT 添加到请求头中:
fetch('/api/protected-endpoint', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
2.3 后端解析 JWT
- Spring Security 通过过滤器来解析和验证 JWT。可以自定义一个
OncePerRequestFilter 以拦截请求,提取 JWT,并验证其有效性。
3. 退出登录
由于 JWT 是无状态的,后端不需要记录会话状态。用户可以通过前端操作(例如,删除存储的 JWT)来'退出登录'。可以实现一个注销接口,用于前端执行相关逻辑。
4. 保护敏感信息
- 确保 HTTPS:在前后端通信中使用 HTTPS,确保传输中的数据安全。
- 令牌过期:设置 JWT 的有效期,过期后需要用户重新登录。
- 刷新令牌:可以实现刷新令牌的机制,以提高用户体验。
4. 实现 JWT
4.1 OncePerRequestFilter
- 作用:
OncePerRequestFilter 是 Spring Security 提供的一个抽象类,确保在每个请求中只执行一次特定的过滤逻辑。它是实现自定义过滤器的基础,通常用于对请求进行预处理或后处理。(实现 JWT 会用到这个接口)
- 功能:提供了一种机制,以确保过滤器的逻辑在每个请求中只执行一次,非常适合需要对每个请求进行处理的场景。通过继承该类,可以轻松实现自定义过滤器适合用于记录日志、身份验证、权限检查等场景。
- 注册过滤器:可以在 Spring Security 配置类中注册自定义的过滤器。
实现:继承 OncePerRequestFilter 类,并重写 doFilterInternal 方法。
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("Request URI: " + request.getRequestURI());
filterChain.doFilter(request, response);
}
}
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
4.2 生成 JWT
基于我们上面引入的三个 JWT 相关的依赖,写 JwtUtil 类。
- 功能:
JwtUtil 类提供了生成和解析 JWT 的功能,是实现基于 JWT 的认证和授权的重要工具。
- 应用场景:可以在用户登录后生成 JWT,并在后续请求中携带 JWT 用于用户身份验证和权限校验。通过设置合适的过期时间,可以提高安全性。
@Component
public class JwtUtil {
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;
public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) { ttlMillis = JwtUtil.JWT_TTL; }
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.id(uuid)
.subject(subject)
.issuer("sg")
.issuedAt(now)
.signWith(secretKey)
.expiration(expDate);
}
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(jwt).getBody();
}
}
1. 类注解与常量
@Component:将这个类标记为 Spring 组件,允许 Spring 管理该类的生命周期,便于依赖注入。
JWT_TTL:JWT 的有效期,设置为 14 天(单位为毫秒)。
JWT_KEY:用于签名的密钥字符串,经过 Base64 编码。它是生成和验证 JWT 的关键。
2. UUID 生成
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
- 作用:生成一个唯一的 UUID,去掉了其中的连字符。这通常用作 JWT 的 ID(
jti),确保每个 JWT 都是唯一的。
3. 创建 JWT
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
- 参数:
subject 是 JWT 的主题,通常是用户的身份信息。
- 功能:调用
getJwtBuilder 方法生成一个 JWT 构建器,并将其压缩为字符串返回。
4. JWT 构建器
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
}
- 功能:生成一个 JWT 的构建器,配置 JWT 的各个属性,包括:
setId(uuid):设置 JWT 的唯一标识。
setSubject(subject):设置 JWT 的主题。
setIssuer("sg"):设置 JWT 的发行者,通常是你的应用或服务名称。
setIssuedAt(now):设置 JWT 的签发时间。
signWith(signatureAlgorithm, secretKey):使用指定的算法和密钥进行签名。
setExpiration(expDate):设置 JWT 的过期时间。
5. 生成密钥
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
- 功能:将
JWT_KEY 进行 Base64 解码,生成一个 SecretKey 对象,用于 JWT 的签名和验证。使用 HMAC SHA-256 算法进行签名。
6. 解析 JWT
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(jwt).getBody();
}
- 参数:
jwt 是要解析的 JWT 字符串。
- 功能:
- 使用之前生成的密钥解析 JWT。
- 如果 JWT 有效,将返回 JWT 的声明(
Claims 对象),其中包含了 JWT 的主题、发行者、过期时间等信息。
- 该方法可能会抛出异常,表示 JWT 无效或解析失败。
4.3 应用 JWT
先加一个依赖 JetBrains Java Annotations, 下面的代码会用到其中的一个注解 @NotNull。
这个 JwtAuthenticationTokenFilter 类是一个实现了 OncePerRequestFilter 接口自定义的 Spring Security 过滤器。
- 功能:
JwtAuthenticationTokenFilter 通过 JWT 对用户进行身份验证,确保请求中携带的 JWT 是有效的,并根据 JWT 提供的用户信息设置认证状态。
- 应用场景:通常用于保护需要身份验证的 API 接口,确保只有经过认证的用户才能访问资源。该过滤器通常放置在 Spring Security 过滤链的合适位置,以确保在处理请求之前进行身份验证。
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserMapper userMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
token = token.substring(7);
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
throw new RuntimeException("用户名未登录");
}
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
1. 类注解与继承
@Component:标记该类为 Spring 组件,使其能够被 Spring 扫描并注册到应用上下文中。
- 继承
OncePerRequestFilter:确保每个请求只调用一次该过滤器,适合进行请求预处理。
2. 依赖注入
@Autowired
private UserMapper userMapper;
UserMapper:一个数据访问对象(DAO),用于与数据库交互,以便根据用户 ID 查询用户信息。通过 Spring 的依赖注入机制自动注入。
3. 获取 JWT
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
- 从请求头中获取
Authorization 字段的值,检查是否包含 JWT。
- 如果没有 JWT 或格式不正确,直接调用
filterChain.doFilter(request, response) 继续执行下一个过滤器,返回。
4. 解析 JWT
token = token.substring(7);
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
- 从 token 中去掉 "Bearer " 前缀,只保留 JWT 部分。
- 调用
JwtUtil.parseJWT(token) 解析 JWT,提取出用户 ID (userid)。如果解析失败,会抛出异常。
5. 查询用户信息
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
throw new RuntimeException("用户名未登录");
}
- 根据解析出的用户 ID 从数据库中查询用户信息。如果用户不存在,抛出异常。
6. 设置安全上下文
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
- 创建一个自定义的
UserDetailsImpl 对象,将查询到的用户信息封装。
- 创建一个
UsernamePasswordAuthenticationToken 对象,表示用户的认证信息,并将其设置到 Spring Security 的 SecurityContextHolder 中,以便后续请求能够访问到用户的认证信息。
4. 继续过滤链
filterChain.doFilter(request, response);
- 调用
filterChain.doFilter(request, response),继续执行后续的过滤器和最终的请求处理。
4.4 注册自定义的 JwtAuthenticationTokenFilter 过滤器
把我们的过滤器应用到 spring security 中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
5. 常用操作
5.1 配置 AuthenticationManager
将 AuthenticationManager 对象添加到 spring 容器中。(添加到我们创建的 SecurityConfig 配置类中)
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
5.2 验证当前用户
@Autowired
private AuthenticationManager authenticationManager;
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
User user = (User) authenticate.getPrincipal();
String jwt = JwtUtil.createJWT(user.getId().toString());
5.3 获取当前用户的认证信息
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
User user = (User) authenticate.getPrincipal();
UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
User user = userDetails.getUser();
5.4 对比 authenticationManager 和 SecurityContext 获取 Authentication
SecurityContextHolder.getContext().getAuthentication():
- 这个方法获取当前线程的安全上下文中的
Authentication 对象。
- 通常在用户已经被认证后使用,用于获取当前用户的信息(如身份、权限等)。
authenticationManager.authenticate(authenticationToken):
- 这个方法是用于实际执行身份验证的过程。
- 它接收一个凭证(如用户名和密码)并返回一个经过验证的
Authentication 对象,或者抛出异常。
- 在用户登录或验证时使用,属于身份验证的实际逻辑。
5.5 Authentication 接口
Authentication 接口包含以下重要方法:
getPrincipal():返回当前用户的身份,通常是用户的详细信息(如用户名)。
getCredentials():返回用户的凭证(如密码),但通常不直接使用此方法。
getAuthorities():返回用户的权限列表(角色或权限)。
6. 授权
Spring Security 的授权机制用于控制用户对应用程序资源的访问权限。授权通常是基于用户角色或权限的,以下是对 Spring Security 授权的详细讲解。
1. 授权的基本概念
- 授权(Authorization):指的是决定用户是否有权限访问特定资源的过程。在用户认证成功后,系统会根据预定义的规则来判断用户是否可以访问某些资源。
2. 授权的主要组成部分
2.1 权限与角色
- 权限(Permission):通常指对特定资源的操作能力,如'读取'、'写入'或'删除'。
- 角色(Role):一组权限的集合。例如,管理员角色可能具有所有权限,而普通用户角色可能只有读取权限。
2.2 GrantedAuthority 接口
- Spring Security 使用
GrantedAuthority 接口表示用户的权限。每个用户的权限可以通过实现该接口的对象来表示。
3. 授权方式
Spring Security 支持多种授权方式,主要包括:
3.1 基于角色的授权
- 定义角色:通常在用户注册或用户管理中定义。
- 配置角色授权:可以在 Spring Security 配置类中设置基于角色的访问控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated();
}
3.2 基于权限的授权
- 定义权限:与角色类似,定义更细粒度的权限。
- 配置权限授权:在配置类中设置基于权限的访问控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/edit/**").hasAuthority("EDIT_PRIVILEGE")
.anyRequest().authenticated();
}
4. 自定义授权逻辑
如果需要更复杂的授权逻辑,可以实现自定义的 AccessDecisionVoter 或 AccessDecisionManager。
4.1 AccessDecisionVoter
4.2 AccessDecisionManager
- 管理多个
AccessDecisionVoter 的调用,确定用户是否有权访问请求的资源。
7. 其他自定义行为
以下接口和类用于处理不同的安全事件,提供了自定义处理的能力:
1. AuthenticationSuccessHandler
- 作用:用于处理用户成功认证后的逻辑。
- 功能:可以自定义成功登录后的跳转行为,比如重定向到特定页面、返回 JSON 响应等。
- 实现:实现
AuthenticationSuccessHandler 接口,并重写 onAuthenticationSuccess 方法。
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
}
}
http.formLogin(form -> form.successHandler(new MyAuthenticationSuccessHandler()));
2. AuthenticationFailureHandler
- 作用:用于处理用户认证失败的逻辑。
- 功能:可以自定义失败登录后的行为,比如返回错误信息、重定向到登录页面并显示错误提示等。
- 实现:实现
AuthenticationFailureHandler 接口,并重写 onAuthenticationFailure 方法。
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
}
}
http.formLogin(form -> form.failureHandler(new MyAuthenticationFailureHandler()));
3. LogoutSuccessHandler
- 作用:用于处理用户成功登出的逻辑。
- 功能:可以自定义注销成功后的行为,比如重定向到登录页面、显示注销成功的消息等。
- 实现:实现
LogoutSuccessHandler 接口,并重写 onLogoutSuccess 方法。
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
}
}
http.logout(logout -> {
logout.logoutSuccessHandler(new MyLogoutSuccessHandler());
});
4. AuthenticationEntryPoint
- 作用:用于处理未认证用户访问受保护资源时的逻辑。
- 功能:可以自定义未认证用户的响应,比如返回 401 状态码、重定向到登录页面等。
- 实现:实现
AuthenticationEntryPoint 接口,并重写 commence 方法。
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
}
}
http.exceptionHandling(exception -> {
exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());
});
5. SessionInformationExpiredStrategy
- 作用:用于处理用户会话过期的逻辑。
- 功能:可以自定义会话超时后的响应,比如重定向到登录页面或返回 JSON 响应等。
- 实现:实现
SessionInformationExpiredStrategy 接口,并重写 onExpiredSession 方法。
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
}
}
http.sessionManagement(session -> {
session.maximumSessions(1).expiredSessionStrategy(new MySessionInformationExpiredStrategy());
});
6. AccessDeniedHandler
- 作用:用于处理用户访问被拒绝的情况。当用户尝试访问没有权限的资源时,Spring Security 会调用实现了
AccessDeniedHandler 接口的处理器。
- 功能:可以自定义拒绝访问后的响应行为,比如重定向到特定的错误页面、返回错误信息或 JSON 响应。
- 实现:实现
AccessDeniedHandler 接口,并重写 handle 方法。
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
}
}
// 在安全配置中注册自定义的 AccessDeniedHandler
http.exceptionHandling(exception -> {
exception.accessDeniedHandler(new MyAccessDeniedHandler());
});
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online