用Spring Security, JWT, Vue实现一个先后端分离无状态认证Demo

简介

完整代码 https://github.com/PuZhiweizuishuai/SpringSecurity-JWT-Vue-Deomcss

运行展现

 

后端

主要展现 Spring Security 与 JWT 结合使用构建后端 API 接口。html

主要功能包括登录(如何在 Spring Security 中添加验证码登录),查找,建立,删除并对用户权限进行区分等等。前端

ps:因为只是 Demo,因此没有调用数据库,以上所说增删改查均在 HashMap 中完成。vue

前端

展现如何使用 Vue 构建前端后与后端的配合,包括跨域的设置,前端登录拦截java

并实现 POST,GET,DELETE 请求。包括如何在 Vue 中使用后端的 XSRF-TOKEN 防范 CSRF 攻击ios

技术栈

组件 技术
前端 Vue.js 2
后端 (REST API) SpringBoot (Java)
安全 Token Based (Spring Security, JJWT, CSRF)
前端脚手架 vue-cli3, Webpack, NPM
后端构建 Maven

实现细节

后端搭建

基础配置

建立 Spring boot 项目,添加 JJWT 和 Spring Security 的项目依赖,这个很是简单,有不少的教程都有块内容,惟一须要注意的是,若是你使用的 Java 版本是 11,那么你还须要添加如下依赖,使用 Java8 则不须要。git

     <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>

要使用 Spring Security 实现对用户的权限控制,首先须要实现一个简单的 User 对象实现 UserDetails 接口,UserDetails 接口负责提供核心用户的信息,若是你只须要用户登录的帐号密码,不须要其它信息,如验证码等,那么你能够直接使用 Spring Security 默认提供的 User 类,而不须要本身实现。github

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<GrantedAuthority> authorities; /** * 省略其它的 get set 方法 */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
User

这个就是咱们要使用到的 User 对象,其中包含了 记住我,验证码等登录信息,由于 Spring Security 整合 Jwt 本质上就是用本身自定义的登录过滤器,去替换 Spring Security 原生的登录过滤器,这样的话,原生的记住我功能就会没法使用,因此我在 User 对象里添加了记住个人信息,用来本身实现这个功能。web

JWT 令牌认证工具

首先咱们来新建一个 TokenAuthenticationHelper 类,用来处理认证过程当中的验证和请求ajax

public class TokenAuthenticationHelper { /** * 未设置记住我时 token 过时时间 * */
    private static final long EXPIRATION_TIME = 7200000; /** * 记住我时 cookie token 过时时间 * */
    private static final int COOKIE_EXPIRATION_TIME = 1296000; private static final String SECRET_KEY = "ThisIsASpringSecurityDemo"; public static final String COOKIE_TOKEN = "COOKIE-TOKEN"; public static final String XSRF = "XSRF-TOKEN"; /** * 设置登录成功后令牌返回 * */
    public static void addAuthentication(HttpServletRequest request,  HttpServletResponse response, Authentication authResult) throws IOException { // 获取用户登录角色
        Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities(); // 遍历用户角色
        StringBuffer stringBuffer = new StringBuffer(); authorities.forEach(authority -> { stringBuffer.append(authority.getAuthority()).append(","); }); long expirationTime = EXPIRATION_TIME; int cookExpirationTime = -1; // 处理登录附加信息
        LoginDetails loginDetails = (LoginDetails) authResult.getDetails(); if (loginDetails.getRememberMe() != null && loginDetails.getRememberMe()) { expirationTime = COOKIE_EXPIRATION_TIME * 1000; cookExpirationTime = COOKIE_EXPIRATION_TIME; } String jwt = Jwts.builder() // Subject 设置用户名
 .setSubject(authResult.getName()) // 设置用户权限
                .claim("authorities", stringBuffer) // 过时时间
                .setExpiration(new Date(System.currentTimeMillis() + expirationTime)) // 签名算法
 .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); Cookie cookie = new Cookie(COOKIE_TOKEN, jwt); cookie.setHttpOnly(true); cookie.setPath("/"); cookie.setMaxAge(cookExpirationTime); response.addCookie(cookie); // 向前端写入数据
        LoginResultDetails loginResultDetails = new LoginResultDetails(); ResultDetails resultDetails = new ResultDetails(); resultDetails.setStatus(HttpStatus.OK.value()); resultDetails.setMessage("登录成功!"); resultDetails.setSuccess(true); resultDetails.setTimestamp(LocalDateTime.now()); User user = new User(); user.setUsername(authResult.getName()); user.setPower(stringBuffer.toString()); user.setExpirationTime(System.currentTimeMillis() + expirationTime); loginResultDetails.setResultDetails(resultDetails); loginResultDetails.setUser(user); loginResultDetails.setStatus(200); response.setContentType("application/json; charset=UTF-8"); PrintWriter out = response.getWriter(); out.write(new ObjectMapper().writeValueAsString(loginResultDetails)); out.flush(); out.close(); } /** * 对请求的验证 * */
    public static Authentication getAuthentication(HttpServletRequest request) { Cookie cookie = WebUtils.getCookie(request, COOKIE_TOKEN); String token = cookie != null ? cookie.getValue() : null; if (token != null) { Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); // 获取用户权限
            Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get("authorities").toString().split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); String userName = claims.getSubject(); if (userName != null) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userName, null, authorities); usernamePasswordAuthenticationToken.setDetails(claims); return usernamePasswordAuthenticationToken; } return null; } return null; } }
