设置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文件夹里。
- 然后修改一处地方:注意我下面有两句
这是我后端中的注册和登陆方法。在这里写上这两句意思就是,当用户访问这两个方法的时候不用验证token。excludePath.add("/registerCheck"); //注册 excludePath.add("/loginCheck"); //登录
这个很好理解,你登陆和注册的时候肯定是没有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设置为错误,返回给前端。