登陆模块之JWT单点登录

功能描述:通过一次登录来实现多个模块之间来回跳转,数据同步

单点登录的实现方式

  • redis实现session共享
  • jwt单点登录
  • cas单点登录
  • 买单点登录服务器
    本篇博客着重讲解JWT单点登录

首先来说下什么是无状态登录
服务器不存登录的状态和数据,每次访问后台的过程中都需要把密码等用户数据全部校验一遍,校验成功才算登录。
无状态登录的好处是,数据不需要在服务端session或redis中存储,因为Token 自身包含了所有登录用户的信息,每次访问只需要调用解密验证下就可以了,省去了服务器压力,增快了性能。

什么是jwt呢

JSON Web Token(JWT)是一个非常轻巧的数据规范。这个规范允许我们使用JWT在用户端和服务器之间传递安全可靠的信息。

组成

JWT的组成一个JWT就是通过一些算法加密后的一个字符串,它由三部分组成,头部、载荷与签名。头部(Header)

头部:
于描述关于该JWT的最基本的信息,例如其类型以及签名所用的加密算法等,可以是一个JSON对象。
{“typ”:“JWT”,“alg”:“HS256”}在头部指明了签名算法是HS256算法,用这个算法计算成一些数据后, 再进行BASE64编码加密(BASE64是JWT的默认编码格式)。后的字符串如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9编码和加密不是一回事,编码只是通过编码技术让数据变成肉眼无法看出的字符串,编码后可以再进行解码,而加密是通过一些算法让数据进行了加密,让数据更安全,解密得有一定的规则才能解密,不向编码可以很轻松的进行反编译。
载荷
载荷就是存放有效信息的地方,这个名字像是车上承载物品一样,负责载重,这些有效信息包含三个部分(1)标准中注册的声明(建议但不强制使用)iss: jwt签发者sub: jwt所面向的用户aud: 接收jwt的一方exp: jwt的过期时间,这个过期时间必须要大于签发时间nbf: 定义在什么时间之前,该jwt都是不可用的.iat: jwt的签发时间jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。(2)公共的声明公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.(3)私有的声明私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。定义一个payload:{“sub”:“1234567890”,“name”:“John Doe”,“admin”:true}然后将其进行base64编码,得到Jwt的第二部分eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
签证
签名就是把头部和载荷里的东西一块进行加密,加密规则在头部中,加密后就是一个字符串jwt的第三部分是一个签证信息,这个签证信息由三部分组成:header (base64后的)payload (base64后的)secret这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ将这三部分用.连接成一个完整的字符串,构成了最终的jwt:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了上面这句话意思是不要让用户知道了你的secret,否则用户容易攻击你服务器,但即使有了secret也不要紧,因为有时间在里面,你光有secret不知道时间,再次访问生成的token也容易是错的。

如何使用jwt登录

首先加入maven依赖

1、加入依赖   
 <dependency>           
 <groupId>io.jsonwebtoken</groupId>  
           <artifactId>jjwt</artifactId>            
           <version>待加入版本</version>        
           </dependency>

前台jsp页面展示代码

<form id="login">
用户名:<input type="text" name="uname" ><br>
    密码:<input type="text" name="pwd" ><br>
    <input type="button" value="登录" onclick="login()" ><br>
</form>
<script type="text/javascript">
function login() {
    $.ajax({
        url:"<%=path %>/video/login.do",
        type:"post",
        data:$("#login").serialize(),
        success:function(data){
            if(data.state=="ok"){
                alert("登陆成功")
                //把token存放到cookie中
                $.cookie("tokenStr",data.tokenStr,{expires: 7,domain: 'xuyuanzeng.cn',path:'/'});
                //页面跳转
                window.location.href="http://www.xuyuanzeng.cn:8086/video-JsoupWeb/list.do";
            }else if(data.state=="0"){
                alert("用户名不存在");
            }else if(data.state=="2"){
                alert("密码有误")
            }

        }
    });
}
</script>

后台controller层

/**
 *@Author XXX
 *@Description //TODO Administrator
 *@Date 20:35 2019/5/26
 *@Param [user, request]
 *@return java.util.Map<java.lang.String,java.lang.Object>
 **/
@RequestMapping("login")
   @ResponseBody
public Map<String,Object> login(User user, HttpServletRequest request){
   //获取请求的头部信息
   String remoteAddr = request.getRemoteAddr();
   System.out.println(remoteAddr);
   System.out.println(user);
   Map<String,Object> result = service.login(user,remoteAddr);
   return result;
}

