zoukankan      html  css  js  c++  java
  • 用Spring Security, JWT, Vue实现一个前后端分离无状态认证Demo

    简介

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

    运行展示

    后端

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

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

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

    前端

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

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

    技术栈

    组件技术
    前端 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 则不需要。

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

    要使用 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<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 对象里添加了记住我的信息,用来自己实现这个功能。

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

  • 相关阅读:
    洛谷P3128 [USACO15DEC]Max Flow P 题解 树上差分(点差分)
    数列分块解决区间更新+区间最值问题
    ThinkPad P1 Gen3 4K 显示器出现间歇闪黑屏情况解决
    Qt自定义弹出式菜单(Qt自定义弹窗)
    软件产品易用性评价评估标准
    vue用echarts实现中国地图和世界地图
    知了业务逻辑梳理
    string.gfind string.gmatch
    无法定位程序输入点在 XXXX上...
    [Lua]c解析lua 嵌套table
  • 原文地址:https://www.cnblogs.com/puzhiwei/p/11989946.html
Copyright © 2011-2022 走看看