TokenAuthenticationHelper
  1. addAuthentication 方法负责返回登录成功的信息,使用 HTTP Only 的 Cookie 能够有效防止 XSS 攻击。

  2. 登录成功后返回用户的权限,用户名,登录过时时间,能够有效的帮助前端构建合适的用户界面。

  3. getAuthentication 方法负责对用户的其它请求进行验证,若是用户的 JWT 解析正确,则向 Spring Security 返回 usernamePasswordAuthenticationToken 用户名密码验证令牌,告诉 Spring Security 用户所拥有的权限,并放到当前的 Context 中,而后执行过滤链使请求继续执行下去。

至此,咱们的基本登录与验证所须要的方法就写完了

ps:其中的 LoginResultDetails 类和 ResultDetails 请看项目源码,篇幅所限,此处不在赘述。

JWT 过滤器配置

众所周知,Spring Security 是借助一系列的 Servlet Filter 来来实现提供各类安全功能的,因此咱们要使用 JWT 就须要本身实现两个和 JWT 有关的过滤器

  1. 一个是用户登陆的过滤器,在用户的登陆的过滤器中校验用户是否登陆成功,若是登陆成功,则生成一个 token 返回给客户端,登陆失败则给前端一个登陆失败的提示。

  2. 第二个过滤器则是当其余请求发送来,校验 token 的过滤器,若是校验成功,就让请求继续执行。

这两个过滤器,咱们分别来看,先看第一个:

在项目下新建一个包,名为 filter, 在 filter 下新建一个类名为 JwtLoginFilter,并使其继承 AbstractAuthenticationProcessingFilter 类,这个类是一个基于浏览器的基于 HTTP 的身份验证请求的抽象处理器。

