Spring boot+VUE使用token实现登录验证及退出
Spring boot+VUE实现token验证
Vue+Spring Boot实现token认证主要可分为六步:
1.前端登录,post用户名和密码到后端。
2.后端验证用户名和密码,若通过,生成一个token返回给前端。
3.前端拿到token用vuex和localStorage管理,登录成功进入首页。
4.之后前端每一次权限操作如跳转路由,都需要判断是否存在token,若不存在,跳转至登录页。
5.前端之后的每一个对后端的请求都要在请求头上带上token,后端查看请求头是否有token,拿到token检查是否过期,返回对应状态给前端。(通常失败返回401)
6.若token已过期,清除token信息,跳转至登录页。
导依赖
<!-- jwt.token要用到的--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>
添加token工具类
.withClaim("Username", staff.getUsername())
我这个项目没有用户密码,所以我这里只用一个Username,如果有密码的话可以加多一行,比如说:
.withClaim("Password", staff.getPassword())
package com.xfish.employeeManagement.token; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.xfish.employeeManagement.pojo.staff; import java.util.Date; public class TokenUtil { private static final long EXPIRE_TIME= 10*60*60*1000; //十小时 private static final String TOKEN_SECRET="123456"; //密钥盐 /** * 签名生成 * @param staff * @return */ public static String sign(staff staff){ String token = null; try { Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME); token = JWT.create() .withIssuer("auth0") .withClaim("Username", staff.getUsername()) // .withAudience(staff.getUsername()) .withExpiresAt(expiresAt) // 使用了HMAC256加密算法。 .sign(Algorithm.HMAC256(TOKEN_SECRET)); } catch (Exception e){ e.printStackTrace(); } return token; } /** * 签名验证 * @param token * @return */ public static boolean verify(String token){ try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build(); DecodedJWT jwt = verifier.verify(token); System.err.println("认证通过:"); System.err.println("Username: " + jwt.getClaim("Username").asString()); System.err.println("过期时间: " + jwt.getExpiresAt()); return true; } catch (Exception e){ return false; } } }
添加拦截器
这个拦截器主要的功能就是,把一些没有token的请求拦截下来并返回错误信息,有token就可以直接通过
package com.xfish.employeeManagement.Interceptor; import com.alibaba.fastjson.JSONObject; import com.xfish.employeeManagement.token.TokenUtil; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class TokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{ if(request.getMethod().equals("OPTIONS")){ response.setStatus(HttpServletResponse.SC_OK); return true; } response.setCharacterEncoding("utf-8"); String token = request.getHeader("token"); //前端vue将token添加在请求头中 // System.err.println(request.getHeader()); if(token != null){ boolean result = TokenUtil.verify(token); if(result){ System.out.println("通过拦截器"); return true; } } response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); try{ JSONObject json = new JSONObject(); json.put("msg","token verify fail"); json.put("code","50000"); response.getWriter().append(json.toJSONString()); System.out.println("认证失败,未通过拦截器"); }catch (Exception e){ e.printStackTrace(); response.sendError(500); return false; } return false; } }
配置跨域,并处理请求,
注意:这边拦截的是后端的接口,不是前端的,
在中’ /** ‘无论是restful的,还是后面有一大长串地址都可以识别到
excludePath.add("/toLogin/**");
package com.xfish.employeeManagement.config; import com.xfish.employeeManagement.Interceptor.TokenInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; @Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * 开启跨域 */ @Override public void addCorsMappings(CorsRegistry registry) { // 设置允许跨域的路由 registry.addMapping("/**") // 设置允许跨域请求的域名 .allowedOriginPatterns("*") // 设置允许的方法 .allowedMethods("GET","HEAD","POST","PUT","DELETE","OPTIONS") // 是否允许携带cookie参数 .allowCredentials(true) // 设置允许的方法 .allowedMethods("*") // 跨域允许时间 .maxAge(4600); } private TokenInterceptor tokenInterceptor; //构造方法 public WebMvcConfig(TokenInterceptor tokenInterceptor){ this.tokenInterceptor = tokenInterceptor; } @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer){ configurer.setTaskExecutor(new ConcurrentTaskExecutor(Executors.newFixedThreadPool(3))); configurer.setDefaultTimeout(30000); } @Override public void addInterceptors(InterceptorRegistry registry) { List<String> excludePath = new ArrayList<>(); //排除拦截,除了注册登录(此时还没token),其他都拦截 // excludePath.add(""); //登录 excludePath.add("/toLogin/**"); //登录 // excludePath.add("/admin/login"); //注册 // excludePath.add("/**"); excludePath.add("/img/**"); //静态资源 excludePath.add("/song/**"); //静态资源 registry.addInterceptor(tokenInterceptor) .addPathPatterns("/**") .excludePathPatterns(excludePath); WebMvcConfigurer.super.addInterceptors(registry); } }
登录的接口
@GetMapping("/toLogin/{username}") public Object toLogin(@PathVariable("username") String username){ staff staff = staffService.queryStaffDataByName(username); JSONObject jsonObject = new JSONObject(); if(staff!=null){ String token = TokenUtil.sign(staff); jsonObject.put("token",token); jsonObject.put("staff",staff); jsonObject.put("msg","登录成功"); jsonObject.put("code",200); }else { jsonObject.put("msg","账号或密码错误"); jsonObject.put("code",500); } return jsonObject; }
前端的配置
(1)main.js配置
1.添加路由前置守卫beforeEach拦截请求,2.在axios中添加请求拦截器:
//路由全局前置守卫 router.beforeEach((to,from,next) => { // if(to.path === '/register' || to.path === '/login' || to.path === '/'){ //若是进入登录与注册页面 ==> pass if(to.path === '/register' || to.path === '' || to.path === '/'){ next() }else{ let userToken = localStorage.getItem('token'); // console.log("Token为:"+userToken); if(userToken == null || userToken == ''){ alert("无权限,请先登录!"); return next('/'); }else{ next(); } } }), //请求拦截器 在请求头中加token axios.interceptors.request.use( config => { // 将获取到的token设置给header中的token if(localStorage.getItem('token')){ config.headers.token = localStorage.getItem('token'); } return config; }, error => { return Promise.reject(error); } )
配置Vuex:
在配置前记得一定要下载Vuex的依赖
cnpm install vuex --save
vuex的使用方式(可以不看)
(5条消息) 在vue-cli项目里使用vuex,vuex的详细使用_小仙女de成长的博客-CSDN博客
(2)store/index.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { user: localStorage.getItem('staff') ? localStorage.getItem('staff') : null, //若localSorage存在token,将值赋给Vuex.state.token token: localStorage.getItem('token') ? localStorage.getItem('token') : null }, mutations: { setUsername(state, staff) { state.staff = staff // JSON.stringify(ary) //将staff存成JSON格式 localStorage.setItem('staff', JSON.stringify(staff)) }, setToken(state, token) { localStorage.setItem('token', token) state.token = token }, //我页面中写了退出登录的按钮所以补了一个logout用来清空localStorage中的数据 logout(state) { localStorage.removeItem('token') state.token = null localStorage.removeItem('staff') state.setUsername = null //我写的RSA加密需向后端获取公钥,为了方便我在拿到公钥后直接存入localStorage中,所以在这里也要清空 localStorage.removeItem('publickey') state.publickey=null } } })
登录方法
记得一定要导包
import store from '../store/index.js'
import store from '../store/index.js' export default { data() { return { ruleForm: { pass: '', username: ''}, staff:null, } }, methods: { }
submitForm() { var vm=this; vm.axios.get('http://localhost:8090/toLogin/'+this.ruleForm.username+'' ).then(function (response) { console.log(response.data.staff) if(response.data.code == 200){//将token和user保存到localStorage中 store.commit('setToken',response.data.token) store.commit('setUsername',response.data.staff) //跳转到登录成功后的页面 console.log("========"+response.data.msg) vm.$router.push('/index') //获取本地存储中的staff vm.staff=JSON.parse(localStorage.getItem('staff')) console.log(vm.staff); } else{ alert(response.data.msg) } }).catch(function (e) { console.log(e) }) }
退出登录方法,记得一定要导包
import store from '../store/index.js'
loginOut(){ store.commit('logout') }
Spring boot+VUE为什么不用session,
因为session是存在与服务器的,跨域session是会变的。