用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

相關文章
相關標籤/搜索