技术应用:使用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和其他必要的依赖:

xml 复制代码
<dependencies>
    <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>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
3. 配置文件

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

properties 复制代码
spring.security.user.name=admin
spring.security.user.password=admin
4. JWT工具类

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

java 复制代码
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类处理认证请求:

java 复制代码
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类:

java 复制代码
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类:

java 复制代码
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类:

java 复制代码
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 Basic

AuthenticationEntryPoint {

    @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类:

java 复制代码
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实例:

javascript 复制代码
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中创建认证服务:

javascript 复制代码
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:

javascript 复制代码
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中创建登录组件:

vue 复制代码
<template>
  <div>
    <h1>Login</h1>
    <form @submit.prevent="login">
      <div>
        <label for="username">Username</label>
        <input type="text" v-model="username" required>
      </div>
      <div>
        <label for="password">Password</label>
        <input type="password" v-model="password" required>
      </div>
      <button type="submit">Login</button>
    </form>
  </div>
</template>

<script>
import AuthService from '../services/auth.service';

export default {
  data() {
    return {
      username: '',
      password: ''
    };
  },
  methods: {
    login() {
      AuthService.login({ username: this.username, password: this.password }).then(
        () => {
          this.$router.push('/profile');
        },
        error => {
          console.error('Login failed', error);
        }
      );
    }
  }
};
</script>
5. 创建Profile组件

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

vue 复制代码
<template>
  <div>
    <h1>Profile</h1>
    <p>Welcome to your profile!</p>
  </div>
</template>

<script>
export default {
  name: 'Profile'
};
</script>
6. 创建主入口文件

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

javascript 复制代码
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认证应用的全部内容,希望本文能够对您有所帮助。感谢阅读!

相关推荐
代码之光_19808 分钟前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
十一吖i8 分钟前
前端将后端返回的文件下载到本地
vue.js·elementplus
光影少年9 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
ajsbxi13 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
鹿屿二向箔37 分钟前
基于SSM(Spring + Spring MVC + MyBatis)框架的咖啡馆管理系统
spring·mvc·mybatis
颜淡慕潇1 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
戴眼镜的猴1 小时前
Spring Boot的过滤器与拦截器的区别
spring boot
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
NoneCoder1 小时前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发
尘浮生2 小时前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea