技术应用:使用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.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中添加配置:

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中创建登录组件:

<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组件:

<template>
  <div>
    <h1>Profile</h1>
    <p>Welcome to your profile!</p>
  </div>
</template>

<script>
export default {
  name: 'Profile'
};
</script>
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认证应用的全部内容,希望本文能够对您有所帮助。感谢阅读!

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hugo_Hoo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值