Spring Security核心架构¶
目录¶
架构概览¶
Spring Security架构图¶
HTTP Request
│
▼
┌─────────────────────────────────────────────────────┐
│ DelegatingFilterProxy (Servlet Filter) │
│ (由Spring容器管理) │
└─────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ FilterChainProxy (springSecurityFilterChain)│
│ Bean name: "springSecurityFilterChain" │
└─────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ SecurityFilterChain │
│ ┌───────────────────────────────────────────────┐ │
│ │ 1. SecurityContextPersistenceFilter │ │
│ │ 2. CsrfFilter │ │
│ │ 3. LogoutFilter │ │
│ │ 4. UsernamePasswordAuthenticationFilter │ │
│ │ 5. BasicAuthenticationFilter │ │
│ │ 6. RequestCacheAwareFilter │ │
│ │ 7. SecurityContextHolderAwareRequestFilter │ │
│ │ 8. AnonymousAuthenticationFilter │ │
│ │ 9. SessionManagementFilter │ │
│ │ 10. ExceptionTranslationFilter │ │
│ │ 11. FilterSecurityInterceptor (授权) │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────┬───────────────────────────────────┘
│
▼
Application Code
核心模块¶
┌──────────────────────────────────────────────────┐
│ Spring Security Core │
├──────────────────────────────────────────────────┤
│ Authentication (认证) │
│ • AuthenticationManager │
│ • AuthenticationProvider │
│ • UserDetailsService │
├──────────────────────────────────────────────────┤
│ Authorization (授权) │
│ • AccessDecisionManager │
│ • AccessDecisionVoter │
│ • SecurityMetadataSource │
├──────────────────────────────────────────────────┤
│ Security Context (安全上下文) │
│ • SecurityContext │
│ • SecurityContextHolder │
├──────────────────────────────────────────────────┤
│ Configuration (配置) │
│ • SecurityFilterChain │
│ • HttpSecurity │
│ • WebSecurity │
└──────────────────────────────────────────────────┘
过滤器链机制¶
过滤器链工作原理¶
Spring Security的核心是一系列过滤器(Filter),按照特定顺序组成过滤器链。
过滤器执行顺序:
/**
* Spring Security默认过滤器链顺序
*/
public enum FilterOrderEnum {
FIRST(-100),
CHANNEL_FILTER(100), // 强制HTTPS
SECURITY_CONTEXT_FILTER(200), // SecurityContextPersistenceFilter
CONCURRENT_SESSION_FILTER(300),
HEADERS_FILTER(400), // 添加安全响应头
CSRF_FILTER(500), // CSRF防护
LOGOUT_FILTER(600), // 登出处理
X509_FILTER(700),
PRE_AUTH_FILTER(800),
CAS_FILTER(900),
FORM_LOGIN_FILTER(1000), // UsernamePasswordAuthenticationFilter
OPENID_FILTER(1100),
LOGIN_PAGE_FILTER(1200),
DIGEST_AUTH_FILTER(1300),
BEARER_TOKEN_AUTH_FILTER(1400), // OAuth2/JWT
BASIC_AUTH_FILTER(1500), // BasicAuthenticationFilter
REQUEST_CACHE_FILTER(1600),
SERVLET_API_FILTER(1700),
JAAS_API_FILTER(1800),
REMEMBER_ME_FILTER(1900),
ANONYMOUS_FILTER(2000), // 匿名认证
SESSION_MANAGEMENT_FILTER(2100),
EXCEPTION_TRANSLATION_FILTER(2200), // 异常处理
FILTER_SECURITY_INTERCEPTOR(2300), // 授权决策
SWITCH_USER_FILTER(2400),
LAST(Integer.MAX_VALUE);
private final int order;
}
关键过滤器详解¶
1. SecurityContextPersistenceFilter¶
作用: 在请求开始时加载SecurityContext,请求结束时保存SecurityContext。
/**
* SecurityContextPersistenceFilter简化实现
*/
public class SecurityContextPersistenceFilter extends GenericFilterBean {
private SecurityContextRepository repo = new HttpSessionSecurityContextRepository();
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 1. 从Session加载SecurityContext
SecurityContext contextBeforeChain = repo.loadContext(
new HttpRequestResponseHolder(httpRequest, httpResponse)
);
try {
// 2. 设置到SecurityContextHolder
SecurityContextHolder.setContext(contextBeforeChain);
// 3. 继续过滤器链
chain.doFilter(httpRequest, httpResponse);
} finally {
// 4. 请求结束后,保存SecurityContext到Session
SecurityContext contextAfterChain = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChain, httpRequest, httpResponse);
}
}
}
2. UsernamePasswordAuthenticationFilter¶
作用: 处理表单登录请求。
/**
* UsernamePasswordAuthenticationFilter核心逻辑
*/
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response)
throws AuthenticationException {
// 1. 从请求中提取用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);
// 2. 创建未认证的Authentication对象
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
// 3. 设置详情(IP地址等)
setDetails(request, authRequest);
// 4. 委托给AuthenticationManager进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);
}
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);
}
}
3. BasicAuthenticationFilter¶
作用: 处理HTTP Basic认证。
/**
* BasicAuthenticationFilter核心逻辑
*/
public class BasicAuthenticationFilter extends OncePerRequestFilter {
private AuthenticationManager authenticationManager;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// 1. 检查Authorization头
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
try {
// 2. 解析Base64编码的凭证
String[] tokens = extractAndDecodeHeader(header);
String username = tokens[0];
String password = tokens[1];
// 3. 创建认证令牌
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
// 4. 认证
Authentication authResult = authenticationManager.authenticate(authRequest);
// 5. 设置到SecurityContext
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
} catch (AuthenticationException e) {
SecurityContextHolder.clearContext();
// 返回401响应
response.setHeader("WWW-Authenticate", "Basic realm=\"Realm\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
private String[] extractAndDecodeHeader(String header) throws IOException {
byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
byte[] decoded = Base64.getDecoder().decode(base64Token);
String token = new String(decoded, StandardCharsets.UTF_8);
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
}
}
4. ExceptionTranslationFilter¶
作用: 处理认证和授权异常。
/**
* ExceptionTranslationFilter核心逻辑
*/
public class ExceptionTranslationFilter extends GenericFilterBean {
private AuthenticationEntryPoint authenticationEntryPoint;
private AccessDeniedHandler accessDeniedHandler;
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (AuthenticationException e) {
// 处理认证异常(401)
handleAuthenticationException(request, response, e);
} catch (AccessDeniedException e) {
// 处理授权异常(403)
handleAccessDeniedException(request, response, e);
}
}
private void handleAuthenticationException(ServletRequest request,
ServletResponse response,
AuthenticationException e)
throws IOException, ServletException {
// 保存请求信息,认证后可以重定向回来
requestCache.saveRequest((HttpServletRequest) request, (HttpServletResponse) response);
// 触发认证入口点(通常是跳转到登录页)
authenticationEntryPoint.commence(
(HttpServletRequest) request,
(HttpServletResponse) response,
e
);
}
private void handleAccessDeniedException(ServletRequest request,
ServletResponse response,
AccessDeniedException e)
throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {
// 未认证用户访问受保护资源 → 重定向到登录页
authenticationEntryPoint.commence(
(HttpServletRequest) request,
(HttpServletResponse) response,
new InsufficientAuthenticationException("Full authentication is required")
);
} else {
// 已认证但权限不足 → 403
accessDeniedHandler.handle(
(HttpServletRequest) request,
(HttpServletResponse) response,
e
);
}
}
}
5. FilterSecurityInterceptor¶
作用: 执行授权决策,检查用户是否有权限访问资源。
/**
* FilterSecurityInterceptor核心逻辑
*/
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
// 1. 获取请求URL需要的权限配置
Collection<ConfigAttribute> attributes =
securityMetadataSource.getAttributes(fi);
// 2. 如果该URL不需要权限,直接放行
if (attributes == null || attributes.isEmpty()) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
// 3. 获取当前认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 4. 进行授权决策
try {
this.accessDecisionManager.decide(authentication, fi, attributes);
} catch (AccessDeniedException e) {
throw e; // 将被ExceptionTranslationFilter捕获
}
// 5. 授权通过,继续过滤器链
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
}
自定义过滤器¶
/**
* 自定义JWT认证过滤器
*/
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
try {
// 1. 从请求中提取JWT Token
String token = extractToken(request);
// 2. 验证Token
if (token != null && tokenProvider.validateToken(token)) {
// 3. 从Token获取用户名
String username = tokenProvider.getUsername(token);
// 4. 加载用户详情
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 5. 创建认证对象
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
// 6. 设置到SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Could not set user authentication in security context", e);
}
// 7. 继续过滤器链
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
/**
* 配置自定义过滤器
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
// 在UsernamePasswordAuthenticationFilter之前添加JWT过滤器
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
认证架构¶
认证流程¶
attemptAuthentication()
UsernamePasswordAuthenticationFilter
│
▼
┌───────────────────────────────────────┐
│ AuthenticationManager │
│ (通常是ProviderManager) │
└───────────┬───────────────────────────┘
│ authenticate()
▼
┌───────────────────────────────────────┐
│ AuthenticationProvider (循环尝试) │
│ ┌─────────────────────────────────┐ │
│ │ DaoAuthenticationProvider │ │
│ │ ├─ UserDetailsService │ │
│ │ └─ PasswordEncoder │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ LdapAuthenticationProvider │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ CustomAuthenticationProvider │ │
│ └─────────────────────────────────┘ │
└───────────┬───────────────────────────┘
│
▼ 返回Authentication (已认证)
SecurityContextHolder.setContext()
核心接口¶
1. Authentication¶
表示认证信息的接口。
/**
* Authentication接口
*/
public interface Authentication extends Principal, Serializable {
/**
* 权限集合
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 凭证(通常是密码)
* 认证后通常会被清除
*/
Object getCredentials();
/**
* 详细信息(如IP地址、Session ID)
*/
Object getDetails();
/**
* 身份信息(通常是UserDetails对象或用户名)
*/
Object getPrincipal();
/**
* 是否已认证
*/
boolean isAuthenticated();
/**
* 设置认证状态
*/
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
常用实现类:
// 1. UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(
username, // principal
password, // credentials
authorities // authorities (认证后设置)
);
// 2. AnonymousAuthenticationToken
AnonymousAuthenticationToken anonymous =
new AnonymousAuthenticationToken(
"key",
"anonymousUser",
Collections.singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))
);
// 3. RememberMeAuthenticationToken
RememberMeAuthenticationToken rememberMe =
new RememberMeAuthenticationToken(
"key",
userDetails,
authorities
);
2. AuthenticationManager¶
认证管理器接口,负责协调认证过程。
/**
* AuthenticationManager接口
*/
public interface AuthenticationManager {
/**
* 执行认证
* @param authentication 未认证的Authentication对象
* @return 已认证的Authentication对象
* @throws AuthenticationException 认证失败
*/
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
主要实现:ProviderManager
/**
* ProviderManager简化实现
*/
public class ProviderManager implements AuthenticationManager {
private List<AuthenticationProvider> providers;
private AuthenticationManager parent;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
// 遍历所有Provider
for (AuthenticationProvider provider : providers) {
// 检查Provider是否支持该类型的Authentication
if (!provider.supports(toTest)) {
continue;
}
try {
// 尝试认证
Authentication result = provider.authenticate(authentication);
if (result != null) {
// 认证成功
copyDetails(authentication, result);
return result;
}
} catch (AuthenticationException e) {
lastException = e;
}
}
// 如果有父AuthenticationManager,委托给父
if (parent != null) {
try {
return parent.authenticate(authentication);
} catch (AuthenticationException e) {
lastException = e;
}
}
// 所有Provider都无法认证
if (lastException != null) {
throw lastException;
}
throw new ProviderNotFoundException("No AuthenticationProvider found");
}
}
3. AuthenticationProvider¶
具体的认证提供者。
/**
* AuthenticationProvider接口
*/
public interface AuthenticationProvider {
/**
* 执行认证
*/
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
/**
* 是否支持该类型的Authentication
*/
boolean supports(Class<?> authentication);
}
DaoAuthenticationProvider(最常用):
/**
* DaoAuthenticationProvider简化实现
*/
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private PasswordEncoder passwordEncoder;
private UserDetailsService userDetailsService;
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 1. 检查密码是否为空
if (authentication.getCredentials() == null) {
throw new BadCredentialsException("Credentials cannot be null");
}
String presentedPassword = authentication.getCredentials().toString();
// 2. 验证密码
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
}
@Override
protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
try {
// 从UserDetailsService加载用户
UserDetails loadedUser = userDetailsService.loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null");
}
return loadedUser;
} catch (UsernameNotFoundException e) {
throw e;
} catch (Exception e) {
throw new InternalAuthenticationServiceException(e.getMessage(), e);
}
}
}
4. UserDetailsService¶
加载用户特定数据的核心接口。
/**
* UserDetailsService接口
*/
public interface UserDetailsService {
/**
* 根据用户名加载用户信息
*/
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
实现示例:
/**
* 自定义UserDetailsService实现
*/
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 从数据库查询用户
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
// 2. 转换为Spring Security的UserDetails
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(getAuthorities(user))
.accountExpired(user.isAccountExpired())
.accountLocked(user.isAccountLocked())
.credentialsExpired(user.isCredentialsExpired())
.disabled(!user.isEnabled())
.build();
}
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
Set<GrantedAuthority> authorities = new HashSet<>();
// 添加角色(注意:角色需要ROLE_前缀)
user.getRoles().forEach(role ->
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()))
);
// 添加权限
user.getPermissions().forEach(permission ->
authorities.add(new SimpleGrantedAuthority(permission.getName()))
);
return authorities;
}
}
5. UserDetails¶
核心用户信息接口。
/**
* UserDetails接口
*/
public interface UserDetails extends Serializable {
/**
* 权限集合
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 密码
*/
String getPassword();
/**
* 用户名
*/
String getUsername();
/**
* 账户是否未过期
*/
boolean isAccountNonExpired();
/**
* 账户是否未锁定
*/
boolean isAccountNonLocked();
/**
* 凭证是否未过期
*/
boolean isCredentialsNonExpired();
/**
* 账户是否可用
*/
boolean isEnabled();
}
自定义UserDetails实现:
/**
* 自定义UserDetails实现
*/
public class CustomUserDetails implements UserDetails {
private final Long id;
private final String username;
private final String password;
private final String email;
private final boolean enabled;
private final boolean accountNonExpired;
private final boolean credentialsNonExpired;
private final boolean accountNonLocked;
private final Collection<? extends GrantedAuthority> authorities;
public CustomUserDetails(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.password = user.getPassword();
this.email = user.getEmail();
this.enabled = user.isEnabled();
this.accountNonExpired = !user.isAccountExpired();
this.credentialsNonExpired = !user.isCredentialsExpired();
this.accountNonLocked = !user.isAccountLocked();
this.authorities = mapAuthorities(user.getRoles());
}
private Collection<? extends GrantedAuthority> mapAuthorities(Set<Role> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
.collect(Collectors.toList());
}
// Getter方法
public Long getId() {
return id;
}
public String getEmail() {
return email;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
6. PasswordEncoder¶
密码编码器接口。
/**
* PasswordEncoder接口
*/
public interface PasswordEncoder {
/**
* 编码原始密码
*/
String encode(CharSequence rawPassword);
/**
* 验证原始密码与编码密码是否匹配
*/
boolean matches(CharSequence rawPassword, String encodedPassword);
/**
* 是否需要重新编码(可选,用于升级加密算法)
*/
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
常用实现:
/**
* PasswordEncoder配置
*/
@Configuration
public class PasswordEncoderConfig {
/**
* BCrypt编码器(推荐)
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // strength: 12
}
/**
* 委托密码编码器(支持多种算法)
*/
@Bean
public PasswordEncoder delegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
}
/**
* 使用示例
*/
@Service
public class UserService {
@Autowired
private PasswordEncoder passwordEncoder;
public void createUser(String username, String rawPassword) {
// 加密密码
String encodedPassword = passwordEncoder.encode(rawPassword);
// 存储到数据库: {bcrypt}$2a$12$...
// 验证密码
boolean matches = passwordEncoder.matches(rawPassword, encodedPassword);
}
}
授权架构¶
授权流程¶
FilterSecurityInterceptor
│
▼
┌───────────────────────────────────────┐
│ SecurityMetadataSource │
│ 获取访问资源所需的权限配置 │
└───────────┬───────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ AccessDecisionManager │
│ 决定是否授权访问 │
└───────────┬───────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ AccessDecisionVoter (投票器) │
│ ┌─────────────────────────────────┐ │
│ │ RoleVoter │ │
│ │ AuthenticatedVoter │ │
│ │ WebExpressionVoter │ │
│ │ Custom Voter │ │
│ └─────────────────────────────────┘ │
└───────────┬───────────────────────────┘
│
▼
ACCESS_GRANTED / ACCESS_DENIED
核心接口¶
1. AccessDecisionManager¶
/**
* AccessDecisionManager接口
*/
public interface AccessDecisionManager {
/**
* 决定是否授予访问权限
* @param authentication 当前认证信息
* @param object 被保护的对象(FilterInvocation、MethodInvocation等)
* @param configAttributes 访问所需的权限配置
* @throws AccessDeniedException 如果访问被拒绝
*/
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
实现策略:
/**
* 1. AffirmativeBased - 一票通过(默认)
* 只要有一个Voter投赞成票,就授予访问权限
*/
public class AffirmativeBased extends AbstractAccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return; // 通过
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException("Access is denied");
}
// 检查是否允许全弃权
checkAllowIfAllAbstainDecisions();
}
}
/**
* 2. ConsensusBased - 少数服从多数
* 赞成票多于反对票则授予访问权限
*/
public class ConsensusBased extends AbstractAccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
int grant = 0;
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
grant++;
break;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
}
}
if (grant > deny) {
return; // 通过
}
if (deny > grant) {
throw new AccessDeniedException("Access is denied");
}
// 平票处理
if ((grant == deny) && (grant != 0)) {
if (this.allowIfEqualGrantedDeniedDecisions) {
return;
} else {
throw new AccessDeniedException("Access is denied");
}
}
checkAllowIfAllAbstainDecisions();
}
}
/**
* 3. UnanimousBased - 一票否决
* 所有Voter都投赞成票才授予访问权限
*/
public class UnanimousBased extends AbstractAccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
int grant = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
grant++;
break;
case AccessDecisionVoter.ACCESS_DENIED:
throw new AccessDeniedException("Access is denied");
}
}
if (grant > 0) {
return; // 全部赞成,通过
}
checkAllowIfAllAbstainDecisions();
}
}
2. AccessDecisionVoter¶
/**
* AccessDecisionVoter接口
*/
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1; // 赞成
int ACCESS_ABSTAIN = 0; // 弃权
int ACCESS_DENIED = -1; // 反对
/**
* 投票
*/
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
常用Voter:
/**
* RoleVoter - 角色投票器
*/
public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_";
@Override
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
if (authentication == null) {
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// 检查用户是否拥有所需角色
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
@Override
public boolean supports(ConfigAttribute attribute) {
return (attribute.getAttribute() != null)
&& attribute.getAttribute().startsWith(rolePrefix);
}
}
/**
* 自定义Voter示例 - IP地址投票器
*/
public class IpAddressVoter implements AccessDecisionVoter<FilterInvocation> {
private static final String IP_PREFIX = "IP_";
private static final String IP_LOCAL_HOST = "IP_LOCAL_HOST";
@Override
public int vote(Authentication authentication, FilterInvocation fi,
Collection<ConfigAttribute> attributes) {
int result = ACCESS_ABSTAIN;
for (ConfigAttribute attribute : attributes) {
if (!this.supports(attribute)) {
continue;
}
result = ACCESS_DENIED;
String clientIp = fi.getRequest().getRemoteAddr();
if (IP_LOCAL_HOST.equals(attribute.getAttribute())) {
if ("127.0.0.1".equals(clientIp) || "0:0:0:0:0:0:0:1".equals(clientIp)) {
return ACCESS_GRANTED;
}
}
// 可以添加更多IP检查逻辑
}
return result;
}
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute() != null
&& attribute.getAttribute().startsWith(IP_PREFIX);
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
Security Context¶
SecurityContext和SecurityContextHolder¶
/**
* SecurityContext接口
*/
public interface SecurityContext extends Serializable {
/**
* 获取当前认证信息
*/
Authentication getAuthentication();
/**
* 设置认证信息
*/
void setAuthentication(Authentication authentication);
}
SecurityContextHolder - 存储SecurityContext的工具类:
/**
* SecurityContextHolder使用示例
*/
public class SecurityContextExample {
/**
* 获取当前认证信息
*/
public static Authentication getCurrentAuthentication() {
SecurityContext context = SecurityContextHolder.getContext();
return context.getAuthentication();
}
/**
* 获取当前用户名
*/
public static String getCurrentUsername() {
Authentication authentication = getCurrentAuthentication();
if (authentication == null) {
return null;
}
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
} else {
return principal.toString();
}
}
/**
* 获取当前用户详情
*/
public static UserDetails getCurrentUserDetails() {
Authentication authentication = getCurrentAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
return (UserDetails) authentication.getPrincipal();
}
return null;
}
/**
* 检查是否已认证
*/
public static boolean isAuthenticated() {
Authentication authentication = getCurrentAuthentication();
return authentication != null
&& authentication.isAuthenticated()
&& !(authentication instanceof AnonymousAuthenticationToken);
}
/**
* 检查是否拥有角色
*/
public static boolean hasRole(String role) {
Authentication authentication = getCurrentAuthentication();
if (authentication == null) {
return false;
}
String roleWithPrefix = "ROLE_" + role;
return authentication.getAuthorities().stream()
.anyMatch(auth -> auth.getAuthority().equals(roleWithPrefix));
}
/**
* 手动设置认证信息(谨慎使用)
*/
public static void setAuthentication(Authentication authentication) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
/**
* 清除认证信息
*/
public static void clearAuthentication() {
SecurityContextHolder.clearContext();
}
}
SecurityContextHolder存储策略:
/**
* SecurityContextHolder有三种存储策略
*/
public class SecurityContextHolderExample {
/**
* 1. MODE_THREADLOCAL(默认)
* 使用ThreadLocal存储,每个线程独立
*/
public static void threadLocalMode() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL);
// 在当前线程中设置
SecurityContextHolder.getContext().setAuthentication(auth);
// 新线程中无法访问
new Thread(() -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// auth 为 null
}).start();
}
/**
* 2. MODE_INHERITABLETHREADLOCAL
* 使用InheritableThreadLocal,子线程可以继承父线程的SecurityContext
*/
public static void inheritableThreadLocalMode() {
SecurityContextHolder.setStrategyName(
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
SecurityContextHolder.getContext().setAuthentication(auth);
// 子线程可以访问
new Thread(() -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// auth 不为 null
}).start();
}
/**
* 3. MODE_GLOBAL
* 全局模式,JVM中所有线程共享同一个SecurityContext
* 仅适用于独立应用,不适用于服务器应用
*/
public static void globalMode() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL);
}
}
核心组件详解¶
1. SecurityConfigurer¶
/**
* SecurityConfigurer是配置Spring Security的核心接口
*/
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
/**
* 初始化
*/
void init(B builder) throws Exception;
/**
* 配置
*/
void configure(B builder) throws Exception;
}
2. HttpSecurity¶
/**
* HttpSecurity配置示例
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 授权配置
.authorizeHttpRequests(authz -> authz
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").hasAnyRole("USER", "ADMIN")
.requestMatchers(HttpMethod.POST, "/api/data").hasAuthority("DATA_WRITE")
.anyRequest().authenticated()
)
// 表单登录
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/perform_login")
.defaultSuccessUrl("/home", true)
.failureUrl("/login?error=true")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
)
// 登出
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.permitAll()
)
// 记住我
.rememberMe(remember -> remember
.key("uniqueAndSecret")
.tokenValiditySeconds(86400) // 24小时
.rememberMeParameter("remember-me")
)
// 会话管理
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.maxSessionsPreventsLogin(false)
.expiredUrl("/login?expired")
)
// CSRF
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)
// 异常处理
.exceptionHandling(exception -> exception
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.accessDeniedHandler((request, response, ex) -> {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("Access Denied");
})
);
return http.build();
}
}
自定义扩展¶
1. 自定义AuthenticationProvider¶
/**
* 自定义认证提供者
*/
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 自定义认证逻辑
User user = userService.findByUsername(username);
if (user == null) {
throw new BadCredentialsException("用户不存在");
}
if (!user.isEnabled()) {
throw new DisabledException("账户已禁用");
}
if (user.isLocked()) {
throw new LockedException("账户已锁定");
}
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("密码错误");
}
// 认证成功,创建Authentication对象
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
return new UsernamePasswordAuthenticationToken(
username,
password,
authorities
);
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
/**
* 配置自定义AuthenticationProvider
*/
@Configuration
public class AuthConfig {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder auth =
http.getSharedObject(AuthenticationManagerBuilder.class);
auth.authenticationProvider(customAuthenticationProvider);
return auth.build();
}
}
2. 自定义AccessDecisionVoter¶
/**
* 时间段访问控制Voter
*/
public class TimeBasedVoter implements AccessDecisionVoter<Object> {
private static final String TIME_PREFIX = "TIME_";
@Override
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
int result = ACCESS_ABSTAIN;
LocalTime now = LocalTime.now();
for (ConfigAttribute attribute : attributes) {
if (!supports(attribute)) {
continue;
}
result = ACCESS_DENIED;
String value = attribute.getAttribute();
if (value.equals("TIME_BUSINESS_HOURS")) {
// 工作时间:9:00 - 18:00
if (now.isAfter(LocalTime.of(9, 0)) &&
now.isBefore(LocalTime.of(18, 0))) {
return ACCESS_GRANTED;
}
}
}
return result;
}
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute() != null
&& attribute.getAttribute().startsWith(TIME_PREFIX);
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
总结¶
Spring Security核心架构包括:
- 过滤器链:请求的第一道防线,负责认证和授权的前置处理
- 认证架构:AuthenticationManager → AuthenticationProvider → UserDetailsService
- 授权架构:AccessDecisionManager → AccessDecisionVoter
- Security Context:存储和传递认证信息
- 扩展机制:提供丰富的扩展点,支持自定义认证和授权逻辑
理解这些核心组件的交互方式是掌握Spring Security的关键。
继续学习: - 上一章:Java认证框架对比 - 下一章:Spring Security实战配置