认证与授权
2026/1/15大约 3 分钟SpringSpring SecurityJava安全认证授权
认证与授权
认证流程
自定义 UserDetailsService
基于数据库的用户认证
@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));
// 转换为 UserDetails
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles().toArray(new String[0]))
.accountExpired(!user.isActive())
.accountLocked(user.isLocked())
.build();
}
}用户实体
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "role")
private Set<String> roles = new HashSet<>();
private boolean active = true;
private boolean locked = false;
// getters and setters
}密码加密
PasswordEncoder
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
// 推荐使用 BCrypt
return new BCryptPasswordEncoder();
}
}常用 PasswordEncoder
| 实现 | 说明 |
|---|---|
| BCryptPasswordEncoder | 推荐,自带盐值 |
| Pbkdf2PasswordEncoder | PBKDF2 算法 |
| SCryptPasswordEncoder | SCrypt 算法 |
| Argon2PasswordEncoder | Argon2 算法 |
| NoOpPasswordEncoder | 不加密(仅测试) |
密码加密示例
@Service
public class UserService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserRepository userRepository;
public User register(String username, String rawPassword) {
User user = new User();
user.setUsername(username);
user.setPassword(passwordEncoder.encode(rawPassword)); // 加密存储
user.getRoles().add("USER");
return userRepository.save(user);
}
}URL 授权
基于路径的权限控制
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
// 静态资源
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
// 公开接口
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/login", "/register").permitAll()
// 角色控制
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
// 权限控制
.requestMatchers("/api/users/**").hasAuthority("USER_READ")
.requestMatchers(HttpMethod.POST, "/api/users/**").hasAuthority("USER_WRITE")
// 其他请求需要认证
.anyRequest().authenticated()
);
return http.build();
}
}权限表达式
| 表达式 | 说明 |
|---|---|
permitAll() | 允许所有访问 |
denyAll() | 拒绝所有访问 |
authenticated() | 需要认证 |
anonymous() | 匿名用户 |
hasRole("ADMIN") | 需要 ROLE_ADMIN 角色 |
hasAnyRole("USER", "ADMIN") | 需要任一角色 |
hasAuthority("USER_READ") | 需要指定权限 |
hasAnyAuthority(...) | 需要任一权限 |
hasIpAddress("192.168.1.0/24") | IP 地址限制 |
方法级授权
启用方法安全
@Configuration
@EnableMethodSecurity // Spring Security 6.x
public class MethodSecurityConfig {
}@PreAuthorize
在方法执行前进行权限检查。
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
// 只有 ADMIN 角色可以删除用户
}
@PreAuthorize("hasAuthority('USER_READ')")
public User getUser(Long id) {
// 需要 USER_READ 权限
}
@PreAuthorize("#username == authentication.name or hasRole('ADMIN')")
public User getUserByUsername(String username) {
// 只能查看自己的信息,或者是管理员
}
@PreAuthorize("@permissionService.canAccess(#id)")
public Resource getResource(Long id) {
// 调用自定义权限检查
}
}@PostAuthorize
在方法执行后进行权限检查。
@Service
public class DocumentService {
@PostAuthorize("returnObject.owner == authentication.name or hasRole('ADMIN')")
public Document getDocument(Long id) {
// 只能返回自己的文档,或者是管理员
return documentRepository.findById(id).orElse(null);
}
}@PreFilter 和 @PostFilter
过滤集合参数或返回值。
@Service
public class DocumentService {
@PreFilter("filterObject.owner == authentication.name")
public void deleteDocuments(List<Document> documents) {
// 只删除属于当前用户的文档
}
@PostFilter("filterObject.owner == authentication.name or hasRole('ADMIN')")
public List<Document> getAllDocuments() {
// 只返回属于当前用户的文档,管理员可以看到所有
return documentRepository.findAll();
}
}自定义权限检查
@Component("permissionService")
public class PermissionService {
@Autowired
private ResourceRepository resourceRepository;
public boolean canAccess(Long resourceId) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Resource resource = resourceRepository.findById(resourceId).orElse(null);
if (resource == null) {
return false;
}
// 检查是否是资源所有者
if (resource.getOwner().equals(username)) {
return true;
}
// 检查是否有共享权限
return resource.getSharedUsers().contains(username);
}
public boolean hasPermission(String permission) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals(permission));
}
}使用:
@PreAuthorize("@permissionService.canAccess(#id)")
public Resource getResource(Long id) {
return resourceRepository.findById(id).orElse(null);
}获取当前用户
方式一:SecurityContextHolder
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();方式二:@AuthenticationPrincipal
@GetMapping("/me")
public User getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
return userService.findByUsername(userDetails.getUsername());
}方式三:Principal 参数
@GetMapping("/me")
public User getCurrentUser(Principal principal) {
return userService.findByUsername(principal.getName());
}小结
Spring Security 提供了完善的认证和授权机制。认证通过 UserDetailsService 加载用户信息,授权可以基于 URL 或方法级别进行控制。使用 @PreAuthorize 等注解可以实现细粒度的权限控制。
面试题预览
常见面试题
- Spring Security 的认证流程是怎样的?
- @PreAuthorize 和 @PostAuthorize 有什么区别?
- 如何实现自定义的权限检查逻辑?
