一、使用JSON Web Token的好处?

1.性能问题。

JWT方式将用户状态分散到了客户端中,相比于session,可以明显减轻服务端的内存压力。
Session方式存储用户id的最大弊病在于Session是存储在服务器端的,所以需要占用大量服务器内存,
对于较大型应用而言可能还要保存许多的状态,一般还需借助nosql和缓存机制来实现session的存储,如果是分布式应用还需session共享。

2.单点登录。

JWT能轻松的实现单点登录,因为用户的状态已经被传送到了客户端。

token 可保存自定义信息,如用户基本信息,web服务器用key去解析token,就获取到请求用户的信息了。

我们也可以配置它以便包含用户拥有的任何权限。这意味着每个服务不需要与授权服务交互才能授权用户。

3.前后端分离。
以前的传统模式下,后台对应的客户端就是浏览器,就可以使用session+cookies的方式实现登录,
但是在前后分离的情况下,后端只负责通过暴露的RestApi提供数据,而页面的渲染、路由都由前端完成。因为rest是无状态的,因此也就不会有session记录到服务器端。

4.兼容性。
支持移动设备,支持跨程序调用,Cookie 是不允许垮域访问的,而 Token 则不存在这个问题。

5.可拓展性。

jwt是无状态的,特别适用于分布式站点的单点登录(SSO)场景。
比如有3台机器(A、B、C)组成服务器集群,若session存在机器A上,session只能保存在其中一台服务器,此时你便不能访问机器B、C,因为B、C上没有存放该Session,
而使用token就能够验证用户请求合法性,并且我再加几台机器也没事,所以可拓展性好。

6.安全性。因为有签名,所以JWT可以防止被篡改。

二、JSON Web Token是什么?

JWT是基于token的身份认证的方案

json web token全称。可以保证安全传输的前提下传送一些基本的信息,以减轻对外部存储的依赖,减少了分布式组件的依赖,减少了硬件的资源。

实现无状态、分布式的Web应用授权,jwt的安全特性保证了token的不可伪造和不可篡改。

本质上是一个独立的身份验证令牌,可以包含用户标识、用户角色和权限等信息,以及您可以存储任何其他信息(自包含)。任何人都可以轻松读取和解析,并使用密钥来验证真实性。

缺陷:

1)JWT在生成token的时候支持失效时间,但是支持的失效时间是固定的,比如说一天。
但是用户在等出的时候是随机触发的,那么我们jwt token来做这个失效是不可行的,因为jwt在初始化的时候已经定死在什么时候过期了。
采用其他方案,在redis中存储token,设置token的过期时间,每次鉴权的时候都会去延长时间

2)jwt不适合存放大量信息,信息越多token越长

JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:

A.B.C

A由JWT头部信息header加密得到
B由JWT用到的身份验证信息json数据加密得到
C由A和B加密得到,是校验部分

分别是头部、载荷、签名。 
头部部分header 

“alg”: “HS256”, 
“typ”: “JWT” 

alg描述的是签名算法。默认值是HS256。

将header用base64加密,得到A。

载荷部分payload 

“iss”: “发行者”, 
“sub”: 主题”, 
“aud”: “观众”, 
“exp”:”过期时间”, 
“iat”:”签发时间” 
以下可以添加自定义数据 
“id”:”1”, 
“nickname”:”昵称” 
}

根据JWT claim set[用base64]加密得到的。claim set是一个json数据,是表明用户身份的数据,可自行指定字段很灵活,也有固定字段表示特定含义(但不一定要包含特定字段,只是推荐)。
Base64算法是可逆的,不可以在载荷部分保存用户密码等敏感信息。如果业务需要,也可以采用对称密钥加密。

签名部分signature 
HMACSHA256(Base64(Header) + “.” + Base64(Payload), secret),secret是加密的盐。
签名的目的是用来验证头部和载荷是否被非法篡改。 
验签过程描述:获取token值,读取Header部分并Base64解码,得到签名算法。根据以上方法算出签名,如果签名信息不一致,说明是非法的。

三、JSON Web Token工作原理

  1. 初次登录:用户初次登录,输入用户名密码

  2. 密码验证:服务器从数据库取出用户名和密码进行验证

  3. 生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT

  4. 返还JWT:服务器的将token放在cookie中将JWT返还

  5. 带JWT的请求:以后客户端发起请求,带上cookie中的token信息。