后台service层

@Override
public Map<String, Object> login(User user, String remoteAddr) {
   //先进行登录验证,判断用户能否根据用户名和密码登录成功
   User us = mapper.login(user);
       System.out.println("us=============="+us);
   Map<String,Object> map = new HashMap<>();
   if(us==null){
      map.put("state","0");//用户名不存在
   }
   if(!us.getPwd().equals(user.getPwd())){
      map.put("state","2");//密码有误
   }
   //登录成功
   String jwtStr = JwtUtil.createJWT(us.getUid() + "", us.getUname(), null, remoteAddr);
       System.out.println(jwtStr);
   map.put("state","ok");
   map.put("tokenStr",jwtStr);
   return map;
}
工具类

JwtUtils

public class JwtUtil {
    private static String key = "xuyuanzeng";
    private static long ttl = 1000*60*60;//一个小时

    public static String getKey() {
        return key;
    }
    public void setKey(String key) {
        this.key = key;
    }
    public static long getTtl() {
        return ttl;
    }
    public void setTtl(long ttl) {
        this.ttl = ttl;
    }
    /**
     * 生成JWT
     *
     * @param userId
     * @param subject
     * @return
     */
    public static String createJWT(String userId, String subject, String roles,String ipAddr) {

        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        JwtBuilder builder = Jwts.builder().setId(userId)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, ipAddr+key).claim("roles",roles);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
   }
        return builder.compact();
    }
    /**
     * 解析JWT
     * @param jwtStr
     * @return
     */
    public static Claims parseJWT(String jwtStr, String ipAddr){
        return  Jwts.parser()
                .setSigningKey(ipAddr+key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }
}

拦截器
public class CommonInterceptor implements HandlerInterceptor {

JwtUtil jwtUtil = new JwtUtil();

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    //获取ip地址,用于解密时使用
    String ipAddr = request.getRemoteAddr();

    //{"Authorization":"Bearer jklakjfadskljfdaskljfadskljsfadljkfadsljkfadsklj"}

    //先获取请求头信息
    String authHeader = request.getHeader("Authorization");//获取头信息
    if(authHeader==null){
        request.setAttribute("status", "error");
        return true;
    }

    if(!authHeader.startsWith("Bearer ")){
        request.setAttribute("status", "error");
        return true;
    }

    String token=authHeader.substring(7);//提取token
    Claims claims = null;
    try{
        claims = jwtUtil.parseJWT(token,ipAddr);
    }catch(Exception e){
        request.setAttribute("status", "error");
        return true;
    }
    //判断成功后返回
    request.setAttribute("claims", claims);
    request.setAttribute("status", "ok");

    return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}

}

这些是最基本的实现了JWTA登录的功能,还有一些其他具体的没有贴出来,比如说设置token过期时间等,还需要看开发需求再添加功能

基本的流程
用户从客户端登录传送到后台,后台获取请求的ip地址并验证用户是否登录成功,这里仿照京东商城来举例,如果登录失败,则把status改为0或其他,然后展示一些只读的列表信息给前台,如果登录成功,则将请求的数据放到封装好的JWT工具类中进行加密,将加密后的字符串返回来,并将status改为ok一起返回给前台

String jwtStr = JwtUtil.createJWT(us.getUid() + “”, us.getUname(), null, remoteAddr);

需要注意的是,传递的参数分别为用户id,用户名和ip地址,为什么传一个ip地址,是为了更加安全性的方面考虑,如果有人到了你的token,但只要不是在你本地的电脑登陆那么token,也不会验证成功,算是加了一层保险吧。

前台接受到了这个字符串后将其存放在客户端中如cookie中,之后用户的每一次请求都要拿着这个token去后台先进行验证,验证成功后,才能让用户进行具体的操作

为什么要使用拦截器拦截请求?

如果不使用拦截器的话,我们每个方法都去写一段代码,冗余度太高,不利于维护,我们可以将这段代码放入拦截器去实现,避免重复写代,这样前台在请求的时候就直接被拦截器过滤,过滤之后返回后台就可以知道token验证成功与否,所以加上了拦截器功能

前台token放到cookie中,那么我跳到另一个客户端,还怎么拿着token去后台验证?

这里就需要考虑到cookie的跨域资源共享,cookie做跨域共享需要用到其中的一个属性domain,具体的还请看这篇博客cookie实现跨域

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值