导语:
在当前的软件开发领域,前后端分离的架构已经成为了主流。结合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认证应用的全部内容,希望本文能够对您有所帮助。感谢阅读!