• 基于Token的身份验证--JWT


    初次了解JWT,很基础,高手勿喷。

    基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session。

    JWT是啥?

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

        "A.B.C"

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

    怎样生成A?

    header格式为:

    {
        "typ": "JWT",
        "alg": "HS256" 
    }

    它就是一个json串,两个字段是必须的,不能多也不能少。alg字段指定了生成C的算法,默认值是HS256
    将header用base64加密,得到A
    通常,JWT库中,可以把A部分固定写死,用户最多指定一个alg的取值

    怎样计算B?

    根据JWT claim set[用base64]加密得到的。claim set是一个json数据,是表明用户身份的数据,可自行指定字段很灵活,也有固定字段表示特定含义(但不一定要包含特定字段,只是推荐)。
    这里偷懒,直接用php中的代码来表示claim set了,重在说明字段含义:

    $token = array(
        "iss" => "http://example.org",   #非必须。issuer 请求实体,可以是发起请求的用户的信息,也可是jwt的签发者。
        "iat" => 1356999524,                #非必须。issued at。 token创建时间,unix时间戳格式
        "exp" => "1548333419",            #非必须。expire 指定token的生命周期。unix时间戳格式
        "aud" => "http://example.com",   #非必须。接收该JWT的一方。
        "sub" => "jrocket@example.com",  #非必须。该JWT所面向的用户
        "nbf" => 1357000000,   # 非必须。not before。如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟。
        "jti" => '222we',     # 非必须。JWT ID。针对当前token的唯一标识
    
        "GivenName" => "Jonny", # 自定义字段
        "Surname" => "Rocket",  # 自定义字段
        "Email" => "jrocket@example.com", # 自定义字段
        "Role" => ["Manager", "Project Administrator"] # 自定义字段
    );

    JWT遵循RFC7519,里面提到claim set的json数据中,自定义字段的key是一个string,value是一个json数据。因此随意编写吧,很灵活。

    个人初学,认为一个最基本最简单最常用的claim set为:

    $token=array(
        "user_id" => 123456, #用户id,表明用户
        "iat" => 1356999524, #token发布时间
        "exp" => 1556999524, #token过期时间
    );

    将claim set加密后得到B,学名payload

    怎样计算C?

    A.B使用HS256加密(其实是用header中指定的算法),当然加密过程中还需要密钥(自行指定的一个字符串)。
    加密得到C,学名signature,其实就是一个字符串。作用类似于CRC校验,保证加密没有问题。

    好了,现在A.B.C就是生成的token了。

    怎样使用token?

    可以放到HTTP请求的请求头中,通常是Authorization字段。
    也有人说放到cookie。不过移动端app用cookie似乎不方便。

    token应用流程?

      1. 初次登录:用户初次登录,输入用户名密码
      2. 密码验证:服务器从数据库取出用户名和密码进行验证
      3. 生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT
      4. 返还JWT:服务器的HTTP RESPONSE中将JWT返还
      5. 带JWT的请求:以后客户端发起请求,HTTP REQUEST HEADER中的Authorizatio字段都要有值,为JWT
     
    使用样例:
     1 package com.feihe.util;
     2 
     3 import java.util.HashMap;
     4 import java.util.Map;
     5 import com.auth0.jwt.JWTSigner;
     6 import com.auth0.jwt.JWTVerifier;
     7 import com.auth0.jwt.internal.com.fasterxml.jackson.databind.ObjectMapper;
     8 import com.feihe.util.ConfHelper;
     9  
    10 public class JWT {
    11  
    12     private static final String SECRET = "XX#$%()(#*!()!KL<><MQLMNQNQJQK sdfkjsdrow32234545fdf>?N<:{LWPW";   // 密码
    13     
    14     private static final String EXP = "exp";
    15     
    16     private static final String PAYLOAD = "payload";
    17  
    18     /**
    19      * get jwt String of object 
    20      * 加密,传入一个对象和有效期
    21      * @param object the POJO object
    22      * @param maxAge the milliseconds of life time
    23      * @return the jwt token
    24      */
    25     public static <T> String sign(T object) {
    26         try {
    27             final JWTSigner signer = new JWTSigner(SECRET);
    28             final Map<String, Object> claims = new HashMap<String, Object>();
    29             ObjectMapper mapper = new ObjectMapper();
    30             String jsonString = mapper.writeValueAsString(object);   // 数据部分
    31             claims.put(PAYLOAD, jsonString);
    32             String jwtExp = ConfHelper.getConf("JWT_EXP");  // 设置token有效时长 暂定30分钟
    33             Long maxAge = Long.parseLong(jwtExp);
    34             claims.put(EXP, System.currentTimeMillis() + maxAge);
    35             return signer.sign(claims);
    36         } catch(Exception e) {
    37             return null;
    38         }
    39     }
    40     
    41     
    42     /**
    43      * get the object of jwt if not expired  
    44      *  解密,传入一个加密后的token字符串和解密后的类型
    45      * @param jwt
    46      * @return POJO object
    47      */
    48     public static<T> T unsign(String jwt, Class<T> classT) {
    49         final JWTVerifier verifier = new JWTVerifier(SECRET);
    50         try {
    51             final Map<String,Object> claims= verifier.verify(jwt);
    52             if (claims.containsKey(EXP) && claims.containsKey(PAYLOAD)) {
    53                 long exp = (Long)claims.get(EXP);
    54                 long currentTimeMillis = System.currentTimeMillis();
    55                 if (exp > currentTimeMillis) {    // 验证token是否过期
    56                     String json = (String)claims.get(PAYLOAD);
    57                     ObjectMapper objectMapper = new ObjectMapper();
    58                     return objectMapper.readValue(json, classT);
    59                 }
    60             }
    61             return null;
    62         } catch (Exception e) {
    63             return null;
    64         }
    65     }
    66     
    67 }

    当用户登录成功后将用户信息或想要放在token值的信息调用JWT.sign()进行处理

       

    1 String token = JWT.sign(user);   // 登录成功,进行JWT处理

    当验证登陆时将token值进行解密即可

    1 SysUser user = JWT.unsign(token, SysUser.class);

    根据结果判断登录情况。

    ps:最后额外附上Spring拦截的配置

     1     <mvc:interceptors>    
     2         <mvc:interceptor>    
     3             <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->  
     4             <mvc:mapping path="/**" />  
     5             <!-- /register 注册 和 /login 登陆 不需要拦截-->  
     6             <!-- <mvc:exclude-mapping path="/user/register" /> -->
     7             <mvc:exclude-mapping path="/user/login" />
     8             
     9             <bean class="com.feihe.filter.TokenInterceptor"></bean>    
    10         </mvc:interceptor>  
    11         <!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->  
    12     </mvc:interceptors> 
     1 package com.feihe.filter;
     2 
     3 import java.io.PrintWriter;
     4 import java.util.List;
     5 
     6 import javax.servlet.http.HttpServletRequest;
     7 import javax.servlet.http.HttpServletResponse;
     8 
     9 import org.springframework.beans.factory.annotation.Autowired;
    10 import org.springframework.web.servlet.HandlerInterceptor;
    11 import org.springframework.web.servlet.ModelAndView;
    12 
    13 import com.feihe.coa.bean.SysLogin;
    14 import com.feihe.coa.bean.SysUser;
    15 import com.feihe.coa.mapper.SysLoginMapper;
    16 import com.feihe.common.ResponseData;
    17 import com.feihe.util.JWT;
    18 
    19 import net.sf.json.JSONObject;
    20 
    21 public class TokenInterceptor implements HandlerInterceptor{
    22 
    23     @Autowired
    24     private SysLoginMapper sysLoginMapper;
    25     
    26     public void afterCompletion(HttpServletRequest request,
    27             HttpServletResponse response, Object handler, Exception arg3)
    28             throws Exception {
    29     }
    30 
    31     public void postHandle(HttpServletRequest request, HttpServletResponse response,
    32             Object handler, ModelAndView model) throws Exception {
    33     }
    34 
    35     //拦截每个请求
    36     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    37         response.setCharacterEncoding("utf-8");
    38         String token = request.getParameter("token");
    39         ResponseData responseData;
    40         //token不存在
    41         if(null != token) {
    42             SysUser user = JWT.unsign(token, SysUser.class);
    43             //解密token后的user是否为null,一般都是token过期
    44             if(null != user) {
    45                 if(user.getUserId() != 0) {
    46                     SysLogin login = new SysLogin();
    47                     login.setUserId(user.getUserId());
    48                     login.setState(1);  // 登入状态为1
    49                     // 判断是否存在该用户的“登入”状态
    50                     List<SysLogin> loginList = sysLoginMapper.selectByUidAndState(login);
    51                     if(null !=loginList && loginList.size() > 0){
    52                         if(token.equals(loginList.get(0).getToken())){
    53                             return true;
    54                         }else {
    55                             responseData = ResponseData.forbidden();
    56                             responseMessage(response, response.getWriter(), responseData);
    57                             return false;
    58                         }
    59                     }else {
    60                         responseData = ResponseData.forbidden();
    61                         responseMessage(response, response.getWriter(), responseData);
    62                         return false;
    63                     }
    64                 }else {
    65                     responseData = ResponseData.forbidden();
    66                     responseMessage(response, response.getWriter(), responseData);
    67                     return false;
    68                 }
    69             }else {
    70                 responseData = ResponseData.forbidden();
    71                 responseMessage(response, response.getWriter(), responseData);
    72                 return false;
    73             }
    74         }else {
    75             responseData = ResponseData.forbidden();
    76             responseMessage(response, response.getWriter(), responseData);
    77             return false;
    78         }
    79     }
    80 
    81     //请求不通过,返回错误信息给客户端
    82     private void responseMessage(HttpServletResponse response, PrintWriter out, ResponseData responseData) {
    83         responseData = ResponseData.forbidden();
    84         response.setContentType("application/json; charset=utf-8");  
    85         String json = JSONObject.fromObject(responseData).toString();
    86         out.print(json);
    87         out.flush();
    88         out.close();
    89     }
    90 
    91 }
  • 相关阅读:
    eclipse写javaee的时候js文件新增函数找不到
    baidu春招题:熊回家
    java自定义容器排序实现接口
    Thread主体和执行主体
    jqurey定位 id
    c中二维数组与指针访问
    ubuntu上浏览器上不了网
    前端经典面试题
    HTML,CSS,JS试题
    CSS3实现文字浮雕效果,镂刻效果,火焰文字
  • 原文地址:https://www.cnblogs.com/haw2106/p/9956497.html
Copyright © 2020-2023  润新知