最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Security入門(mén)到實(shí)戰(zhàn)應(yīng)用

 更新時(shí)間:2026年02月12日 10:53:19   作者:雨中飄蕩的記憶  
本文全面介紹了SpringSecurity的核心概念和使用方法,包括認(rèn)證、授權(quán)、防護(hù)、JWT認(rèn)證等,并通過(guò)實(shí)戰(zhàn)案例展示了如何在實(shí)際應(yīng)用中應(yīng)用SpringSecurity,感興趣的朋友跟隨小編一起看看吧

前言

Spring Security是Spring生態(tài)系統(tǒng)中最強(qiáng)大的安全框架,為Java應(yīng)用提供了全面的安全解決方案。它提供了認(rèn)證、授權(quán)、防護(hù)等核心功能,廣泛應(yīng)用于企業(yè)級(jí)應(yīng)用開(kāi)發(fā)。本文將從基礎(chǔ)概念到實(shí)戰(zhàn)應(yīng)用,全面講解Spring Security的核心知識(shí)點(diǎn)。

一、Spring Security概述

1.1 什么是Spring Security

Spring Security是一個(gè)功能強(qiáng)大且高度可定制的身份驗(yàn)證和訪問(wèn)控制框架,它為Java應(yīng)用提供:

  • 身份認(rèn)證(Authentication)
  • 授權(quán)(Authorization)
  • 防護(hù)常見(jiàn)攻擊(CSRF、Session Fixation等)
  • 與Spring生態(tài)無(wú)縫集成
Spring Security核心架構(gòu)
┌─────────────────────────────────────────┐
│         Security Filter Chain           │
│  ┌───────────────────────────────────┐ │
│  │ UsernamePasswordAuthFilter        │ │
│  │ BasicAuthenticationFilter         │ │
│  │ OAuth2AuthenticationFilter        │ │
│  │ ExceptionTranslationFilter        │ │
│  │ FilterSecurityInterceptor         │ │
│  └───────────────────────────────────┘ │
│               ↓                          │
│  ┌───────────────────────────────────┐ │
│  │   Authentication Manager          │ │
│  └───────────────────────────────────┘ │
│               ↓                          │
│  ┌───────────────────────────────────┐ │
│  │   Authentication Provider         │ │
│  └───────────────────────────────────┘ │
│               ↓                          │
│  ┌───────────────────────────────────┐ │
│  │   UserDetailsService              │ │
│  └───────────────────────────────────┘ │
└─────────────────────────────────────────┘

1.2 核心概念

核心組件說(shuō)明
┌──────────────────────┬────────────────────────┐
│  組件                 │  說(shuō)明                   │
├──────────────────────┼────────────────────────┤
│  Authentication      │  認(rèn)證信息(用戶憑證)    │
│  SecurityContext     │  安全上下文(存儲(chǔ)認(rèn)證)  │
│  UserDetails         │  用戶詳情接口           │
│  UserDetailsService  │  加載用戶數(shù)據(jù)           │
│  GrantedAuthority    │  授權(quán)信息(角色/權(quán)限)   │
│  AccessDecisionManager│  訪問(wèn)決策管理器        │
└──────────────────────┴────────────────────────┘

二、快速開(kāi)始

2.1 添加依賴

<!-- Spring Boot項(xiàng)目 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.2 基礎(chǔ)配置

