关于jwt token鉴权的一些理解
token 中文译 “令牌”,是一种由服务端生成的字符串,颁发给客户端使用的鉴权机制。
客户端获取到一个token后,以后无需带上用户名和密码,只需带上token即可。
身份认证论述:
HTTP是无状态协议,服务端并不清楚是谁请求了它,只有客户端带上用户的账号密码,服务端通过验证后才清楚到底是那个用户请求了它,本次请求完结后,再次请求又需要重新认证。
通用的解决办法是:用户第一次通过认证后,服务端生成一条记录,并将记录id放回给客户端,(类似与session_id),之后客户端每次请求带上这个id,服务端只需要检索这个id,找到了这条记录,就说明用户已经通过身份验证。
关于token认证的论述:
token认证的流程:用户第一次登录服务端,服务器根据用户的私有信息,时间戳,签名算法,私钥 通过算法生成token并返回给客户端,之后客户端请求只需要带上这个token即可,服务端只需对这个token进行解析,即可得知那个用户进行了操作。
私有信息: 一般为用户的信息,用户的标识等
时间戳: 当前时间
私钥: 这个比较重要,服务端自有信息。 生成token 解密时候需要它。就相当于一把锁的钥匙,用它锁上的信息,也只有用它才能解锁。不然客户端自己也可以生成token了
直接上代码:
JWTUtils.java
/**
-
ToKen工具类
-
JWT生成
*/
@Component
public class ToKenUtil {/**
-
生成Jwt
-
使用Hs256算法 私匙使用固定私钥
-
@param timeOut jwt过期时间
-
@return
*/
public static String createJWT(long timeOut,Map claims) {//指定签名的时候使用的签名算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;//生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);//校验项
claims.put(“admin”,true);//下面就是在为payload添加各种标准声明和私有声明了
//这里其实就是new一个JwtBuilder,设置jwt的body
JwtBuilder builder = Jwts.builder()
//如果有私有声明,一定要先设置这个自己创建的私有的声明,
// 这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
//设置jti(JWT ID):是JWT的唯一标识
.setId(UUID.randomUUID().toString())
//iat: jwt的签发时间
.setIssuedAt(now)
//JWT的主体。
.setSubject(UUID.randomUUID().toString())
//设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, JwtConstants.SECRET);
if (timeOut >= 0) {
long expMillis = nowMillis + timeOut;
Date exp = new Date(expMillis);
System.out.println(new SimpleDateFormat(“yyyy-MM-dd HH:ss:mm”).format(exp));
//设置过期时间
builder.setExpiration(exp);
}
return builder.compact();
}
/**
- 生成token
- 超时默认5分钟
- */
public static String createJWT(Map claims){
return createJWT(JwtConstants.TIME_OUT,claims);
}
/**
-
Token的解密
-
@param token 加密后的token
-
@return
*/
public static Claims parseJWT(String token) {//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(JwtConstants.SECRET)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
/**
-
校验token
-
异常为token过期
-
@param token
-
@return
*/
public static Boolean isVerify(String token) {//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(JwtConstants.SECRET)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();if (claims.get(“admin”).equals(true)) {
return true;
}
return false;
}
}
-
JwtConstants.java
/**
-
jwt相关配置
*/
public class JwtConstants {//超时时间 60天
public static long TIME_OUT = 1000 * 3600 * 24 * 60;
//私钥
public static String SECRET = “123456”;
public static String TOKEN_KEY = “authentication”;
//忽略的token验证
public static List IGNORE_PERMISSION = CollectionUtil.newLinkedList("/api/login/getAccessToKen");
}
restApiInteceptor.java 拦截器
/**
-
Rest Api接口鉴权
*/
public class RestApiInteceptor extends HandlerInterceptorAdapter {@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof org.springframework.web.servlet.resource.ResourceHttpRequestHandler) {
return true;
}
return check(request, response);
}private boolean check(HttpServletRequest request, HttpServletResponse response) {
if (JwtConstants.IGNORE_PERMISSION.toString().toUpperCase().contains(request.getServletPath().toUpperCase())) {
return true;
}
final String authToken = request.getHeader(JwtConstants.TOKEN_KEY);
if (authToken != null) {
try {
//验证token是否过期 包含验证jwt是否正确
if(!ToKenUtil.isVerify(authToken)){
throw new Exception();
}
} catch (ExpiredJwtException e) {
//token超时
RenderUtil.renderJson(response, LoginResponseData.toKenTimeOut());
return false;
}catch (Exception e) {
//token解析失败
RenderUtil.renderJson(response, LoginResponseData.invalidToKen());
return false;
}
} else {
//header没有带authentication字段
RenderUtil.renderJson(response, LoginResponseData.noToKen());
return false;
}
return true;
}
}
webConfig.java
/**
-
web 配置类
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {/**
- 静态资源映射
/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/*- 设置映射资源
- */
registry.addResourceHandler(uploadResourceHandler).addResourceLocations(“file:///” + uploadPath);
//本应用
registry.addResourceHandler("/assets/**").addResourceLocations(“classpath:/assets/”);
}
/**
- 增加对rest api鉴权的spring mvc拦截器
/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//对 /api/* 的请求进行拦截
registry.addInterceptor(newRestApiInteceptor()).excludePathPatterns().addPathPatterns("/api/**");
}
/**
- RequestContextListener注册
*/
@Bean
public ServletListenerRegistrationBean requestContextListenerRegistration() {
return new ServletListenerRegistrationBean<>(new RequestContextListener());
}
/**
- ConfigListener注册
*/
@Bean
public ServletListenerRegistrationBean configListenerRegistration() {
return new ServletListenerRegistrationBean<>(new ConfigListener());
}
- 静态资源映射
}
大概流程如下:
① 客户端第一次登录,服务器根据用户的私有信息 时间戳 签名串 私钥 过期时间 生成token并返回到客户端,之后客户端请求时带上token令牌
② 由于服务端每次接到请求都要认证,故此将认证逻辑推荐写入拦截器,并在webConfig配置中加入此拦截器,若不是springBoot工程,可在web.xml里配置
③ 服务端每次接收到请求后,对请求进行拦截,获取token信息后进行解析,若解析成功则通过过。
解析不成功大致有,token超时 无效的token(即不是服务端生成的token)
理解有误的地方可留言指教
token 中文译 “令牌”,是一种由服务端生成的字符串,颁发给客户端使用的鉴权机制。
客户端获取到一个token后,以后无需带上用户名和密码,只需带上token即可。
身份认证论述:
HTTP是无状态协议,服务端并不清楚是谁请求了它,只有客户端带上用户的账号密码,服务端通过验证后才清楚到底是那个用户请求了它,本次请求完结后,再次请求又需要重新认证。
通用的解决办法是:用户第一次通过认证后,服务端生成一条记录,并将记录id放回给客户端,(类似与session_id),之后客户端每次请求带上这个id,服务端只需要检索这个id,找到了这条记录,就说明用户已经通过身份验证。
关于token认证的论述:
token认证的流程:用户第一次登录服务端,服务器根据用户的私有信息,时间戳,签名算法,私钥 通过算法生成token并返回给客户端,之后客户端请求只需要带上这个token即可,服务端只需对这个token进行解析,即可得知那个用户进行了操作。
私有信息: 一般为用户的信息,用户的标识等
时间戳: 当前时间
私钥: 这个比较重要,服务端自有信息。 生成token 解密时候需要它。就相当于一把锁的钥匙,用它锁上的信息,也只有用它才能解锁。不然客户端自己也可以生成token了
直接上代码:
JWTUtils.java
/**
-
ToKen工具类
-
JWT生成
*/
@Component
public class ToKenUtil {/**
-
生成Jwt
-
使用Hs256算法 私匙使用固定私钥
-
@param timeOut jwt过期时间
-
@return
*/
public static String createJWT(long timeOut,Map claims) {//指定签名的时候使用的签名算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;//生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);//校验项
claims.put(“admin”,true);//下面就是在为payload添加各种标准声明和私有声明了
//这里其实就是new一个JwtBuilder,设置jwt的body
JwtBuilder builder = Jwts.builder()
//如果有私有声明,一定要先设置这个自己创建的私有的声明,
// 这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
//设置jti(JWT ID):是JWT的唯一标识
.setId(UUID.randomUUID().toString())
//iat: jwt的签发时间
.setIssuedAt(now)
//JWT的主体。
.setSubject(UUID.randomUUID().toString())
//设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, JwtConstants.SECRET);
if (timeOut >= 0) {
long expMillis = nowMillis + timeOut;
Date exp = new Date(expMillis);
System.out.println(new SimpleDateFormat(“yyyy-MM-dd HH:ss:mm”).format(exp));
//设置过期时间
builder.setExpiration(exp);
}
return builder.compact();
}
/**
- 生成token
- 超时默认5分钟
- */
public static String createJWT(Map claims){
return createJWT(JwtConstants.TIME_OUT,claims);
}
/**
-
Token的解密
-
@param token 加密后的token
-
@return
*/
public static Claims parseJWT(String token) {//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(JwtConstants.SECRET)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
/**
-
校验token
-
异常为token过期
-
@param token
-
@return
*/
public static Boolean isVerify(String token) {//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(JwtConstants.SECRET)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();if (claims.get(“admin”).equals(true)) {
return true;
}
return false;
}
}
-
JwtConstants.java
/**
-
jwt相关配置
*/
public class JwtConstants {//超时时间 60天
public static long TIME_OUT = 1000 * 3600 * 24 * 60;
//私钥
public static String SECRET = “123456”;
public static String TOKEN_KEY = “authentication”;
//忽略的token验证
public static List IGNORE_PERMISSION = CollectionUtil.newLinkedList("/api/login/getAccessToKen");
}
restApiInteceptor.java 拦截器
/**
-
Rest Api接口鉴权
*/
public class RestApiInteceptor extends HandlerInterceptorAdapter {@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof org.springframework.web.servlet.resource.ResourceHttpRequestHandler) {
return true;
}
return check(request, response);
}private boolean check(HttpServletRequest request, HttpServletResponse response) {
if (JwtConstants.IGNORE_PERMISSION.toString().toUpperCase().contains(request.getServletPath().toUpperCase())) {
return true;
}
final String authToken = request.getHeader(JwtConstants.TOKEN_KEY);
if (authToken != null) {
try {
//验证token是否过期 包含验证jwt是否正确
if(!ToKenUtil.isVerify(authToken)){
throw new Exception();
}
} catch (ExpiredJwtException e) {
//token超时
RenderUtil.renderJson(response, LoginResponseData.toKenTimeOut());
return false;
}catch (Exception e) {
//token解析失败
RenderUtil.renderJson(response, LoginResponseData.invalidToKen());
return false;
}
} else {
//header没有带authentication字段
RenderUtil.renderJson(response, LoginResponseData.noToKen());
return false;
}
return true;
}
}
webConfig.java
/**
-
web 配置类
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {/**
- 静态资源映射
/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/*- 设置映射资源
- */
registry.addResourceHandler(uploadResourceHandler).addResourceLocations(“file:///” + uploadPath);
//本应用
registry.addResourceHandler("/assets/**").addResourceLocations(“classpath:/assets/”);
}
/**
- 增加对rest api鉴权的spring mvc拦截器
/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//对 /api/* 的请求进行拦截
registry.addInterceptor(newRestApiInteceptor()).excludePathPatterns().addPathPatterns("/api/**");
}
/**
- RequestContextListener注册
*/
@Bean
public ServletListenerRegistrationBean requestContextListenerRegistration() {
return new ServletListenerRegistrationBean<>(new RequestContextListener());
}
/**
- ConfigListener注册
*/
@Bean
public ServletListenerRegistrationBean configListenerRegistration() {
return new ServletListenerRegistrationBean<>(new ConfigListener());
}
- 静态资源映射
}
大概流程如下:
① 客户端第一次登录,服务器根据用户的私有信息 时间戳 签名串 私钥 过期时间 生成token并返回到客户端,之后客户端请求时带上token令牌
② 由于服务端每次接到请求都要认证,故此将认证逻辑推荐写入拦截器,并在webConfig配置中加入此拦截器,若不是springBoot工程,可在web.xml里配置
③ 服务端每次接收到请求后,对请求进行拦截,获取token信息后进行解析,若解析成功则通过过。
解析不成功大致有,token超时 无效的token(即不是服务端生成的token)
理解有误的地方可留言指教