Spring Security 鉴权与授权详解(前后端分离项目)

前言

在现代 Web 开发中,前后端分离架构已经成为主流。后端专注于提供 RESTful API,而前端通过 AJAX 请求与后端交互。在这种架构下,如何对用户进行 认证(Authentication)授权(Authorization) 成为了系统设计中的核心问题。

Spring Security 是 Spring 框架中用于构建安全系统的模块,它不仅提供了强大的安全机制,还支持灵活的自定义配置。本文将围绕 鉴权失败和成功时的行为、需要拦截的路径配置、以及具体的代码实现方式 展开讲解,并结合多个实际例子,帮助你深入理解 Spring Security 在前后端分离项目中的使用。

本文将从以下三个方面详细展开:

  1. 基础概念:包括认证、授权、关键组件及其作用;
  2. 配置流程:从登录请求到权限控制的完整流程;
  3. 实战示例:JWT 认证、异常处理、多种路径保护等具体实现;

无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供一套完整的解决方案。


一、基础概念详解

Spring Security 的核心在于 认证(Authentication)授权(Authorization)

1. SecurityFilterChain(安全过滤器链)

  • 作用:定义 HTTP 请求的安全策略。
  • 位置:通常在配置类中通过 @Bean 注解创建。
  • 示例
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/public/**").permitAll()
            .anyRequest().authenticated()
        )
        .formLogin(withDefaults())
        .httpBasic(withDefaults());

    return http.build();
}

可以定义多个 SecurityFilterChain 来处理不同路径的权限策略。


2. UserDetailsService(用户详情服务)

  • 作用:负责加载用户信息(用户名、密码、权限)。
  • 实现方式
    • 内存方式(适合测试):
@Bean
public UserDetailsService userDetailsService() {
    UserDetails user = User.withUsername("user")
                           .password(passwordEncoder().encode("123456"))
                           .roles("USER")
                           .build();
    return new InMemoryUserDetailsManager(user);
}
  • 数据库方式(推荐生产环境):
@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("用户不存在"));
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                getAuthorities(user.getRoles()));
    }

    private Collection<? extends GrantedAuthority> getAuthorities(Collection<Role> roles) {
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
                .collect(Collectors.toList());
    }
}

3. PasswordEncoder(密码编码器)

  • 作用:加密存储密码,并验证密码是否匹配。
  • 常用实现
    • BCryptPasswordEncoder(推荐)
    • NoOpPasswordEncoder(不加密,仅用于开发阶段)
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

4. JwtRequestFilter(自定义 JWT 过滤器)

  • 作用:拦截每个请求,解析 Token 并设置当前用户认证信息。
  • 实现方式
@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        String token = extractToken(request);

        if (token != null && jwtTokenUtil.validateToken(token)) {
            String username = jwtTokenUtil.getUsernameFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

    private String extractToken(HttpServletRequest request) {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            return header.substring(7);
        }
        return null;
    }
}

5. AuthenticationEntryPoint(鉴权失败处理器)

  • 作用:当用户未认证访问受保护资源时触发该处理器。
  • 实现方式
@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);
        response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"认证失败,请重新登录\"}");
    }
}

6. AccessDeniedHandler(权限不足处理器)

  • 作用:当用户已认证但没有访问权限时触发。
  • 实现方式
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.getWriter().write("{\"error\": \"Forbidden\", \"message\": \"你没有权限访问该资源\"}");
    }
}

二、配置流程详解

成功时的流程:

  1. 用户发送登录请求 → /api/auth/login
  2. 后端验证用户名密码,生成 JWT Token 并返回给前端
  3. 前端保存 Token(如 localStorage)
  4. 后续请求携带 Token(放在 Header 中)
  5. 后端通过自定义 JWT 过滤器解析 Token 并设置认证信息
  6. 用户访问受保护资源成功

失败时的流程:

  1. Token 缺失或格式错误 → 返回 401 Unauthorized
  2. Token 已过期或签名无效 → 返回 401 Unauthorized
  3. 用户没有权限访问某资源 → 返回 403 Forbidden

三、实战示例详解

示例 1:JWT Token 工具类(完整版)

@Component
public class JwtTokenUtil {

    private static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; // 5小时
    private String secret = "your-secret-key-here";

    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(secret)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails.getUsername());
    }

    private String doGenerateToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}

示例 2:登录接口

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserDetailsService userDetailsService;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) throws Exception {
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())
            );
        } catch (AuthenticationException e) {
            throw new Exception("用户名或密码错误");
        }

        final UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername());
        final String token = jwtTokenUtil.generateToken(userDetails);

        return ResponseEntity.ok().header("Authorization", "Bearer " + token).build();
    }
}

示例 3:多种路径保护方式

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(AbstractHttpConfigurer::disable)
        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
        .exceptionHandling(exceptions -> exceptions
            .authenticationEntryPoint(authenticationEntryPoint)
            .accessDeniedHandler(accessDeniedHandler))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/public/**").permitAll()
            .requestMatchers("/api/admin/**").hasRole("ADMIN")
            .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
            .anyRequest().authenticated()
        );

    return http.build();
}

结语

本文从 Spring Security 的基础概念出发,详细介绍了认证与授权的核心组件,并结合多个实战示例展示了 JWT 的集成、路径保护、异常处理等常见场景的实现方式。

<think>嗯,用户需要Java+Vue前后端分离项目的架构示例,结合之前的引用内容来看,ta可能是个刚接触前后端分离Java开发者。引用[1]提到用户对前后端分离"两眼一抹黑",引用[5]详细描述了技术选型优势,这些都很关键。先拆解问题核心:用户要的是"项目架构示例",重点在技术栈组成和交互逻辑。引用[2][3]提供了后端部署细节,引用[4][5]则是前端配置范例,正好能组合成完整方案。架构设计上需要突出分层思想:后端SpringBoot构建RESTAPI(引用[2]提到的SpringMVC优化),前端Vue通过axios交互(引用[4]的全局配置)。部署部分参考引用[3]的宝塔部署经验,这个对初学者很实用。特别注意用户可能存在的认知盲区:前后端分离本质是职责解耦。引用[5]强调的"后端无需解析前端页面"这点要用示例代码具象化,比如展示后端Controller如何返回纯JSON。安全措施要提醒,引用[4]显示的axios配置没有CSRF防护,实际项目需要补充。路由部分参考引用[5]的按需加载特性,用动态import()演示性能优化。最后生成的示例必须包含可运行的代码片段,用户显然需要能快速验证的脚手架。相关问题的设计要覆盖用户后续可能遇到的工程化难点,比如跨域配置这种高频坑点。</think> 以下是基于Java和Vue的前后端分离项目架构示例及关键实现细节: --- ### **项目架构设计** #### **1. 后端架构(Java - Spring Boot)** ```java // 示例:用户管理模块 @RestController @RequestMapping("/api/user") public class UserController { @Autowired private UserService userService; // 返回JSON数据,无需视图渲染 @GetMapping("/list") public ResponseEntity<List<User>> getUsers() { return ResponseEntity.ok(userService.getAllUsers()); // [^2] } } ``` **技术栈**: - **Spring Boot 3.x**:快速启动RESTful API服务 - **Spring Security + JWT**:接口(如`Authorization: Bearer xxx`) - **Spring Data JPA / MyBatis**:数据库操作 - **Maven/Gradle**:依赖管理 - **端口配置**:如`server.port=8080`(需前端axios调用端口一致)[^3] --- #### **2. 前端架构(Vue 3)** ```javascript // src/api/user.js - 封装axios请求 import axios from 'axios'; export const getUserList = () => { return axios.get('/api/user/list', { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); // [^4][^5] } ``` ```vue <!-- src/views/User.vue --> <template> <el-table :data="users"> <el-table-column prop="name" label="姓名" /> </el-table> </template> <script> import { getUserList } from '@/api/user'; export default { data() { return { users: [] } }, async mounted() { const res = await getUserList(); this.users = res.data; // 绑定后端数据 } } </script> ``` **技术栈**: - **Vue 3 + Vue Router + Pinia**:核心框架状态管理 - **Element Plus / Ant Design Vue**:UI组件库[^5] - **Axios**:封装HTTP请求(需配置反向代理) - **Vite**:项目构建工具(替代Webpack)[^5] --- #### **3. 前后端交互流程** ```mermaid sequenceDiagram 前端->>后端: HTTP请求(GET /api/user/list) 后端->>数据库: 查询数据 后端-->>前端: 返回JSON格式数据 前端->>前端: 数据渲染到Vue组件 ``` --- ### **关键配置详解** 1. **跨域解决**(开发环境): ```javascript // vite.config.js export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', // Spring Boot地址 changeOrigin: true } } } }) // [^5] ``` 2. **生产部署**: - **后端**:打包为JAR部署至云服务器(如`java -jar app.jar`)[^3] - **前端**:执行`npm run build`生成静态文件,由Nginx托管 - **Nginx配置**: ``` location / { root /var/www/dist; # Vue编译后的目录 try_files $uri $uri/ /index.html; # 支持Vue路由 } location /api { proxy_pass http://127.0.0.1:8080; # 反向代理API请求 } // [^3] ``` --- ### **分离优势总结** | **层面** | **前 端** | **后 端** | |----------------|-----------------------------------|-------------------------------| | **职责分离** | 页面渲染、用户交互 | 业务逻辑、数据安全、接口提供 | | **技术自由** | 可独立升级框架版本 | 专注Java生态优化 | | **部署独立** | 静态资源CDN加速 | 微服务集群部署 | | **协作效率** | 并行开发(前端模拟接口/Mock.js) | 提供Swagger接口文档 | [^1][^5] --- ### **学习资源推荐** 1. [Vue官方文档](https://cn.vuejs.org/) - 最佳入门指南[^1] 2. [Spring Boot REST API教程](https://spring.io/guides/gs/rest-service/) 3. [Element Plus组件库](https://element-plus.org/) 4. [Axios拦截器封装示例](https://axios-http.com/docs/interceptors) ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值