/**
 * Spring Security基礎(chǔ)配置
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 配置授權(quán)規(guī)則
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()      // 公開(kāi)接口
                .requestMatchers("/admin/**").hasRole("ADMIN")  // 需要ADMIN角色
                .anyRequest().authenticated()                   // 其他需要認(rèn)證
            )
            // 配置表單登錄
            .formLogin(form -> form
                .loginPage("/login")                            // 自定義登錄頁(yè)
                .defaultSuccessUrl("/home")                     // 登錄成功跳轉(zhuǎn)
                .permitAll()
            )
            // 配置登出
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
                .permitAll()
            );
        return http.build();
    }
    /**
     * 配置用戶詳情服務(wù)
     */
    @Bean
    public UserDetailsService userDetailsService() {
        // 內(nèi)存用戶(僅用于測(cè)試)
        UserDetails user = User.builder()
            .username("user")
            .password(passwordEncoder().encode("password"))
            .roles("USER")
            .build();
        UserDetails admin = User.builder()
            .username("admin")
            .password(passwordEncoder().encode("admin"))
            .roles("ADMIN", "USER")
            .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
    /**
     * 密碼編碼器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2.3 基礎(chǔ)使用

/**
 * 測(cè)試Controller
 */
@RestController
public class TestController {
    @GetMapping("/public/hello")
    public String publicHello() {
        return "公開(kāi)接口,無(wú)需認(rèn)證";
    }
    @GetMapping("/user/profile")
    public String userProfile() {
        return "用戶信息,需要認(rèn)證";
    }
    @GetMapping("/admin/dashboard")
    public String adminDashboard() {
        return "管理后臺(tái),需要ADMIN角色";
    }
    @GetMapping("/current-user")
    public String currentUser(Authentication authentication) {
        return "當(dāng)前用戶: " + authentication.getName();
    }
}

三、認(rèn)證(Authentication)

3.1 自定義UserDetailsService

/**
 * 自定義用戶詳情服務(wù)
 */
@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 從數(shù)據(jù)庫(kù)加載用戶
        com.example.entity.User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("用戶不存在: " + username));
        // 轉(zhuǎn)換為Spring Security的UserDetails
        return org.springframework.security.core.userdetails.User.builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .authorities(getAuthorities(user))
            .accountExpired(!user.isAccountNonExpired())
            .accountLocked(!user.isAccountNonLocked())
            .credentialsExpired(!user.isCredentialsNonExpired())
            .disabled(!user.isEnabled())
            .build();
    }
    /**
     * 獲取用戶權(quán)限
     */
    private Collection<? extends GrantedAuthority> getAuthorities(com.example.entity.User user) {
        return user.getRoles().stream()
            .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
            .collect(Collectors.toList());
    }
}
/**
 * 用戶實(shí)體
 */
@Entity
@Table(name = "users")
@Data
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;
    private String email;
    private boolean enabled = true;
    private boolean accountNonExpired = true;
    private boolean accountNonLocked = true;
    private boolean credentialsNonExpired = true;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();
}
/**
 * 角色實(shí)體
 */
@Entity
@Table(name = "roles")
@Data
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true, nullable = false)
    private String name;
    private String description;
}

3.2 自定義認(rèn)證提供者

/**
 * 自定義認(rèn)證提供者
 */
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        // 加載用戶
        UserDetails user = userDetailsService.loadUserByUsername(username);
        // 驗(yàn)證密碼
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new BadCredentialsException("密碼錯(cuò)誤");
        }
        // 額外的業(yè)務(wù)校驗(yàn)
        if (!user.isEnabled()) {
            throw new DisabledException("賬戶已禁用");
        }
        if (!user.isAccountNonLocked()) {
            throw new LockedException("賬戶已鎖定");
        }
        // 創(chuàng)建認(rèn)證令牌
        return new UsernamePasswordAuthenticationToken(
            user, password, user.getAuthorities()
        );
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

3.3 登錄接口

/**
 * 登錄控制器
 */
@RestController
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    /**
     * 用戶登錄
     */
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        try {
            // 認(rèn)證
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    request.getUsername(),
                    request.getPassword()
                )
            );
            // 設(shè)置到安全上下文
            SecurityContextHolder.getContext().setAuthentication(authentication);
            // 生成JWT Token
            String token = jwtTokenProvider.generateToken(authentication);
            return ResponseEntity.ok(new LoginResponse(token, "登錄成功"));
        } catch (BadCredentialsException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new ErrorResponse("用戶名或密碼錯(cuò)誤"));
        } catch (DisabledException e) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body(new ErrorResponse("賬戶已禁用"));
        }
    }
    /**
     * 用戶注冊(cè)
     */
    @PostMapping("/register")
    public ResponseEntity<?> register(@RequestBody RegisterRequest request) {
        // 實(shí)現(xiàn)注冊(cè)邏輯
        return ResponseEntity.ok("注冊(cè)成功");
    }
}
@Data
class LoginRequest {
    private String username;
    private String password;
}
@Data
@AllArgsConstructor
class LoginResponse {
    private String token;
    private String message;
}
@Data
@AllArgsConstructor
class ErrorResponse {
    private String message;
}
@Data
class RegisterRequest {
    private String username;
    private String password;
    private String email;
}

四、授權(quán)(Authorization)

4.1 基于URL的授權(quán)