四、jwt+redis的登录方案流程:

  • 前端服务器收到用户登录请求,传给后台API网关。

  • API网关把请求分发到用户服务里进行身份验证。
  • 后台用户服务验证通过,然后从账号信息抽取出userName、login_time等基本信息组成payload,进而组装一个JWT,把JWT放入redis(因为退出的时候无法使jwt立即作废,所以使用保存在redis中,退出的时候delete掉就可以了,鉴权的时候加一层判断jwt是否在redis里,如果不在则证明jwt已过期作废),然后包装cookie中返回到前端服务器,这就登录成功了。

  • 前端服务器拿到JWT,进行存储(可以存储在缓存中,也可以存储在数据库中,如果是浏览器,可以存储在 localStorage 中,我实现的是放入到cookie里面)

  • 登录后,再访问其他微服务的时候,前端会携带jwt访问后台,后台校验 JWT,验签通过后,返回相应资源和数据就可以了。

(这里没有将redis画出来)

结合拦截器与上篇session-cookie方式的区别:

首次登录步骤:

1.首先AuthInterceptor拦截器拦截用户请求,在preHandle中看cookie中是否有token信息,没有就接着拦截器AuthActionInterceptor拦截需要登录的url,看threadlocal当中是否有user对象,如果没有就跳转到登录页面进行登录,登录成功后会将user对象放到threadlocal中。(注意这个地方和上篇中提到的登录成功后将user放到session的不同

登录处理流程:在数据库中查询验证用户名密码,通过就讲账号信息抽取出username、email等信息组成一个payload,进而组装成一个JWT,然后将JWT放到redis当中,设置过期时间。

生成token

给定签名算法、给定载荷的map、进行签名

2.当业务逻辑处理完之后在AuthInterceptor的postHandle中,从threadlocal获取user对象中的token信息,将token放到cookie中返回给前端。

3.请求结束后在AuthInterceptor的afterCompletion将user从threadlocal中移除。

验证流程:

前端将携带jwt的cookie传到后台,AuthInterceptor会根据token验证解析出user,(注意根之前在session中取对象的不同)验证后再将user放到threadlocal中,AuthActionInterceptor一看threadlocal有user对象,直接通过。后面的步骤一样。

验证token:

1)从token的Header中拿出签名算法,看和之前生成token的签名算法是否一致。

2)验证签名,获取载荷map,从中获取用户标识email,在redis中看是否失效,如果失效,抛出未登录错误;如果未失效,更新redis的失效时间,返回用户的信息。

AuthInterceptor

@Component
public class AuthInterceptor implements HandlerInterceptor { private static final String TOKEN_COOKIE = "token"; @Autowired
private UserDao userDao; @Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler)
throws Exception {
Map<String, String[]> map = req.getParameterMap();
map.forEach((k,v) ->req.setAttribute(k, Joiner.on(",").join(v)));
String requestURI = req.getRequestURI();
if (requestURI.startsWith("/static") || requestURI.startsWith("/error")) {
return true;
}
Cookie cookie = WebUtils.getCookie(req, TOKEN_COOKIE);
if (cookie != null && StringUtils.isNoneBlank(cookie.getValue())) {
User user = userDao.getUserByToken(cookie.getValue());
if (user != null) {
req.setAttribute(CommonConstants.LOGIN_USER_ATTRIBUTE, user);
// req.setAttribute(CommonConstants.USER_ATTRIBUTE, user);
UserContext.setUser(user);
}
}
return true;
} @Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler,
ModelAndView modelAndView) throws Exception {
String requestURI = req.getRequestURI();
if (requestURI.startsWith("/static") || requestURI.startsWith("/error")) {
return ;
}
User user = UserContext.getUser();
if (user != null && StringUtils.isNoneBlank(user.getToken())) {
String token = requestURI.startsWith("logout")? "" : user.getToken();
Cookie cookie = new Cookie(TOKEN_COOKIE, token);
//此处的参数,是相对于应用服务器存放应用的文件夹的根目录而言的(比如tomcat下面的webapp),因此cookie.setPath("/");
//之后,可以在webapp文件夹下的所有应用共享cookie,而cookie.setPath("/webapp_b/");
//是指cas应用设置的cookie只能在webapp_b应用下的获得,即便是产生这个cookie的cas应用也不可以。
cookie.setPath("/");
//如果在Cookie中设置了"HttpOnly"为true属性,那么通过JavaScript脚本将无法读取到Cookie信息,这样能有效的防止XSS攻击,让网站应用更加安全。
//这里可以让js读取,置为false
cookie.setHttpOnly(false);
res.addCookie(cookie);
} } @Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
UserContext.remove();
}
}

