目录
Spring Security概述
Spring Security核心组件
Spring Security是Spring生态系统中的安全框架,提供完整的认证和授权解决方案。
核心概念:
- Authentication(认证):验证用户身份
- Authorization(授权):控制用户访问权限
- Principal(主体):代表用户的身份
- Authority(权限):具体的能力和权限
Maven依赖配置
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.7.5</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.7.5</version>
</dependency>
<!-- Spring Security JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</<|tool▁sep|>version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Spring Security OAuth2 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>5.7.5</version>
</dependency>
</dependencies>
基础安全配置
Security配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 添加JWT过滤器
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
用户详情服务
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
return CustomUserPrincipal.create(user);
}
}
public class CustomUserPrincipal implements UserDetails {
private Long id;
private String username;
private String email;
private String password;
private Collection<? extends GrantedAuthority> authorities;
private boolean enabled;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
public CustomUserPrincipal(Long id, String username, String email,
String password, Collection<? extends GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.email = email;
this.password = password;
this.authorities = authorities;
this.enabled = true;
this.accountNonExpired = true;
this.accountNonLocked = true;
this.credentialsNonExpired = true;
}
public static CustomUserPrincipal create(User user) {
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName().toUpperCase()))
.collect(Collectors.toList());
return new CustomUserPrincipal(
user.getId(),
user.getUsername(),
user.getEmail(),
user.getPassword(),
authorities
);
}
// getters and setters
}
用户认证机制
表单认证
登录控制器
@RestController
@RequestMapping("/api/auth")
@CrossOrigin(origins = "*", maxAge = 3600)
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/signin")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateJwtToken(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream()
.map(item -> item.getAuthority())
.collect(Collectors.toList());
return ResponseEntity.ok(new JwtResponse(jwt,
userDetails.getId(),
userDetails.getUsername(),
userDetails.getEmail(),
roles));
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new MessageResponse("用户名或密码错误"));
}
}
@PostMapping("/signup")
public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
return ResponseEntity.badRequest()
.body(new MessageResponse("用户名已存在"));
}
if (userRepository.existsByEmail(signUpRequest.getEmail())) {
return ResponseEntity.badRequest()
.body(new MessageResponse("邮箱已被使用"));
}
// 创建新用户
User user = new User(
signUpRequest.getUsername(),
signUpRequest.getEmail(),
passwordEncoder.encode(signUpRequest.getPassword()));
Set<Role> roles = new HashSet<>();
Role userRole = roleRepository.findByName(ERole.ROLE_USER)
.orElseThrow(() -> new RuntimeException("角色不存在"));
roles.add(userRole);
user.setRoles(roles);
userRepository.save(player);
return ResponseEntity.ok(new MessageResponse("用户注册成功"));
}
}
JWT令牌处理
JWT工具类
@Component
public class JwtUtils {
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
private static final String jwtSecret = "mySecretKey";
private static final int jwtExpirationMs = 86400000; // 24小时
public String generateJwtToken(Authentication authentication) {
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
return Jwts.builder()
.setSubject((userPrincipal.getUsername()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
JWT认证过滤器
public class AuthTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails,
null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7, headerAuth.length());
}
return null;
}
}
用户授权控制
基于角色的访问控制(RBAC)
角色和权限实体
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Enumerated(EnumType.STRING)
@Column(length = 20)
private ERole name;
@ManyToMany(mappedBy = "roles")
private Set<User> users;
// 构造函数、getters、setters
}
@Entity
@Table(name = "permissions")
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name;
private String description;
@ManyToMany(mappedBy = "permissions")
private Set<Role> roles;
// 构造函数、getters、setters
}
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 20)
private String username;
@Column(unique = true, nullable = false, length = 50)
private String email;
@Column(nullable = false)
private String password;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_permissions",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(nameUnique = "permission_id"))
private Set<Permission> permissions = new HashSet<>();
// 构造函数、getters、setters
}
权限验证服务
权限验证服务
@Service
public class PermissionService {
@Autowired
private UserRepository userRepository;
@Autowired
private PermissionRepository permissionRepository;
public boolean hasPermission(Long userId, String permissionName) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
// 检查用户直接权限
boolean hasDirectPermission = user.getPermissions().stream()
.anyMatch(permission -> permission.getName().equals(permissionName));
// 检查用户通过角色获得的权限
boolean hasRolePermission = user.getRoles().stream()
.flatMap(role -> role.getPermissions().stream())
.anyMatch(permission -> permission.getName().equals(permissionName));
return hasDirectPermission || hasRolePermission;
}
public boolean hasRole(Long userId, String roleName) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
return user.getRoles().stream()
.anyMatch(role -> role.getName().name().equals(roleName));
}
public List<String> getUserPermissions(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
Set<String> permissions = new HashSet<>();
// 直接权限
user.getPermissions().forEach(permission ->
permissions.add(permission.getName()));
// 角色权限
user.getRoles().forEach(role ->
role.getPermissions().forEach(permission ->
permissions.add(permission.getName())));
return new ArrayList<>(permissions);
}
}
安全过滤器链
自定义安全过滤器
IP白名单过滤器
@Component
public class IPWhitelistFilter extends GenericFilterBean {
private static final Logger logger = LoggerFactory.getLogger(IPWhitelistFilter.class);
private Set<String> whitelistedIPs = Set.of(
"127.0.0.1",
"192.168.1.0/24", // 本地网络
"10.0.0.0/8" // 内网
);
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String clientIP = getClientIP(httpRequest);
if (!isIPWhitelisted(clientIP)) {
logger.warn("访问被拒绝,IP地址: {}", clientIP);
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("{\"error\":\"Access denied\"}");
return;
}
chain.doFilter(request, response);
}
private boolean isIPWhitelisted(String ip) {
// 简单的IP检查逻辑,实际项目中可以使用更复杂的IP范围检查
for (String whitelistedIP : whitelistedIPs) {
if (ip.equals(whitelistedIP)) {
return true;
}
}
return false;
}
private String getClientIP(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
请求频率限制过滤器
@Component
public class RateLimitFilter extends GenericFilterBean {
private final Map<String, AtomicInteger> requests = new ConcurrentHashMap<>();
private final Map<String, Long> timestamps = new ConcurrentHashMap<>();
private static final int MAX_REQUESTS = 100; // 每分钟最大请求数
private static final long TIME_WINDOW = 60000; // 时间窗口:1分钟
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = httpResponse) response;
String clientIP = getClientIP(httpRequest);
String uri = httpRequest.getRequestURI();
String key = clientIP + ":" + uri;
long currentTime = System.currentTimeMillis();
// 清理过期的请求记录
cleanupExpiredRequests(currentTime);
// 检查请求频率
AtomicInteger requestCount = requests.computeIfAbsent(key, k -> new AtomicInteger(0));
Long timestamp = timestamps.computeIfAbsent(key, k -> currentTime);
// 如果在时间窗口内且超过限制
if (currentTime - timestamp < TIME_WINDOW) {
if (requestCount.incrementAndGet() > MAX_REQUESTS) {
logger.warn("请求频率超限: {} - {}", clientIP, uri);
httpResponse.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
httpResponse.getWriter().write("{\"error\":\"Too many requests\"}");
return;
}
} else {
// 重置计数器
requestCount.set(1);
timestamps.put(key, currentTime);
}
chain.doFilter(request, response);
}
private void cleanupExpiredRequests(long currentTime) {
Iterator<Map.Entry<String, Long>> iterator = timestamps.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Long> entry = iterator.next();
if (currentTime - entry.getValue() > TIME_WINDOW) {
iterator.remove();
requests.remove(entry.getKey());
}
}
}
private String getClientIP(HttpServletRequest request) {
// IP获取逻辑同上
return request.getRemoteAddr();
}
}
过滤器注册配置
@Configuration
public class SecurityFilterConfig {
@Autowired
private IPWhitelistFilter ipWhitelistFilter;
@Autowired
private RateLimitFilter rateLimitFilter;
@Bean
public FilterRegistrationBean<IPWhitelistFilter> registerIPWhitelistFilter() {
FilterRegistrationBean<IPWhitelistFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(ipWhitelistFilter);
registration.addUrlPatterns("/api/admin/*");
registration.setOrder(1);
return registration;
}
@Bean
public FilterRegistrationBean<RateLimitFilter> registerRateLimitFilter() {
FilterRegistrationBean<RateLimitFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(rateLimitFilter);
registration.addUrlPatterns("/api/*");
registration.setOrder(2);
return registration;
}
}
CSRF防护
CSRF防护配置
CSRF配置
@Configuration
public class CsrfConfig {
@Bean
public CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
repository.setParameterName("_csrf");
return repository;
}
@Bean
public CsrfFilter csrfFilter() {
return new CsrfFilter(csrfTokenRepository());
}
}
前端CSRF处理
// JavaScript中处理CSRF Token
function getCsrfToken() {
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
}
// AJAX请求中自动添加CSRF Token
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-XSRF-TOKEN", getCsrfToken());
}
}
});
// Angular HttpClient中处理CSRF
@Injectable()
export class HttpInterceptorService implements HttpInterceptor {
intercept(req: Request<any>, next: HttpHandler): Observable<HttpResponse<any>> {
const csrfToken = this.getCsrfToken();
if (csrfToken && req.method !== 'GET') {
req = req.clone({
setHeaders: {
'X-XSRF-TOKEN': csrfToken
}
});
}
return next.handle(req);
}
private getCsrfToken(): string {
const meta = document.querySelector('meta[name="csrf-token"]');
return meta ? meta.getAttribute('content') : '';
}
}
方法级安全
方法级权限控制
业务服务中的安全注解
@Service
public class UserManagementService {
@Autowired
private UserRepository userRepository;
@PreAuthorize("hasRole('ADMIN')")
public List<User> getAllUsers() {
return userRepository.findAll();
}
@PreAuthorize("hasRole('ADMIN') or authentication.name == #username")
public User getUserByUsername(String username) {
return userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("用户不存在"));
}
@PreAuthorize("hasPermission(targetObject, 'READ')")
public User getUser(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
}
@PreAuthorize("@securityUtils.canModifyUser(#userId, authentication.name)")
public User updateUser(Long userId, UserUpdateRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
user.setFirstName(request.getFirstName());
user.setLastName(request.getLastName());
return userRepository.save(user);
}
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public void deleteUser(Long userId) {
userRepository.deleteById(userId);
}
}
自定义权限评估器
@Component
public class SecurityUtils {
@Autowired
private PermissionService permissionService;
public boolean canModifyUser(Long userId, String currentUsername) {
// 管理员可以修改所有用户
if (permissionService.hasRole("ADMIN")) {
return true;
}
// 用户可以修改自己的信息
User user = userRepository.findById(userId).orElse(null);
if (user != null && user.getUsername().equals(currentUsername)) {
return true;
}
return false;
}
public boolean hasPermission(String permission) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
return auth.getAuthorities().stream()
.anyMatch(authority -> authority.getAuthority().equals(permission));
}
return false;
}
}
安全测试
安全集成测试
安全测试配置
@WebMvcTest(UserController.class)
@WithMockUser
class SecurityIntegrationTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
@WithAnonymousUser
@DisplayName("匿名用户不能访问受保护的资源")
void shouldDenyAccessToProtectedResources() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockUser(roles = "USER")
@DisplayName("普通用户可以访问自己的信息")
void shouldAllowUserToAccessOwnInfo() throws Exception {
mockMvc.perform(get("/api/users/profile"))
.with(user("testuser").roles("USER"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(roles = "USER")
@DisplayName("普通用户不能访问管理员功能")
void shouldDenyUserAccessToAdminFunctions() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isForbidden());
}
@Test
@WithMockUser(roles = "ADMIN")
@DisplayName("管理员可以访问所有功能")
void shouldAllowAdminAccessToAllFunctions() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(username = "testuser", roles = "USER")
@DisplayName("用户只能更新自己的信息")
void shouldAllowUserToUpdateOwnInfoOnly() throws Exception {
UserUpdateRequest request = new UserUpdateRequest();
request.setFirstName("UpdatedName");
mockMvc.perform(put("/api/users/123")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk());
mockMvc.perform(put("/api/users/456")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isForbidden());
}
}
总结
Spring MVC与Spring Security的集成提供了完整的企业级安全解决方案:
核心技术要点
- 认证机制 - 用户名密码、JWT令牌、OAuth2多种认证方式
- 授权控制 - 基于角色和权限的细粒度访问控制
- 安全过滤器 - 自定义安全过滤器和过滤器链配置
- CSRF防护 - 跨站请求伪造攻击的防护机制
- 方法级安全 - 在业务层进行权限控制
- 安全测试 - 完整的安全功能测试策略
企业级应用价值
- 多层防护:从Web层到方法层的全面安全保护
- 灵活配置:支持多种安全策略和定制化需求
- 性能优化:合理的安全机制设计不影响应用性能
- 易于维护:清晰的安全策略配置和测试体系
下一步学习建议
掌握Spring MVC安全集成后,建议继续深入学习:
- Spring Data JPA集成 - 数据层安全的最佳实践
- RESTful API安全 - API级别的高级安全策略
- 微服务安全 - 分布式环境下的安全架构
安全是企业级应用的核心要求,通过本教程的学习,您将在Spring MVC安全开发方面具备扎实的理论基础和实战能力!
1287

被折叠的 条评论
为什么被折叠?