/**
 * URL授權(quán)配置
 */
@Configuration
@EnableWebSecurity
public class UrlAuthorizationConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                // 公開(kāi)接口
                .requestMatchers("/", "/public/**", "/auth/**").permitAll()
                // 需要認(rèn)證
                .requestMatchers("/user/**").authenticated()
                // 角色授權(quán)
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/manager/**").hasAnyRole("ADMIN", "MANAGER")
                // 權(quán)限授權(quán)
                .requestMatchers("/api/users/**").hasAuthority("USER_MANAGE")
                .requestMatchers("/api/posts/**").hasAnyAuthority("POST_READ", "POST_WRITE")
                // 使用SpEL表達(dá)式
                .requestMatchers("/api/account/**").access(
                    new WebExpressionAuthorizationManager(
                        "hasRole('USER') and #username == authentication.name"
                    )
                )
                // 其他請(qǐng)求需要認(rèn)證
                .anyRequest().authenticated()
            );
        return http.build();
    }
}

4.2 基于方法的授權(quán)

/**
 * 啟用方法級(jí)安全
 */
@Configuration
@EnableMethodSecurity(
    prePostEnabled = true,      // 啟用@PreAuthorize/@PostAuthorize
    securedEnabled = true,       // 啟用@Secured
    jsr250Enabled = true         // 啟用@RolesAllowed
)
public class MethodSecurityConfig {
}
/**
 * 方法級(jí)授權(quán)示例
 */
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    /**
     * @PreAuthorize:方法執(zhí)行前檢查權(quán)限
     */
    @PreAuthorize("hasRole('ADMIN')")
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    /**
     * 基于參數(shù)的權(quán)限檢查
     */
    @PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')")
    public User getUserById(Long userId) {
        return userRepository.findById(userId).orElse(null);
    }
    /**
     * 復(fù)雜表達(dá)式
     */
    @PreAuthorize("hasRole('USER') and #user.username == authentication.name")
    public void updateUser(User user) {
        userRepository.save(user);
    }
    /**
     * @PostAuthorize:方法執(zhí)行后檢查權(quán)限
     */
    @PostAuthorize("returnObject.username == authentication.name or hasRole('ADMIN')")
    public User findUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    /**
     * @Secured:基于角色的簡(jiǎn)單授權(quán)
     */
    @Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
    public void deleteUser(Long userId) {
        userRepository.deleteById(userId);
    }
    /**
     * @RolesAllowed:JSR-250標(biāo)準(zhǔn)
     */
    @RolesAllowed("ADMIN")
    public void resetPassword(Long userId, String newPassword) {
        User user = userRepository.findById(userId).orElseThrow();
        user.setPassword(newPassword);
        userRepository.save(user);
    }
    /**
     * @PreFilter:過(guò)濾方法參數(shù)
     */
    @PreFilter("filterObject.owner == authentication.name")
    public void deleteItems(List<Item> items) {
        items.forEach(item -> System.out.println("刪除: " + item.getName()));
    }
    /**
     * @PostFilter:過(guò)濾返回結(jié)果
     */
    @PostFilter("filterObject.owner == authentication.name")
    public List<Item> getItems() {
        // 返回所有數(shù)據(jù),Spring Security會(huì)過(guò)濾
        return Arrays.asList(
            new Item("Item1", "user1"),
            new Item("Item2", "user2"),
            new Item("Item3", "user1")
        );
    }
}
@Data
@AllArgsConstructor
class Item {
    private String name;
    private String owner;
}

4.3 自定義權(quán)限評(píng)估器

/**
 * 自定義權(quán)限評(píng)估器
 */
@Component("permissionEvaluator")
public class CustomPermissionEvaluator implements PermissionEvaluator {
    @Autowired
    private UserRepository userRepository;
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject,
                                Object permission) {
        if (authentication == null || targetDomainObject == null || !(permission instanceof String)) {
            return false;
        }
        String targetType = targetDomainObject.getClass().getSimpleName().toUpperCase();
        return hasPrivilege(authentication, targetType, permission.toString());
    }
    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId,
                                String targetType, Object permission) {
        if (authentication == null || targetType == null || !(permission instanceof String)) {
            return false;
        }
        return hasPrivilege(authentication, targetType.toUpperCase(), permission.toString());
    }
    private boolean hasPrivilege(Authentication auth, String targetType, String permission) {
        String username = auth.getName();
        User user = userRepository.findByUsername(username).orElse(null);
        if (user == null) {
            return false;
        }
        // 檢查用戶權(quán)限
        return user.getRoles().stream()
            .flatMap(role -> role.getPermissions().stream())
            .anyMatch(p -> p.getName().equals(targetType + "_" + permission));
    }
}
/**
 * 使用自定義權(quán)限評(píng)估器
 */