AuthActionInterceptor

@Component
public class AuthActionInterceptor implements HandlerInterceptor { @Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler)
throws Exception {
User user = UserContext.getUser();
if (user == null) {
String msg = URLEncoder.encode("请先登录", "utf-8");
StringBuffer sb = req.getRequestURL();
String target = URLEncoder.encode(sb.toString(), "utf-8");
if ("GET".equalsIgnoreCase(req.getMethod())) {
res.sendRedirect("/accounts/signin?errorMsg=" + msg + "&target=" + target);
}else {
res.sendRedirect("/accounts/signin?errorMsg=" + msg);
}
return false;
}
return true;
} @Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler,
ModelAndView modelAndView) throws Exception { } @Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}

UserService

  /**
* 校验用户名密码、生成token并返回用户对象
* @param email
* @param passwd
* @return
*/
public User auth(String email, String passwd) {
if (StringUtils.isBlank(email) || StringUtils.isBlank(passwd)) {
throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail");
}
User user = new User();
user.setEmail(email);
user.setPasswd(HashUtils.encryPassword(passwd));
//user.setEnable(1);
List<User> list = getUserByQuery(user);
if (!list.isEmpty()) {
User retUser = list.get();
onLogin(retUser);
return retUser;
}
throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail");
} //生成token的操作
private void onLogin(User user) {
//最后一个是时间戳
String token = JwtHelper.genToken(ImmutableMap.of("email", user.getEmail(), "name", user.getName(),"ts",Instant.now().getEpochSecond()+""));
renewToken(token,user.getEmail());
user.setToken(token);
} //重新设置缓存过期时间
private String renewToken(String token, String email) {
redisTemplate.opsForValue().set(email, token);
redisTemplate.expire(email, , TimeUnit.MINUTES);
return token;
} //验证token获取登录用户
public User getLoginedUserByToken(String token) {
Map<String, String> map = null;
try {
map = JwtHelper.verifyToken(token);
} catch (Exception e) {
throw new UserException(Type.USER_NOT_LOGIN,"User not login");
}
String email = map.get("email");
Long expired = redisTemplate.getExpire(email);
//判断是否失效
if (expired > 0L) {
renewToken(token, email);
User user = getUserByEmail(email);
user.setToken(token);
return user;
}
throw new UserException(Type.USER_NOT_LOGIN,"user not login"); } private User getUserByEmail(String email) {
User user = new User();
user.setEmail(email);
List<User> list = getUserByQuery(user);
if (!list.isEmpty()) {
return list.get();
}
throw new UserException(Type.USER_NOT_FOUND,"User not found for " + email);
} public void invalidate(String token) {
Map<String, String> map = JwtHelper.verifyToken(token);
redisTemplate.delete(map.get("email"));
}

JWTHelper

public class JwtHelper {

  private static final String  SECRET = "session_secret";

