• 技术应用:使用Spring Boot和Vue.js构建前后端分离的JWT认证应用


    导语:
    在当前的软件开发领域,前后端分离的架构已经成为了主流。结合Spring Boot和Vue.js这两个流行的框架,我们可以轻松地构建出强大而现代的Web应用。本文将深入探讨如何使用Spring Boot和Vue.js构建一个前后端分离的JWT认证应用,以实现安全的用户认证和授权。


    引言:
    在当今的软件开发中,安全性是至关重要的。用户认证和授权是确保Web应用程序安全性的重要组成部分。JWT(JSON Web Token)作为一种轻量级、安全的身份验证机制,被广泛应用于前后端分离的Web应用中。本文将介绍如何使用Spring Boot和Vue.js构建一个基于JWT的前后端分离应用,实现用户的认证和授权。

    JWT简介:
    JWT(JSON Web Token)是一种开放标准(RFC 7519),定义了一种简洁的、自包含的方式用于在各方之间传递信息。JWT通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。JWT的工作流程如下:

    • 用户进行身份认证,将认证信息发送给服务端。
    • 服务端校验认证信息,生成JWT,并将其返回给客户端。
    • 客户端将JWT保存在本地,每次请求时将JWT发送给服务端。
    • 服务端校验JWT的合法性,完成用户身份认证。

    后端(Spring Boot)

    1. 创建Spring Boot项目
    项目结构:
    src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               ├── config
    │   │               │   ├── SecurityConfig.java
    │   │               │   └── WebConfig.java
    │   │               ├── controller
    │   │               │   └── AuthController.java
    │   │               ├── exception
    │   │               │   └── CustomExceptionHandler.java
    │   │               ├── filter
    │   │               │   └── JwtRequestFilter.java
    │   │               ├── model
    │   │               │   └── User.java
    │   │               ├── service
    │   │               │   └── CustomUserDetailsService.java
    │   │               └── util
    │   │                   └── JwtUtil.java
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── com
                └── example
                    └── demo
                        └── DemoApplicationTests.java
    
    2. 添加依赖

    pom.xml文件中添加Spring Security、JWT和其他必要的依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.9.1version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>
    
    3. 配置文件

    src/main/resources/application.properties中添加配置:

    spring.security.user.name=admin
    spring.security.user.password=admin
    
    4. JWT工具类

    创建JwtUtil类用于生成和验证JWT:

    package com.example.demo.util;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Function;
    
    @Component
    public class JwtUtil {
    
        @Value("${jwt.secret}")
        private String secret;
    
        public String extractUsername(String token) {
            return extractClaim(token, Claims::getSubject);
        }
    
        public Date extractExpiration(String token) {
            return extractClaim(token, Claims::getExpiration);
        }
    
        public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
            final Claims claims = extractAllClaims(token);
            return claimsResolver.apply(claims);
        }
    
        private Claims extractAllClaims(String token) {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }
    
        private Boolean isTokenExpired(String token) {
            return extractExpiration(token).before(new Date());
        }
    
        public String generateToken(String username) {
            Map<String, Object> claims = new HashMap<>();
            return createToken(claims, username);
        }
    
        private String createToken(Map<String, Object> claims, String subject) {
            return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                    .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
                    .signWith(SignatureAlgorithm.HS256, secret).compact();
        }
    
        public Boolean validateToken(String token, String username) {
            final String tokenUsername = extractUsername(token);
            return (tokenUsername.equals(username) && !isTokenExpired(token));
        }
    }
    
    5. 认证控制器

    创建AuthController类处理认证请求:

    package com.example.demo.controller;
    
    import com.example.demo.service.CustomUserDetailsService;
    import com.example.demo.util.JwtUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    public class AuthController {
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private JwtUtil jwtUtil;
    
        @Autowired
        private CustomUserDetailsService userDetailsService;
    
        @PostMapping("/authenticate")
        public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthRequest authRequest) {
            try {
                Authentication authentication = authenticationManager.authenticate(
                        new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
                );
                final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername());
                final String jwt = jwtUtil.generateToken(userDetails.getUsername());
                return ResponseEntity.ok(new AuthResponse(jwt));
            } catch (BadCredentialsException e) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Incorrect username or password");
            }
        }
    
        static class AuthRequest {
            private String username;
            private String password;
    
            public String getUsername() {
                return username;
            }
    
            public void setUsername(String username) {
                this.username = username;
            }
    
            public String getPassword() {
                return password;
            }
    
            public void setPassword(String password) {
                this.password = password;
            }
        }
    
        static class AuthResponse {
            private String jwt;
    
            public AuthResponse(String jwt) {
                this.jwt = jwt;
            }
    
            public String getJwt() {
                return jwt;
            }
        }
    }
    
    6. 自定义用户详细信息服务

    创建CustomUserDetailsService类:

    package com.example.demo.service;
    
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    
    @Service
    public class CustomUserDetailsService implements UserDetailsService {
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 这里使用硬编码用户。生产环境中应该使用数据库
            if ("admin".equals(username)) {
                return new User("admin", "admin", new ArrayList<>());
            } else {
                throw new UsernameNotFoundException("User not found with username: " + username);
            }
        }
    }
    
    7. JWT过滤器

    创建JwtRequestFilter类:

    package com.example.demo.filter;
    
    import com.example.demo.util.JwtUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Component
    public class JwtRequestFilter extends OncePerRequestFilter {
    
        @Autowired
        private JwtUtil jwtUtil;
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                throws ServletException, IOException {
            final String authorizationHeader = request.getHeader("Authorization");
    
            String username = null;
            String jwt = null;
    
            if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
                jwt = authorizationHeader.substring(7);
                username = jwtUtil.extractUsername(jwt);
            }
    
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
    
                if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                }
            }
    
            chain.doFilter(request, response);
        }
    }
    
    8. 自定义异常处理类

    创建CustomExceptionHandler类:

    package com.example.demo.exception;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @ControllerAdvice
    @RestController
    public class CustomExceptionHandler extends BasicAuthenticationEntryPoint {
    
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
                throws IOException {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
        }
    
        @ExceptionHandler(AuthenticationException.class)
        public final ResponseEntity<Object> handleAuthenticationException(AuthenticationException ex, HttpServletRequest request) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token is expired or invalid");
        }
    
        @Override
        public void afterPropertiesSet() {
            setRealmName("my-app");
            super.afterPropertiesSet();
        }
    }
    
    9. Spring Security配置类

    创建SecurityConfig类:

    package com.example.demo.config;
    
    import com.example.demo.filter.JwtRequestFilter;
    import com.example.demo.service.CustomUserDetailsService;
    import com.example.demo.exception.CustomExceptionHandler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.password.NoOpPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private CustomUserDetailsService customUserDetailsService;
    
        @Autowired
        private JwtRequestFilter jwtRequestFilter;
    
        @Autowired
        private CustomExceptionHandler customExceptionHandler;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(customUserDetailsService);
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();  // 用于示例,生产中请使用更强的加密方式
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/authenticate").permitAll()  // 登录接口允许所有人访问
                    .anyRequest().authenticated()  // 其他接口需要认证
                    .and()
                    .exceptionHandling().authenticationEntryPoint(customExceptionHandler)
                    .and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);  // 使用无状态的会话
    
            // 添加JWT过滤器
            http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }
    

    前端(Vue.js

    1. 设置Axios实例

    src/services/http.service.js文件中设置Axios实例:

    import axios from 'axios';
    import AuthService from './auth.service';
    import router from '../router';  // Vue Router实例
    
    const API_URL = 'http://localhost:8081/';  // Spring Boot应用运行的地址
    
    const http = axios.create({
      baseURL: API_URL,
      headers: {
        'Content-Type': 'application/json'
      }
    });
    
    // 请求拦截器
    http.interceptors.request.use(
      config => {
        const user = AuthService.getCurrentUser();
        if (user && user.jwt) {
          config.headers['Authorization'] = 'Bearer ' + user.jwt;
        }
        return config;
      },
      error => {
        return Promise.reject(error);
      }
    );
    
    // 响应拦截器
    http.interceptors.response.use(
      response => {
        return response;
      },
      error => {
        if (error.response && error.response.status === 401) {
          // Token过期或无效,执行登出操作并重定向到登录页面
          AuthService.logout();
          router.push('/login');
        }
        return Promise.reject(error);
      }
    );
    
    export default http;
    
    2. 创建认证服务

    src/services/auth.service.js中创建认证服务:

    import http from './http.service';
    
    class AuthService {
      login(user) {
        return http
          .post('authenticate', {
            username: user.username,
            password: user.password
          })
          .then(response => {
            if (response.data.jwt) {
              localStorage.setItem('user', JSON.stringify(response.data));
            }
            return response.data;
          });
      }
    
      logout() {
        localStorage.removeItem('user');
      }
    
      getCurrentUser() {
        return JSON.parse(localStorage.getItem('user'));
      }
    }
    
    export default new AuthService();
    
    3. 设置Vue Router

    src/router/index.js中设置Vue Router:

    import Vue from 'vue';
    import Router from 'vue-router';
    import Login from '../views/Login.vue';
    import Profile from '../views/Profile.vue';
    
    Vue.use(Router);
    
    const router = new Router({
      mode: 'history',
      routes: [
        {
          path: '/login',
          name: 'login',
          component: Login
        },
        {
          path: '/profile',
          name: 'profile',
          component: Profile
        }
      ]
    });
    
    export default router;
    
    4. 创建登录组件

    src/views/Login.vue中创建登录组件:

    
    
    
    
    5. 创建Profile组件

    src/views/Profile.vue中创建Profile组件:

    
    
    
    
    6. 创建主入口文件

    src/main.js中创建主入口文件:

    import Vue from 'vue';
    import App from './App.vue';
    import router from './router';
    
    Vue.config.productionTip = false;
    
    new Vue({
      router,
      render: h => h(App)
    }).$mount('#app');
    

    总结:
    通过本文的介绍,读者将了解如何使用Spring Boot和Vue.js构建一个前后端分离的JWT认证应用。JWT作为一种轻量级的身份验证机制,在前后端分离的应用中具有广泛的应用前景。希望本文能够帮助读者更好地理解JWT认证原理,并在实际项目中得以应用。

    通过以上步骤的实现,我们成功地构建了一个完整的前后端分离的JWT认证应用。在这个应用中,Spring Boot后端负责处理用户认证和授权,Vue.js前端负责用户界面的呈现和与后端的交互。通过JWT的使用,我们实现了安全的用户认证和授权机制,确保了应用的安全性和可靠性。

    通过本文的学习,我们不仅掌握了JWT认证的基本原理和工作流程,还深入了解了如何在Spring Boot和Vue.js项目中实现JWT认证。同时,我们还学习了如何使用Spring Security进行安全配置,在后端保护接口的安全性。在前端方面,我们使用了Axios实现了HTTP请求的发送和响应处理,并结合Vue Router实现了页面导航和路由控制。

    总的来说,本文所涉及的内容不仅可以帮助我们构建安全可靠的前后端分离应用,还对我们理解和掌握现代Web开发中的安全机制具有重要意义。希望本文能够对读者有所帮助,也欢迎大家在实践中进一步探索和应用这些技术。

    这就是关于使用Spring Boot和Vue.js构建前后端分离的JWT认证应用的全部内容,希望本文能够对您有所帮助。感谢阅读!

  • 相关阅读:
    Item 38: Be aware of varying thread handle destructor behavior.
    Zookeeper 命令使用和数据说明
    【战斗吧,青春!】
    15.模型评估和选择问题
    elementui table超出两行显示...鼠标已入tip显示
    Unity下tga和png格式图片打包成AB包大小和加载速度测试
    【ArcGIS超级工具】基于ArcPy的矢量数据批量自动化入库工具
    46LVS+Keepalived群集
    双点重发布&路由策略实验
    安卓毕业设计app项目基于Uniapp+SSM实现的家庭账单财务APP
  • 原文地址:https://blog.csdn.net/wangjiansui/article/details/139595315