@Service
public class DocumentService {
    /**
     * 使用hasPermission檢查權(quán)限
     */
    @PreAuthorize("hasPermission(#document, 'EDIT')")
    public void editDocument(Document document) {
        System.out.println("編輯文檔: " + document.getTitle());
    }
    @PreAuthorize("hasPermission(#documentId, 'DOCUMENT', 'DELETE')")
    public void deleteDocument(Long documentId) {
        System.out.println("刪除文檔: " + documentId);
    }
}
@Data
class Document {
    private Long id;
    private String title;
    private String owner;
}

五、JWT認(rèn)證

5.1 JWT工具類

/**
 * JWT Token提供者
 */
@Component
public class JwtTokenProvider {
    @Value("${jwt.secret:mySecretKey}")
    private String jwtSecret;
    @Value("${jwt.expiration:86400000}")  // 24小時(shí)
    private long jwtExpiration;
    /**
     * 生成Token
     */
    public String generateToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpiration);
        return Jwts.builder()
            .setSubject(userDetails.getUsername())
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }
    /**
     * 從Token獲取用戶名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody();
        return claims.getSubject();
    }
    /**
     * 驗(yàn)證Token
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            return true;
        } catch (SignatureException ex) {
            System.err.println("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            System.err.println("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            System.err.println("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            System.err.println("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            System.err.println("JWT claims string is empty");
        }
        return false;
    }
}

5.2 JWT認(rèn)證過(guò)濾器

/**
 * JWT認(rèn)證過(guò)濾器
 */
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 {
            // 從請(qǐng)求頭獲取Token
            String jwt = getJwtFromRequest(request);
            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                // 從Token獲取用戶名
                String username = tokenProvider.getUsernameFromToken(jwt);
                // 加載用戶詳情
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                // 創(chuàng)建認(rèn)證對(duì)象
                UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities()
                    );
                authentication.setDetails(
                    new WebAuthenticationDetailsSource().buildDetails(request)
                );
                // 設(shè)置到安全上下文
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }
        filterChain.doFilter(request, response);
    }
    /**
     * 從請(qǐng)求頭提取Token
     */
    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

5.3 JWT配置

/**
 * JWT安全配置
 */
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthEntryPoint;
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 禁用CSRF(使用JWT不需要)
            .csrf(csrf -> csrf.disable())
            // 禁用Session
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            // 異常處理
            .exceptionHandling(exception -> exception
                .authenticationEntryPoint(jwtAuthEntryPoint)
            )
            // 授權(quán)配置
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/auth/**").permitAll()
                .anyRequest().authenticated()
            );
        // 添加JWT過(guò)濾器
        http.addFilterBefore(
            jwtAuthenticationFilter(),
            UsernamePasswordAuthenticationFilter.class
        );
        return http.build();
    }
    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }
}
/**
 * JWT認(rèn)證入口點(diǎn)(處理認(rèn)證失?。?
 */
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                        HttpServletResponse response,
                        AuthenticationException authException)
            throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        Map<String, Object> error = new HashMap<>();
        error.put("status", 401);
        error.put("error", "Unauthorized");
        error.put("message", authException.getMessage());
        error.put("path", request.getServletPath());
        ObjectMapper mapper = new ObjectMapper();
        response.getWriter().write(mapper.writeValueAsString(error));
    }
}

六、實(shí)戰(zhàn)案例

6.1 案例1:多租戶權(quán)限隔離

/**
 * 租戶上下文
 */
public class TenantContext {
    private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
    public static void setCurrentTenant(String tenant) {
        currentTenant.set(tenant);
    }
    public static String getCurrentTenant() {
        return currentTenant.get();
    }
    public static void clear() {
        currentTenant.remove();
    }
}
/**
 * 租戶過(guò)濾器
 */
