SSO&OAuth2.0&JWT

SSO单点登录
SSO是为了解决一个用户在鉴权服务器登陆过一次以后,可以在任何应用中畅通无阻,一次登陆,多系统访问

一、使用Session共享(维护会话)

在这里插入图片描述

方式一、定向流量分发
前端用户访问时候,根据用户的某个属性(列如:userId)生成hash值,进行hash取模,从而固定以后访问都是同一台机器。
问题:

  • 会有流量倾斜的问题(可能出现大部分用户只访问某一台机器)
  • 当服务器扩容,用户就需要从新登录

方式二、使用统一权限认证中心服务器
使用SpringSession+Redis,把用户的session储到redis中。

二、OAuth2.0

在这里插入图片描述

OAuth 的核心就是向第三方应用颁发令牌

能够做到一个应用向用户征求授权,来向资源服务器获取资源

应用场景

  • 单点登录
  • 第三方登录(第三方授权)

角色

Resource Owner

资源拥有者,对应微信的每个用户微信上设置的个人信息是属于每个用户的,不属于腾讯。

Resource Server

资源服务器,一般就是用户数据的一些操作(增删改查)的REST API,比如微信的获取用户基本信息的接口。

Client Application

第三方客户端,对比微信中就是各种微信公众号开发的应用,第三方应用经过认证服务器授权后即可访问资源服务器的REST API来获取用户的头像、性别、地区等基本信息。

Authorization Server

认证服务器,验证第三方客户端是否合法。如果合法就给客户端颁布token,第三方通过token来调用资源服务器的API。

授权认证流程

在这里插入图片描述

授权类型

anthorization_code

授权码类型,适用于Web Server Application。模式为:客户端先调用/oauth/authorize/进到用户授权界面,用户授权后返回code,客户端然后根据code和appSecret获取access token

implicit 简化类型,相对于授权码类型少了授权码获取的步骤。客户端应用授权后认证服务器会直接将access token放在客户端的url。客户端解析url获取token。这种方式其实是不太安全的,可以通过https安全通道缩短access token的有效时间来较少风险。

password

密码类型,客户端应用通过用户的username和password获access token。适用于资源服务器、认证服务器与客户端具有完全的信任关系,因为要将用户要将用户的用户名密码直接发送给客户端应用,客户端应用通过用户发送过来的用户名密码获取token,然后访问资源服务器资源。比如支付宝就可以直接用淘宝用户名和密码登录,因为它们属于同一家公司,彼此充分信任

client_credentials

客户端类型,是不需要用户参与的一种方式,用于不同服务之间的对接。比如自己开发的应用程序要调用短信验证码服务商的服务,调用地图服务商的服务、调用手机消息推送服务商的服务。当需要调用服务是可以直接使用服务商给的appIDappSecret来获取token,得到token之后就可以直接调用服务。

其他
  • scope:访问资源服务器的哪些作用域。一组权限,Role
  • refresh token:当access token 过期后,可以通过refresh token重新获取access token。

应用场景

微信授权

三、JWT(JSON Web Token)

JWT是一种基于JSON的令牌安全验证(在某些
特定的场合可以替代Session或者Cookie),一次生成随处校验

先看这个几个问题可以,知道如何解决吗?

  • token 被截获了怎么办?
  • JWT怎么把用户踢下线?
  • JWT如何续期?
  • JWT 如何实现控制登录设备的个数?
  • JWT有什么缺点?

token 被截获了怎么办?

  • 防止token被截获,设置有效期,有效期不宜太长
  • 防止token被窃取,传输数据采用非对称加密或二次加密,所以使用https来传输。
  • 后端token绑定ip,进行校验ip改变就重新登录,可以防止他人利用自己身份进行操作。

JWT怎么把用户踢下线?

  • 方案一:添加黑白名单,JWT设置一个较短的有效时间,在登录时间进行校验。
  • 方案二:本身把用户踢下线,本质就是不让用户使用所有功能,把该用户的所有功能禁用了,效果也是一样的。

JWT如何续期?

  • 方案一:每一次来一次请求,就刷新一个新的jwt返回。但是这个方法不仅暴力不优雅,每次请求都要做jwt的加密解密,会带来性能问题。
  • 方案二:jwt设置为无时间限制,使用redis为每个jwt设置有效时间。增加一个拦截器,每次请求中带着token来到网关时,重置redis中token的过期时间,达到续期的效果。