  //发布者 后面一块去校验
private static final String ISSUER = "mooc_user"; //生成token的操作
public static String genToken(Map<String, String> claims){
try {
//签名算法
Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTCreator.Builder builder = JWT.create().withIssuer(ISSUER).withExpiresAt(DateUtils.addDays(new Date(), ));
//相当于将claims存储在token中
claims.forEach((k,v) -> builder.withClaim(k, v));
return builder.sign(algorithm).toString();
} catch (IllegalArgumentException | UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
//验证token
public static Map<String, String> verifyToken(String token) {
Algorithm algorithm = null;
try {
algorithm = Algorithm.HMAC256(SECRET);
} catch (IllegalArgumentException | UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
DecodedJWT jwt = verifier.verify(token);
Map<String, Claim> map = jwt.getClaims();
Map<String, String> resultMap = Maps.newHashMap();
map.forEach((k,v) -> resultMap.put(k, v.asString()));
return resultMap;
} }

基于JWT的token身份认证方案的更多相关文章

  1. ASP.NET Web API 2系列(四):基于JWT的token身份认证方案

    1.引言 通过前边的系列教程,我们可以掌握WebAPI的初步运用,但是此时的API接口任何人都可以访问,这显然不是我们想要的,这时就需要控制对它的访问,也就是WebAPI的权限验证.验证方式非常多,本 ...

  2. 基于JWT的token身份认证方案(转)

    https://www.cnblogs.com/xiangkejin/archive/2018/05/08/9011119.html 一.使用JSON Web Token的好处? 1.性能问题. JW ...

  3. .NetCore WebApi——基于JWT的简单身份认证与授权(Swagger)

    上接:.NetCore WebApi——Swagger简单配置 任何项目都有权限这一关键部分.比如我们有许多接口.有的接口允许任何人访问,另有一些接口需要认证身份之后才可以访问:以保证重要数据不会泄露 ...

  4. ASP.NET WebApi 基于JWT实现Token签名认证

    一.前言 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebServi ...

  5. 基于JWT的Token身份验证

    ​ 身份验证,是指通过一定的手段,完成对用户身份的确认.为了及时的识别发送请求的用户身份,我们调研了常见的几种认证方式,cookie.session和token. 1.Cookie ​ cookie是 ...

  6. 基于JWT的Token登录认证(一)

    1.JWT简介 JSON Web Token(缩写 JWT),是目前最流行的跨域认证解决方案. session登录认证方案:用户从客户端传递用户名.密码等信息,服务端认证后将信息存储在session中 ...

  7. 基于JWT的Token登录认证

    1.JWT简介   JSON Web Token(缩写 JWT),是目前最流行的跨域认证解决方案. 2.JWT的原理        JWT的原理是,服务器认证以后,生成一个JSON格式的对象,发回给客 ...

  8. 一种基于主板BIOS的身份认证方案及实现

    .分析AwardBIOSDOS工具cbrom cbrom的功能就是在BIOS文件中添加.删除与提取模块,以便满足用户自己的需求,用法如下: cbromBIOS文件名/参数模块名|RELEASE|EXT ...

  9. 基于session和token的身份认证方案

    一.基于session的身份认证方案 1.方案图示 2.比较通用的鉴权流程实现如下: 在整个流程中有两个拦截器. 第一个拦截器AuthInteceptor是为了每一次的请求的时候都先去session中 ...

随机推荐

  1. JSP内置对象——request 及其响应get和post请求的实例

    request对象客户端的请求信息被封装在request对象中,通过它才能了解到客户的需求,然后做出响应.它是HttpServletRequest类的实例.request对象具有请求域,即完成客户端的 ...

  2. 动态规划——最长公共子序列&&最长公共子串

      最长公共子序列(LCS)是一类典型的动归问题. 问题 给定两个序列(整数序列或者字符串)A和B,序列的子序列定义为从序列中按照索引单调增加的顺序取出若干个元素得到的新的序列,比如从序列A中取出 A ...

  3. Django学习笔记第七篇--实战练习三--关于更有层级的url请求、404错误以及其他响应函数

    一.关于更有层级的URL: 可以实现每一个APP一个子URL目录,例如app1的所有操作都在http://www.localhost1.com:5443/app1/xxxx 在工程主文件夹下的工程同名 ...

  4. 【BZOJ1495】[NOI2006]网络收费 暴力+DP

    [BZOJ1495][NOI2006]网络收费 Description 网络已经成为当今世界不可或缺的一部分.每天都有数以亿计的人使用网络进行学习.科研.娱乐等活动.然而,不可忽视的一点就是网络本身有 ...

  5. UVALive 6933 Virus synthesis(回文树)

    Viruses are usually bad for your health. How about ghting them with... other viruses? In this proble ...

  6. Exchange OAB(Offline Address Book)

    If Outlook is left running constantly in Cached Exchange Mode, it updates the Offline Address Book a ...

  7. java递归构建菜单树

    package testSimple; import java.util.ArrayList; import java.util.List; public class BuildTree { publ ...

  8. CentOS 部署openVPN

    一.openVPN工作原理 VPN技术通过密钥交换.封装.认证.加密手段在公共网络上建立起私密的隧道,保障传输数据的完整性.私密性和有效性.OpenVPN是近年来新出现的开放源码项目,实现了SSLVP ...

  9. 时间序列模式——ARIMA模型

    ARIMA模型全称为自回归积分滑动平均模型(Autoregressive Integrated Moving Average Model,简记ARIMA),是由博克思(Box)和詹金斯(Jenkins ...

  10. MPI Maelstrom---poj1502(最短路模板)

    题目链接:http://poj.org/problem?id=1502 题意:求从处理器1到其它处理器所需的最少时间是多少: 输入是下三角,如果是x表示A[i][j]不能直接联系: #include ...