public class TenantFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                   HttpServletResponse response,
                                   FilterChain filterChain)
            throws ServletException, IOException {
        try {
            // 從請(qǐng)求頭獲取租戶ID
            String tenantId = request.getHeader("X-Tenant-ID");
            if (tenantId != null) {
                TenantContext.setCurrentTenant(tenantId);
            }
            filterChain.doFilter(request, response);
        } finally {
            TenantContext.clear();
        }
    }
}
/**
 * 租戶權(quán)限評(píng)估器
 */
@Component
public class TenantPermissionEvaluator {
    /**
     * 檢查用戶是否屬于當(dāng)前租戶
     */
    public boolean belongsToTenant(Authentication authentication) {
        String currentTenant = TenantContext.getCurrentTenant();
        if (currentTenant == null) {
            return false;
        }
        UserDetails user = (UserDetails) authentication.getPrincipal();
        // 從用戶信息中獲取租戶ID并比較
        return true;  // 簡(jiǎn)化實(shí)現(xiàn)
    }
}
/**
 * 使用租戶權(quán)限
 */
@RestController
@RequestMapping("/api/tenants")
public class TenantController {
    @PreAuthorize("@tenantPermissionEvaluator.belongsToTenant(authentication)")
    @GetMapping("/data")
    public List<TenantData> getTenantData() {
        String tenantId = TenantContext.getCurrentTenant();
        return Arrays.asList(new TenantData(tenantId, "數(shù)據(jù)"));
    }
}
@Data
@AllArgsConstructor
class TenantData {
    private String tenantId;
    private String data;
}

6.2 案例2:動(dòng)態(tài)權(quán)限管理

/**
 * 動(dòng)態(tài)權(quán)限服務(wù)
 */
@Service
public class DynamicPermissionService {
    @Autowired
    private PermissionRepository permissionRepository;
    /**
     * 檢查動(dòng)態(tài)權(quán)限
     */
    public boolean hasPermission(String username, String resource, String action) {
        // 從數(shù)據(jù)庫(kù)查詢用戶權(quán)限
        List<Permission> permissions = permissionRepository.findByUsername(username);
        return permissions.stream()
            .anyMatch(p -> p.getResource().equals(resource) &&
                          p.getAction().equals(action));
    }
}
/**
 * 權(quán)限實(shí)體
 */
@Entity
@Table(name = "permissions")
@Data
class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String resource;
    private String action;
}
/**
 * 動(dòng)態(tài)權(quán)限攔截器
 */
@Component
@Aspect
public class DynamicPermissionAspect {
    @Autowired
    private DynamicPermissionService permissionService;
    @Around("@annotation(requirePermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint,
                                 RequirePermission requirePermission)
            throws Throwable {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String username = auth.getName();
        boolean hasPermission = permissionService.hasPermission(
            username,
            requirePermission.resource(),
            requirePermission.action()
        );
        if (!hasPermission) {
            throw new AccessDeniedException("權(quán)限不足");
        }
        return joinPoint.proceed();
    }
}
/**
 * 自定義權(quán)限注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface RequirePermission {
    String resource();
    String action();
}
/**
 * 使用動(dòng)態(tài)權(quán)限
 */
@RestController
@RequestMapping("/api/users")
public class DynamicPermissionController {
    @RequirePermission(resource = "USER", action = "CREATE")
    @PostMapping
    public String createUser(@RequestBody User user) {
        return "創(chuàng)建用戶成功";
    }
    @RequirePermission(resource = "USER", action = "DELETE")
    @DeleteMapping("/{id}")
    public String deleteUser(@PathVariable Long id) {
        return "刪除用戶成功";
    }
}

6.3 案例3:OAuth2社交登錄

/**
 * OAuth2配置
 */
@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/login/**", "/oauth2/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .userInfoEndpoint(userInfo -> userInfo
                    .userService(oauth2UserService())
                )
                .successHandler(oauth2AuthenticationSuccessHandler())
            );
        return http.build();
    }
    @Bean
    public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
        return new CustomOAuth2UserService();
    }
    @Bean
    public AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler() {
        return new OAuth2AuthenticationSuccessHandler();
    }
}
/**
 * 自定義OAuth2用戶服務(wù)
 */
