副标题:从登录到权限控制,安全防护全流程!🎯
🎬 开场:为什么需要Spring Security?
安全的重要性
没有安全机制的系统:
任何人都可以:
- 访问任何接口 ❌
- 查看任何数据 ❌
- 执行任何操作 ❌
后果:
- 数据泄露 💥
- 恶意操作 💥
- 系统崩溃 💥
有Spring Security的系统:
1. 认证(Authentication):你是谁?
- 登录验证
- 身份确认
2. 授权(Authorization):你能做什么?
- 权限检查
- 访问控制
结果:安全可靠 ✅
📚 核心概念
认证 vs 授权
认证(Authentication):
- 验证用户身份
- 确认"你是谁"
- 例如:用户名密码登录
授权(Authorization):
- 验证用户权限
- 确认"你能做什么"
- 例如:管理员可以删除用户
流程:
登录(认证) → 确认身份 → 检查权限(授权) → 访问资源
核心组件
Spring Security核心组件:
1. SecurityContextHolder
- 存储安全上下文
- 当前用户信息
2. SecurityContext
- 安全上下文
- 包含Authentication
3. Authentication
- 认证信息
- 用户名、密码、权限
4. UserDetails
- 用户详情
- 用户信息接口
5. UserDetailsService
- 用户详情服务
- 加载用户信息
6. PasswordEncoder
- 密码编码器
- 密码加密
7. AuthenticationManager
- 认证管理器
- 执行认证
8. AccessDecisionManager
- 访问决策管理器
- 权限判断
🎯 认证流程
完整流程图
Spring Security认证流程:
1. 用户提交登录请求
POST /login
username=admin&password=123456
↓
2. UsernamePasswordAuthenticationFilter拦截
创建Authentication对象(未认证)
↓
3. AuthenticationManager认证
调用authenticate()方法
↓
4. AuthenticationProvider执行认证
- DaoAuthenticationProvider
↓
5. UserDetailsService加载用户
loadUserByUsername()
↓
6. 从数据库查询用户信息
SELECT * FROM users WHERE username = ?
↓
7. 返回UserDetails对象
包含用户名、密码、权限
↓
8. 比对密码
PasswordEncoder.matches()
↓
9. 认证成功/失败
成功:创建已认证的Authentication
失败:抛出异常
↓
10. 存储到SecurityContext
SecurityContextHolder.setContext()
↓
11. 返回响应
成功:跳转到首页
失败:返回登录页
核心源码
/**
* UsernamePasswordAuthenticationFilter:用户名密码认证过滤器
*/
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
/**
* 尝试认证
*/
@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. 设置详情
setDetails(request, authRequest);
// 4. 调用AuthenticationManager进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
/**
* AuthenticationManager:认证管理器
*/
public interface AuthenticationManager {
/**
* 认证
*/
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
/**
* ProviderManager:AuthenticationManager的实现
*/
public class ProviderManager implements AuthenticationManager {
private List<AuthenticationProvider> providers;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 遍历所有AuthenticationProvider
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(authentication.getClass())) {
continue;
}
try {
// 执行认证
Authentication result = provider.authenticate(authentication);
if (result != null) {
// 认证成功
copyDetails(authentication, result);
return result;
}
} catch (AuthenticationException ex) {
// 认证失败
throw ex;
}
}
throw new ProviderNotFoundException("No AuthenticationProvider found");
}
}
/**
* DaoAuthenticationProvider:基于DAO的认证提供者
*/
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private UserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
/**
* 检索用户
*/
@Override
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
try {
// 加载用户信息
UserDetails loadedUser = this.getUserDetailsService()
.loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null");
}
return loadedUser;
} catch (UsernameNotFoundException ex) {
throw ex;
}
}
/**
* 附加认证检查
*/
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 密码为空
if (authentication.getCredentials() == null) {
throw new BadCredentialsException("Bad credentials");
}
String presentedPassword = authentication.getCredentials().toString();
// 比对密码
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
}
}
🎯 授权流程
授权检查流程
Spring Security授权流程:
1. 用户请求受保护的资源
GET /admin/users
↓
2. FilterSecurityInterceptor拦截
权限拦截器
↓
3. 获取SecurityContext
从SecurityContextHolder获取
↓
4. 获取Authentication
当前用户的认证信息
↓
5. 获取ConfigAttribute
需要的权限配置
例如:ROLE_ADMIN
↓
6. AccessDecisionManager决策
判断是否有权限
↓
7. AccessDecisionVoter投票
- RoleVoter:角色投票器
- AuthenticatedVoter:认证投票器
↓
8. 投票结果
ACCESS_GRANTED:允许访问
ACCESS_DENIED:拒绝访问
↓
9. 返回结果
允许:继续执行
拒绝:抛出AccessDeniedException
💻 基本配置
引入依赖
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置类
/**
* Spring Security配置
*/
@Configuration
@EnableWebSecurity // 启用Web安全
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
/**
* 配置HTTP安全
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 授权配置
.authorizeRequests()
.antMatchers("/", "/login", "/register").permitAll() // 允许所有人访问
.antMatchers("/admin/**").hasRole("ADMIN") // 需要ADMIN角色
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // 需要USER或ADMIN角色
.anyRequest().authenticated() // 其他请求需要认证
.and()
// 表单登录
.formLogin()
.loginPage("/login") // 登录页面
.loginProcessingUrl("/login") // 登录处理URL
.defaultSuccessUrl("/index") // 登录成功跳转
.failureUrl("/login?error") // 登录失败跳转
.permitAll()
.and()
// 注销
.logout()
.logoutUrl("/logout") // 注销URL
.logoutSuccessUrl("/login?logout") // 注销成功跳转
.permitAll()
.and()
// 记住我
.rememberMe()
.key("uniqueAndSecret")
.tokenValiditySeconds(86400) // 有效期1天
.and()
// CSRF
.csrf().disable(); // 禁用CSRF(生产环境不推荐)
}
/**
* 配置认证管理器
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService) // 用户详情服务
.passwordEncoder(passwordEncoder()); // 密码编码器
}
/**
* 密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserDetailsService实现
/**
* 用户详情服务实现
*/
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
/**
* 根据用户名加载用户
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 查询用户
User user = userMapper.selectByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在:" + username);
}
// 2. 查询用户权限
List<String> permissions = userMapper.selectPermissionsByUserId(user.getId());
// 3. 转换为GrantedAuthority
List<GrantedAuthority> authorities = permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// 4. 返回UserDetails
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
authorities
);
}
}
Controller
/**
* 用户Controller
*/
@Controller
public class UserController {
/**
* 登录页面
*/
@GetMapping("/login")
public String login() {
return "login";
}
/**
* 首页
*/
@GetMapping("/index")
public String index() {
// 获取当前用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
System.out.println("当前用户:" + username);
return "index";
}
/**
* 管理员页面
*/
@GetMapping("/admin/users")
@PreAuthorize("hasRole('ADMIN')") // 方法级别的权限控制
public String adminUsers() {
return "admin/users";
}
}
🔧 高级配置
方法级别权限控制
/**
* 启用方法级别的安全
*/
@Configuration
@EnableGlobalMethodSecurity(
prePostEnabled = true, // 启用@PreAuthorize/@PostAuthorize
securedEnabled = true, // 启用@Secured
jsr250Enabled = true // 启用@RolesAllowed
)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
/**
* 使用注解控制权限
*/
@Service
public class UserService {
/**
* @PreAuthorize:方法执行前检查权限
*/
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long userId) {
userMapper.deleteById(userId);
}
/**
* @PostAuthorize:方法执行后检查权限
*/
@PostAuthorize("returnObject.username == authentication.name")
public User getUserById(Long id) {
return userMapper.selectById(id);
}
/**
* @Secured:指定角色
*/
@Secured("ROLE_ADMIN")
public void updateUser(User user) {
userMapper.updateById(user);
}
/**
* @RolesAllowed:JSR-250标准
*/
@RolesAllowed("ADMIN")
public void batchDelete(List<Long> userIds) {
userMapper.deleteBatchIds(userIds);
}
/**
* SpEL表达式:复杂权限判断
*/
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public void updateUserProfile(Long userId, UserProfile profile) {
userProfileMapper.update(profile);
}
}
自定义权限验证
/**
* 自定义权限验证
*/
@Component("customSecurity")
public class CustomSecurityExpression {
/**
* 检查是否是资源所有者
*/
public boolean isOwner(Long userId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return false;
}
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
User currentUser = userService.findByUsername(userDetails.getUsername());
return currentUser.getId().equals(userId);
}
/**
* 检查是否有数据权限
*/
public boolean hasDataPermission(String dataType, Long dataId) {
// 自定义数据权限逻辑
return dataPermissionService.check(dataType, dataId);
}
}
/**
* 使用自定义权限验证
*/
@Service
public class OrderService {
@PreAuthorize("@customSecurity.isOwner(#userId)")
public List<Order> getMyOrders(Long userId) {
return orderMapper.selectByUserId(userId);
}
@PreAuthorize("@customSecurity.hasDataPermission('order', #orderId)")
public Order getOrder(Long orderId) {
return orderMapper.selectById(orderId);
}
}
JWT认证
/**
* JWT工具类
*/
@Component
public class JwtTokenUtil {
private static final String SECRET = "mySecretKey";
private static final long EXPIRATION = 86400000; // 1天
/**
* 生成Token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", userDetails.getUsername());
claims.put("authorities", userDetails.getAuthorities());
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
/**
* 从Token获取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 验证Token
*/
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
}
/**
* JWT认证过滤器
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
// 1. 获取Token
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
try {
// 2. 解析Token
String username = jwtTokenUtil.getUsernameFromToken(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 3. 加载用户详情
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 4. 验证Token
if (jwtTokenUtil.validateToken(token, userDetails)) {
// 5. 创建Authentication
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
// 6. 设置到SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
} catch (Exception e) {
logger.error("JWT认证失败", e);
}
}
chain.doFilter(request, response);
}
}
🎉 总结
核心流程
认证流程:
1. 用户提交登录请求
2. Filter拦截请求
3. AuthenticationManager认证
4. UserDetailsService加载用户
5. PasswordEncoder验证密码
6. 创建Authentication
7. 存储到SecurityContext
授权流程:
1. 用户请求资源
2. Filter拦截请求
3. 获取Authentication
4. 获取ConfigAttribute
5. AccessDecisionManager决策
6. 允许/拒绝访问
记忆口诀
Spring Security守护安全,
认证授权两大功能。
认证流程要记牢:
Filter拦截创建Token,
AuthenticationManager管理认证,
Provider执行具体认证,
UserDetailsService加载用户,
PasswordEncoder验证密码,
成功创建Authentication,
存入SecurityContext保存。
授权流程也简单:
FilterSecurityInterceptor拦截,
获取Authentication当前用户,
ConfigAttribute需要权限,
AccessDecisionManager决策,
Voter投票表决,
允许继续拒绝抛异常。
三级权限控制:
URL级别最常用,
方法级别更精细,
数据级别最复杂。
JWT无状态认证:
生成Token返回客户端,
请求携带Token验证,
无需Session服务端,
适合前后端分离!
愿你的系统安全稳固,Spring Security保驾护航! 🔐✨