Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
灣華視新聞18日晚間字幕出現亂碼。圖片來源:臺灣“中時新聞網”
華夏經緯網5月22日訊:據臺灣“中時新聞網”報導,從4月20日開始,臺灣華視新聞因跑馬燈字幕問題已連出7包,沒想到21日又有網友抓包,在網絡上爆料華視新聞18日晚間字幕出現亂碼“張善政出戰 鄭運鵬認:桃園‘藍>綠’必敗”,過沒幾秒后疑似內部人員發現錯誤,整個畫面的標題全拿掉,引發網友熱議,甚至還有網友分析出這段文字出現的可能原因。
短短24天內,華視新聞連環出包案,令外界感到不可思議。4月20日早上7時新聞跑馬燈誤植“新北市遭解放軍導彈擊中”等字幕,同日9時二度出包 “大屯火山爆發 巖漿滾滾而下”、“臺北凌晨下起拳頭大冰雹 市區交通大亂”,引發軒然大波,陳郁秀、陳雅琳為此雙雙請辭董事長、代總經理職務。未料,4月24日三度出包把臺當局行政機構負責人蘇貞昌誤植成臺灣地區領導人。
鄭自隆4月29日接任華視董事長,不斷說要改革,但華視仍頻頻出包,包括誤植美國職棒隊徽、快訊打出“上海日增1181萬例,確診近1個月新低”錯誤訊息,以及將蔡英文打成“蔡EE”,以及將前臺灣地區副領導人陳建仁打成“美國副總統”,加上5月18日的“張善政出戰 鄭運鵬認:桃園‘藍>綠’必敗”亂碼文,鄭自隆任職任職不到1個月,就出5包,實在有點尷尬。
此事也引發島內網友熱議,“蠻好笑的”、“那是什么意思?”、“有什么密碼嗎?”、“有比蔡EE、美國副總統嚴重嗎”、“要不要下次也把字幕撤了”、“不要有任何字就不會錯了”、“真是螺絲掉滿地”。
還有些網友則紛紛猜起這段文字原本要表達的內容,“這個是html的空格符嗎?”、“大于的html escape”、“空格符是 這個應該是>大于符”、“>是html的大于的意思”、“這是原本要打什么”、“因為字幕程序中,大于符號(>),可能為保留字符,所以鍵入時要輸 >”、“gt就是大于”。
行展示
主要展示 Spring Security 與 JWT 結合使用構建后端 API 接口。
主要功能包括登陸(如何在 Spring Security 中添加驗證碼登陸),查找,創建,刪除并對用戶權限進行區分等等。
ps:由于只是 Demo,所以沒有調用數據庫,以上所說增刪改查均在 HashMap 中完成。
展示如何使用 Vue 構建前端后與后端的配合,包括跨域的設置,前端登陸攔截
并實現 POST,GET,DELETE 請求。包括如何在 Vue 中使用后端的 XSRF-TOKEN 防范 CSRF 攻擊
實現細節
創建 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 對象里添加了記住我的信息,用來自己實現這個功能。
首先我們來新建一個 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
至此,我們的基本登陸與驗證所需要的方法就寫完了
ps:其中的 LoginResultDetails 類和 ResultDetails 請看項目源碼,篇幅所限,此處不在贅述。
眾所周知,Spring Security 是借助一系列的 Servlet Filter 來來實現提供各種安全功能的,所以我們要使用 JWT 就需要自己實現兩個和 JWT 有關的過濾器
這兩個過濾器,我們分別來看,先看第一個:
在項目下新建一個包,名為 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
這個類主要有以下幾個作用
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, 代碼如下
@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("用戶名不存在,請檢查用戶名或注冊!");
}
}
這部分就比較簡單了,唯一的注意點我在注釋中已經寫的很清楚了,當然你要是使用連接數據庫的話,這個問題就不存在了。
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);
}
});
},
作者:陜西顏值扛把子 來源:知乎 github完整代碼可私信獲取!
tml網頁源碼加密
html網頁源碼能加密嗎?能加密到何種程度?
某些時候,我們可能需要對html網頁源碼加密,使網頁源碼不那么容易被他人獲得。出于這個目標,本文測試一種html加密方式。
提前透露:結論超出預期,似乎還實現了反爬蟲。
首先來到網址:http://fairysoftware.com/html_jia_mi.html
由頁面介紹可知,這是一種使用js和escape結合實現的html加密。
直接使用頁面提供的例程,加密這一段html代碼:
得到加密的html代碼,如下圖:
然后將加密代碼粘貼到一個html文件中測試,如下圖:
頁面可以正常打開。查看網頁源碼,果然源碼是加密的,如下圖:
特別的驚喜之處是:
如上圖所示,鏈接果然消失了。
即使用開發者工具查看,也無法得到鏈接地址,而原始未加密前的html代碼中是有鏈接的,如下圖:
那么消失了的鏈接,還能正常點擊嗎?
點擊,鏈接可以正常打開:
雖然href鏈接隱藏了,但還能正常打開頁面,功能完全正常。
測試結果既驚喜又意外,這樣的html網頁加密,效果還真是不錯,值得一用。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。