public class CustomOAuth2UserService
        extends DefaultOAuth2UserService {
    @Autowired
    private UserRepository userRepository;
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) {
        OAuth2User oauth2User = super.loadUser(userRequest);
        // 處理OAuth2用戶信息
        return processOAuth2User(userRequest, oauth2User);
    }
    private OAuth2User processOAuth2User(OAuth2UserRequest userRequest,
                                        OAuth2User oauth2User) {
        String registrationId = userRequest.getClientRegistration()
            .getRegistrationId();
        String email = oauth2User.getAttribute("email");
        // 查找或創(chuàng)建用戶
        User user = userRepository.findByEmail(email)
            .orElseGet(() -> {
                User newUser = new User();
                newUser.setEmail(email);
                newUser.setUsername(oauth2User.getAttribute("name"));
                newUser.setEnabled(true);
                return userRepository.save(newUser);
            });
        return new CustomOAuth2User(user, oauth2User.getAttributes());
    }
}
/**
 * OAuth2成功處理器
 */
public class OAuth2AuthenticationSuccessHandler
        extends SimpleUrlAuthenticationSuccessHandler {
    @Autowired
    private JwtTokenProvider tokenProvider;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                       HttpServletResponse response,
                                       Authentication authentication)
            throws IOException {
        // 生成JWT Token
        String token = tokenProvider.generateToken(authentication);
        // 重定向到前端,攜帶Token
        String targetUrl = "http://localhost:3000/oauth2/redirect?token=" + token;
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }
}

6.4 案例4:驗(yàn)證碼登錄

/**
 * 驗(yàn)證碼服務(wù)
 */
@Service
public class CaptchaService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    /**
     * 生成驗(yàn)證碼
     */
    public String generateCaptcha(String mobile) {
        String code = String.format("%06d", new Random().nextInt(999999));
        // 存儲(chǔ)到Redis,5分鐘過(guò)期
        redisTemplate.opsForValue().set(
            "captcha:" + mobile, code, 5, TimeUnit.MINUTES
        );
        // 實(shí)際應(yīng)用中,這里調(diào)用短信服務(wù)發(fā)送驗(yàn)證碼
        System.out.println("驗(yàn)證碼: " + code);
        return code;
    }
    /**
     * 驗(yàn)證驗(yàn)證碼
     */
    public boolean verifyCaptcha(String mobile, String code) {
        String storedCode = redisTemplate.opsForValue().get("captcha:" + mobile);
        return code.equals(storedCode);
    }
}
/**
 * 驗(yàn)證碼認(rèn)證Token
 */
public class CaptchaAuthenticationToken extends AbstractAuthenticationToken {
    private final String mobile;
    private String captcha;
    public CaptchaAuthenticationToken(String mobile, String captcha) {
        super(null);
        this.mobile = mobile;
        this.captcha = captcha;
        setAuthenticated(false);
    }
    public CaptchaAuthenticationToken(String mobile,
                                     Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.mobile = mobile;
        setAuthenticated(true);
    }
    @Override
    public Object getCredentials() {
        return captcha;
    }
    @Override
    public Object getPrincipal() {
        return mobile;
    }
    public String getMobile() {
        return mobile;
    }
}
/**
 * 驗(yàn)證碼認(rèn)證提供者
 */
@Component
public class CaptchaAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private CaptchaService captchaService;
    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        CaptchaAuthenticationToken token = (CaptchaAuthenticationToken) authentication;
        String mobile = token.getMobile();
        String captcha = (String) token.getCredentials();
        // 驗(yàn)證驗(yàn)證碼
        if (!captchaService.verifyCaptcha(mobile, captcha)) {
            throw new BadCredentialsException("驗(yàn)證碼錯(cuò)誤");
        }
        // 加載用戶
        UserDetails user = userDetailsService.loadUserByUsername(mobile);
        return new CaptchaAuthenticationToken(mobile, user.getAuthorities());
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return CaptchaAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
/**
 * 驗(yàn)證碼登錄接口
 */
