项目实训6
1. token的作用
为什么使用Token验证:
所谓的Token,其实就是服务端生成的一串加密字符串、以作客户端进行请求的一个“令牌”。当用户第一次使用账号密码成功进行登录后,服务器便生成一个Token及Token失效时间并将此返回给客户端,若成功登陆,以后客户端只需在有效时间内带上这个Token前来请求数据即可,无需再次带上用户名和密码。
Token就为我们免去了每次打开应用都要输入账号跟密码的过程。
为什么要使用Token?
为什么要使用Token?这个问题其实很好回答——因为它能解决问题!
当下用户对产品的使用体验要求在逐渐提高,从产品体验方面来讲,Token带来的体验更容易能让用户接受。
那么Token都可以解决哪些问题呢?
- Token具有随机性、不可预测性、时效性、无状态、跨域等特点。
- Token完全由应用管理,所以它可以避开同源策略
- Token可以避免CSRF攻击
- Token可以是无状态的,可以在多个服务间共享
- Token是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回Token给前端。前端可以在每次请求的时候带上Token证明自己的合法地位。如果这个Token在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌。
2. 项目中token的使用
引入JWT
依赖,由于是基于Java
,所以需要的是java-jwt
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2.1 jwt的结构
JWT是由三段信息构成的,将这三段信息文本用
.
连接一起就构成了JWT字符串。
就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT包含了三部分:
Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)
Payload 负载 (类似于飞机上承载的物品)
Signature 签名/签证JWT的头部承载两部分信息:token类型和采用的加密算法。
载荷就是存放有效信息的地方。
有效信息包含三个部分
1.标准中注册的声明
2.公共的声明
3.私有的声明jwt的第三部分是一个签证信息
这个部分需要base64
加密后的header
和base64
加密后的payload
使用.
连接组成的字符串,然后通过header
中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt
的第三部分。**
- 生成token工具类
package com.example.guke.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.guke.entity.User;
import com.example.guke.service.UserService;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUitls {
@Autowired
private UserService userService;
/**
* 生成token
* @param user 用户
* @return token 签名
*/
public String getToken(User user) {
String token="";
token= JWT.create().withAudience(user.getOpenid())
.sign(Algorithm.HMAC256(user.getNickname()));
return token;
}
}
- 定义拦截器
package com.example.guke.interceptor;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.example.guke.annotation.PassToken;
import com.example.guke.annotation.UserLoginToken;
import com.example.guke.entity.User;
import com.example.guke.exception.CustomException;
import com.example.guke.result.ResultCodeEnum;
import com.example.guke.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token
// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查是否有passToken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
// 执行认证
if (token == null) {
throw new CustomException(ResultCodeEnum.NO_TOKEN);
}
// 获取 token 中的 user openid
String openid;
try {
openid = JWT.decode(token).getAudience().get(0);
// log.info(openid);
} catch (JWTDecodeException j) {
throw new RuntimeException("token解析出错,请联系管理员");
}
User user = userService.findUserByOpenid(openid);
// log.info(user.toString());
if (user == null) {
throw new CustomException(ResultCodeEnum.UNKNOWN_USER);
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getNickname())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("token错误");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
}
}
主要目的是在需要的时候判断请求头中是否有token字段,如果没有可以使用之前所提到的统一异常处理直接返回相应的异常。
HandlerInterceptor
接口主要定义了三个方法
1.boolean preHandle ()
:
预处理回调方法,实现处理器的预处理,第三个参数为响应的处理器,自定义Controller
,返回值为true
表示继续流程(如调用下一个拦截器或处理器)或者接着执行
postHandle()
和afterCompletion()
;false
表示流程中断,不会继续调用其他的拦截器或处理器,中断执行。
2.void postHandle()
:
后处理回调方法,实现处理器的后处理(DispatcherServlet
进行视图返回渲染之前进行调用),此时我们可以通过modelAndView
(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView
也可能为null
。
3.void afterCompletion()
:
整个请求处理完毕回调方法,该方法也是需要当前对应的Interceptor
的preHandle()
的返回值为true时才会执行,也就是在DispatcherServlet
渲染了对应的视图之后执行。用于进行资源清理。整个请求处理完毕回调方法。如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally
中的finally
,但仅调用处理器执行链中
- 定义拦截器规则
package com.example.guke.interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
- 整个的主要流程
1.从
http
请求头中取出token
,
2.判断是否映射到方法
3.检查是否有passtoken
注释,有则跳过认证
4.检查有没有需要用户登录的注解,有则需要取出并验证
5.认证通过则可以访问,不通过会报相关错误信息
3. 自定义注解
通过自定义的注解在需要的地方判断token的存在,自定义注解见博客中其他的文章。
所以,我们便完成了项目中的token的使用。