elementui中加验证-超级实用

2023-08-29 0 9,375 百度已收录

介绍

完整代码:

跑秀

前端

主要展示了Spring Security与JWT结合建立前端API套接字。

主要功能包括登录(Spring Security中如何添加验证码登录)、搜索、创建、删除以及区分用户权限等。

ps:因为只是demo,所以没有调用数据库,上面提到的增删改查都是在HashMap中完成的。

后端

展示如何使用Vue建立后端与前端的协作,包括跨域设置、后端登录拦截

并实现POST、GET、DELETE请求。包括如何在Vue中使用前端XSRF-TOKEN来防止CSRF攻击

技术栈

实施细节

创建Springboot项目并添加JJWT和SpringSecurity项目依赖。 这很简单。 有很多带有块内容的教程。 唯一需要注意的是,如果您使用的是Java版本11,那么您需要添加以下依赖项。 使用 Java8 没有必要。

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

elementui中加验证-超级实用

要使用Spring Security实现对用户的权限控制,首先需要实现一个简单的User对象来实现UserDetails套接字。 UserDetails 套接字负责提供核心用户信息。 如果只需要用户登录的账号密码,不需要其他信息,比如验证码等,那么可以直接使用Spring Security默认提供的User类,不需要自己实现。

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;

    /**
    * 省略其它的 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对象,其中包含了记住我、验证码等登录信息。由于SpringSecurity集成了Jwt,所以本质上是使用自己自定义的登录过滤器来替代SpringSecurity原生的登录过滤器。 这样的话,原生的remember-me功能就很难用了,所以我在User对象中添加了remember-me信息,自己实现了这个功能。

JWT 令牌认证工具

首先,我们创建一个新的 TokenAuthenticationHelper 类来处理身份验证过程中的验证和请求

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;
    }
}

令牌认证助手

addAuthentication方法负责返回成功登录信息,使用HTTPOnly cookies可以有效避免XSS攻击。

登录成功后,返回用户的权限、用户名、登录过期时间,可以有效帮助后端构建合适的用户界面。

getAuthentication方法负责验证用户的其他请求。 如果用户的JWT解析正确,就会返回usernamePasswordAuthenticationToken用户名和密码验证token给SpringSecurity,告诉SpringSecurity用户的权限,并放到当前Context中,然后执行过滤器链Make implore进行。

至此,我们基本的登录和验证所需要的技巧就已经写完了

ps:LoginResultDetails类和ResultDetails请参考项目源码。 篇幅有限,这里就不再重复了。

JWT 过滤器配置

众所周知,Spring Security使用一系列Servlet Filter来提供各种安全功能,所以如果我们想要使用JWT,我们需要实现两个与JWT相关的过滤器。

一是用户登录过滤器,验证用户是否登录成功,如果登录成功,则生成token返回给客户端。 如果登录失败,则向后端给出登录失败提示。

elementui中加验证-超级实用

第二个过滤器是在发送其他请求时对令牌进行校准的过滤器,如果校准成功则继续执行该请求。

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

在项目下新建一个名为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();
    }
}

Jwt登录过滤器

该类主要有以下功能

自定义的JwtLoginFilter继承自AbstractAuthenticationProcessingFilter,并实现了三个默认方法。 defaultFilterProcessesUrl变量是我们需要设置的登录路径

在attemptAuthentication方法中,我们从登录参数中提取出用户名和密码,然后调用AuthenticationManager.authenticate()方法进行手动校准。

第二步,如果校准成功,就会来到successfulAuthentication反弹。 在successfulAuthentication方法中,使用之前写的addAuthentication来创建下辈子的token,并使用HttpOnly cookie写入到客户端。

第二步,如果校准失败,就会转到unsuccessfulAuthentication方法。 在此方法中elementui中加验证,将向客户端返回一条错误消息。

ps:verifyCodeService和loginCountService的方法与本文关系不大,代码实现请参考源码

唯一需要注意的是

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

可以看出,这是各种spring security异常的父类。 编写一个验证码异常类继承AuthenticationException,然后直接抛出验证码异常。

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

elementui中加验证-超级实用

@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.bugugaoshu.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安全配置

然后我们来配置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 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的教程,会设置. 为了这个,我还是启用这个功能,顺便学习一下XSRF-TOKEN的使用方法

由于本项目是一个demo,不涉及数据库部分,所以我选择直接将用户写入显存,网上将用户写入显存如上面的代码注释部分。 即使写起来很简单,也存在一些问题。 通过断点,我们可以知道调用Spring Security的方法是ProviderManager方法。 这些方法不方便我们抛出用户名不存在或其异常,它会抛出BadCredentials异常,并且不会提示其他错误。

如右图所示。

elementui中加验证-超级实用

出于安全考虑,SpringSecurity会将所有登录异常归结为BadCredentials异常,因此为了抛出用户名不存在等异常,如果使用SpringSecurity默认的登录方式,可以像GitHub项目Vhr中的那样处理,但是,由于本项目使用Jwt来替代默认的登录表单,因此实现详细的异常信息抛出比较复杂。 我找了很久也没有找到更简单更合适的方法。 如果您有好的方法,欢迎分享。

最后,我的解决方案是使用SpringSecurity的DaoAuthenticationProvider类成为身份验证提供者。 该类实现了AbstractUserDetailsAuthenticationProvider,具体的用户详细信息认证功能。 查看注释,我们可以知道AbstractUserDetailsAuthenticationProvider提供了AbaseAuthenticationProvider,允许子类覆盖并与UserDetails对象一起工作。该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。 (允许通用覆盖并使用 UserDetails 对象的基本身份验证提供程序。此类专用于响应 UsernamePasswordAuthenticationToken 身份验证请求。)

通过配置自定义用户查询实现类,我们可以直接在CustomUserDetailsS​​ervice中抛出找不到用户名的异常,然后将hideUserNotFoundExceptions设置为false,这样我们就可以区分是密码错误还是用户名不存在。

然而,这些方法仍然存在问题。 他们不能抛出异常,例如帐户被锁定。 理论上,继承具体类AbstractUserDetailsAuthenticationProvider后,通过重绘登录方法就可以实现这些功能。 我看了一下,好像很复杂。 演示不是必需的。 , 我放弃。

另外,看起来安全信息暴露得越少越好,暂时就这样吧。 (就是给自己找个理由)

用户查找服务

public class CustomUserDetailsService implements UserDetailsService {
    private List 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("用户名不存在,请检查用户名或注册!");
    }
}

这部分比较简单,唯一的注意点我在注释中已经写得很清楚了,其实如果使用连接数据库的话,这个问题就不会存在。

UserDetailsS​​ervice 套接字是 Spring Security 支持其他数据访问策略的套接字。

至此,一个基本的SpringSecurity+JWT登录前端就完成了,可以编写几个控制器并使用postman来测试功能了。

由于其他部分代码比较简单,大家可以参考源码自行实现自己需要的功能。

后端构建

创建Vue项目的方法网上有很多,这里不再赘述。 我只是想说,以前Vue项目创建后,会在项目目录下生成一个config文件夹,用来存放Vue的配置,但现在是默认创建的。 项目不会生成该文件夹,需要在项目根目录下自动创建 vue.config.js 作为配置文件。

请参考:VueCLI官方文档,配置参考部分

附:使用VueCIL创建Vue项目

依赖包

我使用更简单的fetchapi进行前后端数据传输,但是你也可以选择兼容性更好的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异常。

凭证:“include”这句话也是少不了的elementui中加验证,是携带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);
            }
        });
},

结束

暂时先写这些吧。 如果您有任何疑问或者好的建议,请在评论区提出。

结尾

我知道你 “在看

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 elementui elementui中加验证-超级实用 https://www.wkzy.net/game/180016.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务