@RestController
@RequestMapping("/auth")
public class CaptchaAuthController {
    @Autowired
    private CaptchaService captchaService;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    /**
     * 發(fā)送驗(yàn)證碼
     */
    @PostMapping("/captcha/send")
    public ResponseEntity<?> sendCaptcha(@RequestParam String mobile) {
        captchaService.generateCaptcha(mobile);
        return ResponseEntity.ok("驗(yàn)證碼已發(fā)送");
    }
    /**
     * 驗(yàn)證碼登錄
     */
    @PostMapping("/captcha/login")
    public ResponseEntity<?> loginWithCaptcha(@RequestBody CaptchaLoginRequest request) {
        try {
            Authentication authentication = authenticationManager.authenticate(
                new CaptchaAuthenticationToken(request.getMobile(), request.getCaptcha())
            );
            String token = jwtTokenProvider.generateToken(authentication);
            return ResponseEntity.ok(new LoginResponse(token, "登錄成功"));
        } catch (AuthenticationException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new ErrorResponse(e.getMessage()));
        }
    }
}
@Data
class CaptchaLoginRequest {
    private String mobile;
    private String captcha;
}

七、安全最佳實(shí)踐

7.1 CSRF防護(hù)

/**
 * CSRF配置
 */
@Configuration
public class CsrfConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 啟用CSRF保護(hù)
            .csrf(csrf -> csrf
                // 忽略某些URL
                .ignoringRequestMatchers("/api/**")
                // 使用Cookie存儲(chǔ)Token
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            );
        return http.build();
    }
}

7.2 密碼策略

/**
 * 密碼編碼器配置
 */
@Configuration
public class PasswordConfig {
    /**
     * 使用BCrypt編碼器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);  // 強(qiáng)度為12
    }
    /**
     * 密碼驗(yàn)證規(guī)則
     */
    public boolean isValidPassword(String password) {
        // 至少8位
        if (password.length() < 8) {
            return false;
        }
        // 包含大寫(xiě)字母、小寫(xiě)字母、數(shù)字、特殊字符
        String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
        return password.matches(regex);
    }
}

7.3 會(huì)話管理

/**
 * 會(huì)話管理配置
 */
@Configuration
public class SessionConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .sessionManagement(session -> session
                // 會(huì)話創(chuàng)建策略
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                // 最大會(huì)話數(shù)
                .maximumSessions(1)
                // 阻止新會(huì)話
                .maxSessionsPreventsLogin(true)
                // 會(huì)話過(guò)期URL
                .expiredUrl("/login?expired")
            );
        return http.build();
    }
}

八、總結(jié)

核心知識(shí)點(diǎn)回顧

Spring Security核心要點(diǎn)
│
├── 核心概念
│   ├── Authentication(認(rèn)證)
│   ├── Authorization(授權(quán))
│   ├── SecurityContext(安全上下文)
│   └── FilterChain(過(guò)濾器鏈)
│
├── 認(rèn)證方式
│   ├── 表單登錄
│   ├── HTTP Basic
│   ├── JWT Token
│   ├── OAuth2
│   └── 驗(yàn)證碼登錄
│
├── 授權(quán)機(jī)制
│   ├── URL授權(quán)
│   ├── 方法授權(quán)
│   ├── 表達(dá)式授權(quán)
│   └── 自定義權(quán)限評(píng)估
│
├── 高級(jí)特性
│   ├── 多租戶隔離
│   ├── 動(dòng)態(tài)權(quán)限
│   ├── 社交登錄
│   └── 驗(yàn)證碼認(rèn)證
│
└── 最佳實(shí)踐
    ├── CSRF防護(hù)
    ├── 密碼策略
    ├── 會(huì)話管理
    └── 安全配置

Spring Security是一個(gè)功能強(qiáng)大的安全框架,掌握其核心概念和使用方法對(duì)于構(gòu)建安全的企業(yè)級(jí)應(yīng)用至關(guān)重要。在實(shí)際應(yīng)用中,需要根據(jù)業(yè)務(wù)需求選擇合適的認(rèn)證和授權(quán)策略,并遵循安全最佳實(shí)踐。

到此這篇關(guān)于Spring Security入門(mén)到實(shí)戰(zhàn)應(yīng)用的文章就介紹到這了,更多相關(guān)Spring Security使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

宁化县| 建始县| 杭州市| 二连浩特市| 开阳县| 灵石县| 开江县| 灌云县| 开化县| 南溪县| 开江县| 拜城县| 中阳县| 彰化市| 沿河| 新密市| 临安市| 青河县| 清涧县| 大宁县| 八宿县| 镇宁| 肇庆市| 万山特区| 新竹县| 长白| 苍山县| 永春县| 焉耆| 水富县| 大渡口区| 阿拉善盟| 五华县| 广汉市| 介休市| 杭州市| 泗水县| 崇文区| 龙门县| 乌什县| 承德市|