JWT 如何实现控制登录设备的个数?

方案一:(多设备通知)
     将用户id、用户类型、用户登录IP等做为用户信息,使用JWT根据用户信息生成token,对用户信息做一次hash(MD5算法),将hash值作为用户key,token作为值存入Redis;
     用户进行代理时,系统先根据用户信息做一次hash生成用户key,再根据用户key查询redis里此key是否存在,不存在说明token已失效,则生成新的token进行登录;
     若存在则token未失效,接下来从token中解析出IP地址,与当前请求中的IP进行对比,若一致则认为是来自同一台设备上的操作,否则则认为是用户切换其他设备上进行登录,提示用户已在其他设备登录。
此处有个问题是同一个网关出口获取到的IP是相同的。

方案二:
     用户登录成功 生成token给到用户 同时存储到redis中(key值为用户名(标识))
     用户再次访问系统请求参数中带有token信息
后台拦截进行比对(如果设置登录设备数量为5,小于就直接放行)
----------|如果token匹配成功
----------------|就放行
----------|匹配不成功 说明两个token不一致
----------------|开始比对对应的时间戳,后者时间戳 大于前者 就把当前token覆盖
(如果旧的token请求再次进来 期时间戳就晚于当前redis中的token时间(token已经更新)判断其为被踢出的用户提示重新登录)

JWT有什么缺点?

  • JWT缺点就是发出的token就无法控制了
  • 由于无状态,很多的数据都被放到JWT里,那载荷会更大,经过编码之后导致JWT非常长,JWT的http请求比使用session的开销大得多

优点:适合分布式环境,服务器端不用维护会话,减少了对会话的维护成本(无状态)

JWT组成

头部信息(header)

作用:指定该JWT使用的签名,将下面的json,用Base64URL 算法转成字符串,即为header。

  {
      “alg”:HS256,// 签名算法
      “typ”:JWT//token类型  
  }
消息体playload

也就是负载的信息,这个 JSON 对象也要使用 Base64URL 算法转成字符串。

{
"exp" (expiration time):过期时间
"sub" (subject):主题,一般用用户id,用来标识用户会话
"iat" (Issued At):签发时间
}
签名( signature)

Signature 部分是对前两部分的签名,防止数据篡改。
需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
头部、声明、签名用 . 号

最终:把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔连在一起就得到了我们要的JWT

代码实现
public class JwtUtils {

    private static long tokenExpiration = 24*60*60*1000;
    private static String tokenSignKey = "A1t2g3uigu123456";

    private static Key getKeyInstance(){
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] bytes = DatatypeConverter.parseBase64Binary(tokenSignKey);
        return new SecretKeySpec(bytes,signatureAlgorithm.getJcaName());
    }

    public static String createToken(Long userId, String userName) {
        String token = Jwts.builder()
                .setSubject("SRB-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .claim("userId", userId)
                .claim("userName", userName)
                .signWith(SignatureAlgorithm.HS512, getKeyInstance())
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }

    /**
     * 判断token是否有效
     * @param token
     * @return
     */
    public static boolean checkToken(String token) {
        if(StringUtils.isEmpty(token)) {
            return false;
        }
        try {
            Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    public static Long getUserId(String token) {
        Claims claims = getClaims(token);
        Integer userId = (Integer)claims.get("userId");
        return userId.longValue();
    }

    public static String getUserName(String token) {
        Claims claims = getClaims(token);
        return (String)claims.get("userName");
    }

    public static void removeToken(String token) {
        //jwttoken无需删除,客户端扔掉即可。
    }

    /**
     * 校验token并返回Claims
     * @param token
     * @return
     */
    private static Claims getClaims(String token) {
        if(StringUtils.isEmpty(token)) {
            // LOGIN_AUTH_ERROR(-211, "未登录"),
            throw new SrbException(ResponseEnum.LOGIN_AUTH_ERROR);
        }
        try {
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            return claims;
        } catch (Exception e) {
            throw new SrbException(ResponseEnum.LOGIN_AUTH_ERROR);
        }
    }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值