public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter { private final VerifyCodeService verifyCodeService; private final LoginCountService loginCountService; /** * @param defaultFilterProcessesUrl 配置要过滤的地址,即登录地址 * @param authenticationManager 认证管理器,校验身份时会用到 * @param loginCountService */
    public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager, VerifyCodeService verifyCodeService, LoginCountService loginCountService) { super(new AntPathRequestMatcher(defaultFilterProcessesUrl)); this.loginCountService = loginCountService; // 为 AbstractAuthenticationProcessingFilter 中的属性赋值
 setAuthenticationManager(authenticationManager); this.verifyCodeService = verifyCodeService; } /** * 提取用户帐号密码进行验证 * */ @Override public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException { // 判断是否要抛出 登录请求过快的异常
 loginCountService.judgeLoginCount(httpServletRequest); // 获取 User 对象 // readValue 第一个参数 输入流,第二个参数 要转换的对象
        User user = new ObjectMapper().readValue(httpServletRequest.getInputStream(), User.class); // 验证码验证
 verifyCodeService.verify(httpServletRequest.getSession().getId(), user.getVerifyCode()); // 对 html 标签进行转义,防止 XSS 攻击
        String username = user.getUsername(); username = HtmlUtils.htmlEscape(username); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( username, 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, ServletException { loginCountService.cleanLoginCount(request); // 登录成功
 TokenAuthenticationHelper.addAuthentication(request, response ,authResult); } /** * 登录失败回调 * */ @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { // 错误请求次数加 1
        loginCountService.addLoginCount(request, 1); // 向前端写入数据
        ErrorDetails errorDetails = new ErrorDetails(); errorDetails.setStatus(HttpStatus.UNAUTHORIZED.value()); errorDetails.setMessage("登录失败!"); errorDetails.setError(failed.getLocalizedMessage()); errorDetails.setTimestamp(LocalDateTime.now()); errorDetails.setPath(request.getServletPath()); response.setContentType("application/json; charset=UTF-8"); PrintWriter out = response.getWriter(); out.write(new ObjectMapper().writeValueAsString(errorDetails)); out.flush(); out.close(); } }
JwtLoginFilter

这个类主要有如下几个做用

  1. 自定义 JwtLoginFilter 继承自 AbstractAuthenticationProcessingFilter,并实现其中的三个默认方法,其中的 defaultFilterProcessesUrl 变量就是咱们须要设置的登录路径

  2. attemptAuthentication 方法中,咱们从登陆参数中提取出用户名密码,而后调用 AuthenticationManager.authenticate()方法去进行自动校验。

  3. 第二步若是校验成功,就会来到 successfulAuthentication 回调中,在 successfulAuthentication 方法中,使用以前已经写好的 addAuthentication 来生成 token,并使用 Http Only 的 cookie 写出到客户端。

  4. 第二步若是校验失败就会来到 unsuccessfulAuthentication 方法中,在这个方法中返回一个错误提示给客户端便可。

ps:其中的 verifyCodeService 与 loginCountService 方法与本文关系不大,其中的代码实现请看源码

惟一须要注意的就是

验证码异常须要继承 AuthenticationException 异常,

 

能够看到这是一个 Spring Security 各类异常的父类,写一个验证码异常类继承 AuthenticationException,而后直接将验证码异常抛出就好。

如下完整代码位于 com.bugaugaoshu.security.service.impl.DigitsVerifyCodeServiceImpl 类下

 @Override public void verify(String key, String code) { String lastVerifyCodeWithTimestamp = verifyCodeRepository.find(key); // 若是没有验证码,则随机生成一个
        if (lastVerifyCodeWithTimestamp == null) { lastVerifyCodeWithTimestamp = appendTimestamp(randomDigitString(verifyCodeUtil.getLen())); } String[] lastVerifyCodeAndTimestamp = lastVerifyCodeWithTimestamp.split("#"); String lastVerifyCode = lastVerifyCodeAndTimestamp[0]; long timestamp = Long.parseLong(lastVerifyCodeAndTimestamp[1]); if (timestamp + VERIFY_CODE_EXPIRE_TIMEOUT < System.currentTimeMillis()) { throw new VerifyFailedException("验证码已过时!"); } else if (!Objects.equals(code, lastVerifyCode)) { throw new VerifyFailedException("验证码错误!"); } }
DigitsVerifyCodeServiceImpl

 

异常代码在  com.bugaugaoshu.security.exception.VerifyFailedException 类下

第二个用户过滤器

public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { try { Authentication authentication = TokenAuthenticationHelper.getAuthentication(httpServletRequest); // 对用 token 获取到的用户进行校验
 SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(httpServletRequest, httpServletResponse); } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) { httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token expired,登录已过时"); } } }

这个就很简单了,将拿到的用户 Token 进行解析,若是正确,就将当前用户加入到 SecurityContext 的上下文中,授予用户权限,不然返回 Token 过时的异常

Spring Security 配置

接下来咱们来配置 Spring Security,代码以下:

@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public static String ADMIN = "ROLE_ADMIN"; public static String USER = "ROLE_USER"; private final VerifyCodeService verifyCodeService; private final LoginCountService loginCountService; /** * 开放访问的请求 */
    private final static String[] PERMIT_ALL_MAPPING = { "/api/hello", "/api/login", "/api/home", "/api/verifyImage", "/api/image/verify", "/images/**" }; public WebSecurityConfig(VerifyCodeService verifyCodeService, LoginCountService loginCountService) { this.verifyCodeService = verifyCodeService; this.loginCountService = loginCountService; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 跨域配置 */ @Bean public CorsConfigurationSource corsConfigurationSource() { // 容许跨域访问的 URL
        List<String> allowedOriginsUrl = new ArrayList<>(); allowedOriginsUrl.add("http://localhost:8080"); allowedOriginsUrl.add("http://127.0.0.1:8080"); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 设置容许跨域访问的 URL
 config.setAllowedOrigins(allowedOriginsUrl); config.addAllowedHeader("*"); config.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(PERMIT_ALL_MAPPING) .permitAll() .antMatchers("/api/user/**", "/api/data", "/api/logout") // USER 和 ADMIN 均可以访问
 .hasAnyAuthority(USER, ADMIN) .antMatchers("/api/admin/**") // 只有 ADMIN 才能够访问
 .hasAnyAuthority(ADMIN) .anyRequest() .authenticated() .and() // 添加过滤器链,前一个参数过滤器, 后一个参数过滤器添加的地方 // 登录过滤器
                .addFilterBefore(new JwtLoginFilter("/api/login", authenticationManager(), verifyCodeService, loginCountService), UsernamePasswordAuthenticationFilter.class) // 请求过滤器
                .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // 开启跨域
 .cors() .and() // 开启 csrf
 .csrf() // .disable();
 .ignoringAntMatchers(PERMIT_ALL_MAPPING) .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); } @Override public void configure(WebSecurity web) throws Exception { super.configure(web); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 在内存中写入用户数据
 auth. authenticationProvider(daoAuthenticationProvider()); //.inMemoryAuthentication(); // .withUser("user") // .password(passwordEncoder().encode("123456")) // .authorities("ROLE_USER") // .and() // .withUser("admin") // .password(passwordEncoder().encode("123456")) // .authorities("ROLE_ADMIN") // .and() // .withUser("block") // .password(passwordEncoder().encode("123456")) // .authorities("ROLE_USER") // .accountLocked(true);
 } @Bean public DaoAuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setHideUserNotFoundExceptions(false); provider.setPasswordEncoder(passwordEncoder()); provider.setUserDetailsService(new CustomUserDetailsService()); return provider; }

以上代码的注释很详细,我就很少说了,重点说一下两个地方一个是 csrf 的问题,另外一个就是 inMemoryAuthentication 在内存中写入用户的部分。

首先说 csrf 的问题:我看了看网上有不少 Spring Security 的教程,都会将 .csrf()设置为 .disable() ,这种设置虽然方便,可是不够安全,忽略了使用安全框架的初衷因此为了安全起见,我仍是开启了这个功能,顺便学习一下如何使用 XSRF-TOKEN

由于这个项目是一个 Demo,不涉及数据库部分,因此我选择了在内存中直接写入用户,网上的向内存中写入用户如上代码注释部分,这样写虽然简单,可是有一些问题,在打个断点咱们就能知道种方式调用的是 Spring Security 的是 ProviderManager 这个方法,这种方法不方便咱们抛出入用户名不存在或者其异常,它都会抛出 Bad Credentials 异常,不会提示其它错误,以下图所示。

 

 

Spring Security 为了安全考虑,会把全部的登录异常所有归结为 Bad Credentials 异常,因此为了能抛出像用户名不存在的这种异常,若是采用 Spring Security 默认的登录方式的话,能够采用像GitHub项目Vhr里的这种处理方式,可是由于这个项目使用 Jwt 替换掉了默认的登录方式,想要实现详细的异常信息抛出就比较复杂了,我找了很久也没找到比较简单且合适的方法。若是你有好的方法,欢迎分享。

最后个人解决方案是使用 Spring Security 的 DaoAuthenticationProvider 这个类来成为认证提供者,这个类实现了 AbstractUserDetailsAuthenticationProvider 这一个抽象的用户详细信息身份验证功能,查看注释咱们能够知道 AbstractUserDetailsAuthenticationProvider 提供了 A base AuthenticationProvider that allows subclasses to override and work with UserDetails objects. The class is designed to respond to UsernamePasswordAuthenticationToken authentication requests.(容许子类重写和使用 UserDetails 对象的基自己份验证提供程序。该类旨在响应 UsernamePasswordAuthenticationToken 身份验证请求。)

经过配置自定义的用户查询实现类,咱们能够直接在 CustomUserDetailsService 里抛出没有发现用户名的异常,而后再设置 hideUserNotFoundExceptions 为 false 这样就能够区别是密码错误,仍是用户名不存在的错误了,

可是这种方式仍是有一个问题,不能抛出像帐户被锁定这种异常,理论上这种功能能够继承 AbstractUserDetailsAuthenticationProvider 这个抽象类而后本身重写的登录方法来实现,我看了看好像比较复杂,一个 Demo 不必,我就放弃了。

另外听说安全信息暴露的越少越好,因此暂时就先这样吧。(算是给本身找个理由)

用户查找服务

public class CustomUserDetailsService implements UserDetailsService { private List<UserDetails> userList = new ArrayList<>(); public CustomUserDetailsService() { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); UserDetails user = User.withUsername("user").password(passwordEncoder.encode("123456")).authorities(WebSecurityConfig.USER).build(); UserDetails admin = User.withUsername("admin").password(passwordEncoder.encode("123456")).authorities(WebSecurityConfig.ADMIN).build(); userList.add(user); userList.add(admin); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { for (UserDetails userDetails : userList) { if (userDetails.getUsername().equals(username)) { // 此处我尝试过直接返回 user // 可是这样的话,只有后台服务启动后第一次登录会有效 // 推出后第二次登录会出现 Empty encoded password 的错误,致使没法登录 // 这样写就不会出现这种问题了 // 由于在第一次验证后,用户的密码会被清除,致使第二次登录系统拿到的是空密码 // 因此须要new一个对象或将原对象复制一份 // 这个解决方案来自 https://stackoverflow.com/questions/43007763/spring-security-encoded-password-gives-me-bad-credentials/43046195#43046195
                return new User(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities()); } } throw new UsernameNotFoundException("用户名不存在,请检查用户名或注册!"); } }
CustomUserDetailsService

这部分就比较简单了,惟一的注意点我在注释中已经写的很清楚了,固然你要是使用链接数据库的话,这个问题就不存在了。

UserDetailsService 这个接口就是 Spring Security 为其它的数据访问策略作支持的。

至此,一个基本的 Spring Security + JWT 登录的后端就完成了,你能够写几个 controller 而后用 postman 测试功能了。

其它部分的代码由于比较简单,你能够参照源码自行实现你须要的功能。

 

前端搭建

建立 Vue 项目的方式网上有不少,此处也再也不赘述,我只说一点,过去 Vue 项目建立完成后,在项目目录下会生成一个 config 文件夹,用来存放 vue 的配置,但如今默认建立的项目是不会生成这个文件夹的,须要你手动在项目根目录下建立 vue.config.js 做为配置文件。

此处请参考:Vue CLI 官方文档,配置参考部分

附:使用 Vue CIL 建立 Vue 项目

依赖包

先后端数据传递我使用了更为简单的 fetch api, 固然你也能够选择兼容性更加好的 axios

Ui 为 ElementUI

为了获取 XSRF-TOKEN,还须要 VueCookies

最后为了在项目的首页展现介绍,我还引入了 mavonEditor,一个基于 vue 的 Markdown 插件

引入以上包以后,你与要修改 src 目录下的 main.js 文件以下。

import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import mavonEditor from 'mavon-editor'; import 'mavon-editor/dist/css/index.css'; import VueCookies from 'vue-cookies' import axios from 'axios'

// 让ajax携带cookie
axios.defaults.withCredentials=true; // 注册 axios 为全局变量
Vue.prototype.$axios = axios // 使用 vue cookie
Vue.use(VueCookies) Vue.config.productionTip = false
// 使用 ElementUI 组件
Vue.use(ElementUI) // markdown 解析编辑工具
Vue.use(mavonEditor) // 后台服务地址
Vue.prototype.SERVER_API_URL = "http://127.0.0.1:8088/api"; new Vue({ router, store, render: h => h(App) }).$mount('#app')

前端跨域配置

在建立 vue.config.js 完成后,你须要在里面输入如下内容,用来完成 Vue 的跨域配置

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

 

一些注意事项

页面设计这些没有什么可写的了,须要注意的一点就是在对后端服务器进行 POST,DELETE,PUT 等操做时,请在请求头中带上 "X-XSRF-TOKEN": this.$cookies.get('XSRF-TOKEN'),若是不带,那么哪怕你登录了,后台也会返回 403 异常的。

credentials: "include" 这句也不能少,这是携带 Cookie 所必须的语句。若是不加这一句,等于没有携带 Cookie,也就等于没有登录了。

举个例子:

       deleteItem(data) { fetch(this.SERVER_API_URL + "/admin/data/" + data.id, { headers: { "Content-Type": "application/json; charset=UTF-8", "X-XSRF-TOKEN": this.$cookies.get('XSRF-TOKEN') }, method: "DELETE", credentials: "include" }).then(response => response.json()) .then(json => { if (json.status === 200) { this.systemDataList.splice(data.id, 1); this.$message({ message: '删除成功', type: 'success' }); } else { window.console.log(json); this.$message.error(json.message); } }); },

 

暂时就先写这些吧,若是你有什么问题或者好的建议,欢迎在评论区提出。

参考文档

Spring Security Reference

Vue.js

依赖工具

mavonEditor

element ui

相关文章
相关标签/搜索