设置token拦截的目的是为了让用户必须经过验证(比如账号密码)才能访问内部页面。
比如你写了"/login",“/register”,“/main"几个页面,那从逻辑上来说用户应该要先进行注册登陆才能访问”/main"页面。
但是使用vue写好这三个页面后你会发现,用户直接访问"/main"也是没有任何阻拦的。所以这个时候需要路由卫士和token拦截。
在注册和登陆通过的时候,会根据你的账号或密码生成一串字符串token,然后存在缓存里。
你想访问任何页面之前,会先验证你是否有token,如果没有,就把你送回"/login"页面,如果有,则你想去哪就去哪。
后端
- pom.xml里面添加token依赖
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.7.0</version> </dependency>
- token生成与验证的类
- 应用方式:
- 下面的代码就原封不动复制到后端某个地方,可以建个文件夹存放(为了方便这个文件夹就叫token文件夹了)。
- 作用:
- 这个是用于生成和验证token的。
- 生成token:作用在后端的login方法里(下面有举例),你前端的登陆页面发送了登录请求,那么你后端有个登陆方法(比如就叫checkLogin)验证后,返回结果。现在你要在返回的结果里多带一条信息,就是token,那么token怎么来,需要调用这个TokenGenerate().generateToken(account)方法。
- 验证token:作用在后端的拦截器中(下面有举例)。前端发送的请求要先经过拦截器,然后再到达各个controller。拦截器里会先设置token检验,那么就会用到这个TokenGenerate.verify(token)。
public class TokenGenerate { private static final long EXPIRE_TIME= 15*60*1000; private static final String TOKEN_SECRET="tokenqkj"; //密钥盐 public String generateToken(String username){ String token = null; try{ Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME); token = JWT.create() .withIssuer("auth0") .withClaim("username", username) .withExpiresAt(expiresAt) .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.out.println("认证通过:"); System.out.println("issuer: " + jwt.getIssuer()); System.out.println("username: " + jwt.getClaim("username").asString()); System.out.println("过期时间: " + jwt.getExpiresAt()); return true; } catch (Exception e){ System.out.println("token:"+token); System.out.println("没通过"); return false; } } }
- 拦截器
- 应用方式:
- 下面的代码就原封不动复制到刚刚的token文件夹里。
- 作用:
- 这个就是上面说的拦截器。里面有一句
request.getHeader("token") 就是调用了上面的方法进行验证了。
- 这个就是上面说的拦截器。里面有一句
//token拦截器,对拦截下的接口检查其的token是否符合只有 // 在提供一个有效的token时才能通过验证,否则给出认证失败的响应。 @Component public class TokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception{ //Axios 发起跨域请求前,浏览器也会首先发起 OPTIONS 预检请求。检查服务器是否允许跨域访问。 if(request.getMethod().equals("OPTIONS")){ response.setStatus(HttpServletResponse.SC_OK); System.out.println("允许跨域访问"); return true; } response.setCharacterEncoding("utf-8"); String token = request.getHeader("token"); if(token != null){ boolean result = TokenGenerate.verify(token); if(result){ System.out.println("通过拦截器"); return true; } } response.setCharacterEncoding("UTF-8"); response.getWriter().write("认证失败,错误码:50000"); return false; } }
- 入口拦截
- 应用方式:
- 下面的代码就原封不动复制到刚刚的token文件夹里。
- 然后修改一处地方:注意我下面有两句
excludePath.add("/registerCheck"); //注册 excludePath.add("/loginCheck"); //登录
这是我后端中的注册和登陆方法。在这里写上这两句意思就是,当用户访问这两个方法的时候不用验证token。
这个很好理解,你登陆和注册的时候肯定是没有token的,token是你账号密码验证通过以后才会赋予你的信息。
这两个要修改成你的注册登陆url。
//入口拦截,设置哪些接口需要拦截或不拦截(保护后端接口 防止未经授权的访问) //只需要拦截本身就不会携带token的接口(例如登陆注册) //因为登陆后网页的请求头就会携带token,此时我们需要进行token的验证,防止在未登陆时就可以进行其他操作 @Configuration public class IntercepterConfig implements WebMvcConfigurer { private final TokenInterceptor tokenInterceptor; // 构造方法 public IntercepterConfig(TokenInterceptor tokenInterceptor) { this.tokenInterceptor = tokenInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { //excludePathPatterns用来配置不需要拦截的接口(或者相当于功能) List<String> excludePath = new ArrayList<>();//List用来保存所有不需要拦截的路径 excludePath.add("/registerCheck"); //注册 excludePath.add("/loginCheck"); //登录 //在登陆之后的网页中已经携带token,所以只需要放行登陆注册接口, //若放行其他接口,那么就相当于不需要登陆就可进行接口的使用 registry.addInterceptor(tokenInterceptor)//添加名为tokenInterceptor的拦截器 .addPathPatterns("/**") //指定拦截所有路径 .excludePathPatterns(excludePath);//排除不需要拦截的路径 WebMvcConfigurer.super.addInterceptors(registry); } }
- 登陆函数
- 应用方式:
- 跟着代码里的注释改。
- 作用:
- 在用户账号密码验证正确时,返回一个信号并携带token。
@GetMapping public String loginCheck(String account, String password) { //用户传入了账号密码 System.out.println("账号:" + account); System.out.println("密码:" + password); //在if括号里写你的方法,比如if (account.equals("admin") && password.equals("admin")),或者更复杂的数据库逻辑 if (账号密码是正确的) { //下面这一句用于生成token String token = new TokenGenerate().generateToken(account); //如果验证通过就返回token return token; } else { //如果失败就没有token了,返回null。 return null; } }
前端
- axios
- 应用方式
- 原封不动复制到前端的某个地方
- 然后修改一处,注意到我下面有一句,
url !== '/registerCheck' &&url !== '/loginCheck' && url !== '/logout' ,跟上面一样的意思,当用户访问这几个网址的时候不用验证token。即在安全性上排除了这几个网址。我排除了三个,注册,登录,注销。改成你自己的url。就是你后端的注册登陆网址是什么,这里就写什么。
import axios from 'axios' // 创建axios实例 const instance = axios.create({ baseURL: 'http://localhost:8082', // 设置后端接口地址(使用nginx代理了) timeout: 10000, // 设置请求超时时间 }); // 请求拦截器,在请求头中添加token instance.interceptors.request.use(config => { // 获取请求的URL const {url} = config; // 如果请求的URL不是login、logout或register,则在请求头中添加token if (url !== '/initUser' &&url !== 'check' && url !== '/logout' && url !== '/register') { // 从localStorage中获取token const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; config.headers.token = token; return config; } } return config; }, error => { console.log(error) return Promise.reject(error); }); // 响应拦截器 instance.interceptors.response.use(response => { // 在这里处理响应结果 return response; }, error => { // 在这里处理响应错误 return Promise.reject(error); }); // 导出axios实例 export default instance;
2.前端的注册登陆方法
我举个登陆的例子
//先import刚刚那个文件。"./filterAxios"需要改成你自己存放的位置 import axios from "./filterAxios" //然后去请求后端的loginCheck const res = axios.get("/loginCheck", {param:{{account: "xxx", password: "xxx"}}}) if(res.data === null) //说明登陆失败了,你可以弹个弹框啥的告诉用户 else { //把token存到本地 sessionStorage.setItem("token",res.data); }
- 路由卫士
- 应用方式
- 原封不动复制到前端的router文件夹的index.js中,复制到最后一行(
export default router )的上面!!!。不是最后一行的下面,是上面。 - 然后修改一处
if (to.name === "login" || to.name === "register") ,改成你的注册和登陆页面的前端的url。
- 原封不动复制到前端的router文件夹的index.js中,复制到最后一行(
- 作用
- 这个是先确定你要去哪个网页,如果是注册/登陆网页,那你就直接去。如果不是注册登陆,那就得先验证你有没有token。有你才能去,否则你还是会被送回登陆页面。
router.beforeEach((to, from, next) => { console.log("taken:router:", sessionStorage.getItem('token')) if (!sessionStorage.getItem('token')) { if (to.name === "login" || to.name === "register") next(); else { router.push('login') } } else { next(); } });
注意点
如果加了token以后前端出现了跨域错误“network error”,也有可能是后端出的错。
一度因为后端没报错,而前端报了错在前端苦苦寻找错误。
后端这家伙不爆错是因为在拦截器那里直接拦截了,直接将response设置为错误,返回给前端。