Spring Security+JWT+Vue实现一个前后端分离无状态认证Demo
发布日期:2021-05-19 16:35:23 浏览次数:23 分类:精选文章

本文共 8898 字,大约阅读时间需要 29 分钟。

Spring Security + JWT 实现认证系统

本文将详细介绍如何使用 Spring Security 结合 JWT 实现认证系统的搭建,包含后端 API 接口开发、前端实现以及相关技术要点分享。


后端架构

1. 依赖管理

  • Spring Boot: 用于快速构建后端服务。
  • Spring Security: 提供安全认证功能。
  • JWT: 用于令牌生成与验证。
  • JAX-BIND: 用于imeline JSON 序列化。

此外,为确保兼容性,需在 Java 11 启用以下依赖:

com.sun.xml.bind
jaxb-api
2.3.0

2. User 接口定义

将用户信息扩展 LinkedHashMap 实现 UserDetails 接口:

public class User implements UserDetails {
private String username;
private String password;
private Boolean rememberMe;
private String verifyCode;
private String power;
private Long expirationTime;
private List
authorities;
@Override
public Collection
getAuthorities() {
return authorities;
}
// 省略 get/set 方法...
}

3. JWT 认证工具

TokenAuthenticationHelper 负责令牌验证与生成:

public class TokenAuthenticationHelper {
private static final long EXPIRATION_TIME = 7200000;
private static final int COOKIE_EXPIRATION_TIME = 1296000;
private static final String SECRET_KEY = "ThisIsASpringSecurityDemo";
public static void addAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException {
// 生成并写入令牌
String jwt = Jwts.builder()
.setSubject(authResult.getName())
.claim("authorities", new StringBuffer())
.forEach(authority ->
authorBuffer.append(authority.getAuthority() + ",")
)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
Cookie cookie = new Cookie("COOKIE-TOKEN", jwt);
cookie.setHttpOnly(true).setPath("/").setMaxAge(COOKIE_EXPIRATION_TIME);
response.addCookie(cookie);
// 向前端返回详细信息
LoginResultDetails loginResultDetails = new LoginResultDetails();
ResultDetails resultDetails = new ResultDetails();
resultDetails.setStatus(200).setSuccess(true)
.setTimestamp(new LocalDateTime())
.setMessage("登录成功!");
User user = new User();
user.setUsername(authResult.getName());
user.setPower(authorities.toString());
user.setExpirationTime(System.currentTimeMillis() + EXPIRATION_TIME);
loginResultDetails.setResultDetails(resultDetails)
.setStatus(200)
.setUser(user);
response.setContentType("application/json");
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getWriter(), loginResultDetails);
}
// 定义其他方法(如 getAuthentication...)
}

JWT 过滤器配置

1. 登录过滤器(JwtLoginFilter)

负责处理 HTTP 请求的登录逻辑:

public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {
private LoginCountService loginCountService;
private VerifyCodeService verifyCodeService;
public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager, VerifyCodeService verifyCodeService, LoginCountService loginCountService) {
super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
this.loginCountService = loginCountService;
setAuthenticationManager(authenticationManager);
this.verifyCodeService = verifyCodeService;
}
@Override
protected Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
loginCountService.judgeLoginCount(request);
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
verifyCodeService.verify(request.getSession().getId(), user.getVerifyCode());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
user.getUsername(),
user.getPassword(),
user.getAuthorities()
);
token.setDetails(new LoginDetails(user.getRememberMe(), user.getVerifyCode()));
return getAuthenticationManager().authenticate(token);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
loginCountService.cleanLoginCount(request);
TokenAuthenticationHelper.addAuthentication(request, response, authResult);
}
// 定义其他方法(如 unsuccessfulAuthentication...)
}

2. 遍历过滤器(JwtAuthenticationFilter)

负责处理每个请求,验证令牌有效性:

public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {
try {
Authentication authentication = TokenAuthenticationHelper.getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (ExpiredJwtException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token expired, login expired");
}
}
}

Spring Security 配置

1. 安全配置(WebSecurityConfig)

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
private VerifyCodeService verifyCodeService;
private LoginCountService loginCountService;
public WebSecurityConfig(VerifyCodeService verifyCodeService, LoginCountService loginCountService) {
this.verifyCodeService = verifyCodeService;
this.loginCountService = loginCountService;
}
// 全局 CORS 配置
@Bean
public CorsConfigurationSource corsConfigurationSource() {
List
allowedOriginsUrl = new ArrayList<>();
allowedOriginsUrl.add("http://localhost:8080");
allowedOriginsUrl.add("http://127.0.0.1:8080");
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.setAllowedOrigins(allowedOriginsUrl);
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(permitAllMappings).permitAll()
.antMatchers("/api/user/**", "/api/data", "/api/logout").permitAll()
.hasAnyAuthority(USER, ADMIN).antMatchers("/api/admin/**").hasAuthority(ADMIN)
.anyRequest().authenticated().and().csrf().tokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
http.addFilterBefore(JwtLoginFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(JwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.securityFilterChain.addFilterAfter(new CsrfFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(WebSecurity web) throws Exception {
super.configure(web);
}
// 在内存中配置用户
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
return new DaoAuthenticationProvider();
}
}

前端实现

1. Vue 项目搭建

基于 Vue CLI 创建项目,并配置如下:

// vue.config.js
import Vue from 'vue'
import ElementUI from 'element-ui'
import mavonEditor from 'mavon-editor'
import VueCookies from 'vue-cookies'
import axios from 'axios'
Vue.use(ElementUI)
Vue.use(mavonEditor)
Vue.use(VueCookies)
Vue.config.productionTip = false
// 配置后端 API 地址
Vue.prototype.SERVER_API_URL = 'http://127.0.0.1:8088/api'
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

2. 引入 Element UI 组件

在主应用文件中添加配置:

// main.js
import axios from 'axios'
axios.defaults.withCredentials = true
axios.defaults.headers.common['X-CSRF-TOKEN'] = (this.$cookies.get('XSRF-TOKEN') || '')
Vue.prototype.$axios = axios
Vue.use(VueCookies)

3. 跨域配置

在 vue.config.js 中设置代理:

module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://127.0.0.1:8088',
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}

注意事项

  • 请求头配置:AJAX 请求中需携带 X-CSRF-TOKENCredentials: "include",否则可能导致跨域攻击。

  • 令牌验证:确保前端请求中携带有效的令牌信息,后端需正确解析令牌并验证权限。

  • 权限管理:根据用户角色设置权限,确保资源访问安全。


  • 以上内容形成了一个完整的 Spring Security + JWT 认证系统实现方案,涵盖后端 API 开发、前端配置以及相关技术关键点。如有疑问或建议,欢迎在评论区留言交流!

    上一篇:再见,Postman...
    下一篇:15款开源的Spring项目脚手架

    发表评论

    最新留言

    路过,博主的博客真漂亮。。
    [***.116.15.85]2025年05月07日 02时04分51秒