🔐 Spring Security认证与授权:守护系统安全的铁闸!

75 阅读7分钟

副标题:从登录到权限控制,安全防护全流程!🎯


🎬 开场:为什么需要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=1234562. 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保驾护航! 🔐✨