Java教程

单点登录和权限认证

本文主要是介绍单点登录和权限认证,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1、登录认证

1、Session-Cookie认证

wJWbK45mptZ1lVC

传统认证图

​ 基于Session-Cookie机制的认证是比较原始的一种认证方式,由于HTTP协议是纯文本,无状态的传输协议,那么在一些需要记录状态的场景就很麻烦,如淘宝的购物车,不同用户登录后看到的购物车数据是不一致的;所以需要一种机制能让服务端知道请求的客户端是谁。这样Cookie就应运而生,在客户端登录成功后,服务端返回的响应报文头中会带上一个set-cookie,客户端浏览器判断拿到这个请求头后会放在本地,每次访问cookie中指定的路径时都会带上到请求头中。

1、缺点

​ 基于Session-Cookie会带来一系列的问题。比如:

  1. 服务端的Session都保存在内存中,登录用户过多后会对服务端造成较大的压力
  2. 可能会引起CSRF攻击
  3. 分布式系统中无法进行登录认证

2、解决方案

NT95LeY1XAkDmiV

集中式session认证

​ 为解决Session存储在服务端导致的性能瓶颈的痛点,可以将session信息放入到缓存或者是数据库中进行存储,服务端在校验登录进来的信息时直接从缓存或者数据库中获取对应的信息进行比对,业内一般将Session信息放入到Redis中进行保存。

SpringSession

1 引入pom

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
    <version>1.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

2 application.yaml配置

spring:
    redis:
        database: 1
        host: localhost
        pool:
        	max-active: 20

3 开启@EnableRedisHttpSession

SpringSession原理

先从@EnableRedisHttpSession注解类分析开始

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RedisHttpSessionConfiguration.class})
@Configuration
public @interface EnableRedisHttpSession {
    int maxInactiveIntervalInSeconds() default 1800;

    String redisNamespace() default "spring:session";

    RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;

    String cleanupCron() default "0 * * * * *";
}

可以看到该注解用到了SpringImport来加载需要的bean

RedisHttpSessionConfiguration配置类中主要的功能有以下:

 @Bean
public RedisOperationsSessionRepository sessionRepository() {
    RedisTemplate<Object, Object> redisTemplate = this.createRedisTemplate();
    RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(redisTemplate);
    sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
    if (this.defaultRedisSerializer != null) {
        sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
    }

    sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
    if (StringUtils.hasText(this.redisNamespace)) {
        sessionRepository.setRedisKeyNamespace(this.redisNamespace);
    }

    sessionRepository.setRedisFlushMode(this.redisFlushMode);
    int database = this.resolveDatabase();
    sessionRepository.setDatabase(database);
    return sessionRepository;
}

@Bean
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
    SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository);
    sessionRepositoryFilter.setServletContext(this.servletContext);
    sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
    return sessionRepositoryFilter;
}

创建一个RedisOperationsSessionRepository对象提供给SessionRepositoryFilter操作session的工具。

核心就在SessionRepositoryFilter过滤器中:

@Order(-2147483598)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
   // 省略不必要的代码
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
        SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryFilter.SessionRepositoryRequestWrapper(request, response, this.servletContext);
        SessionRepositoryFilter.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response);
        try {
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } finally {
            wrappedRequest.commitSession();
        }

    }
}

可以看到该过滤器的优先级非常的高,那么请求进入服务器第一个就会来到该过滤器。该过滤器将HttpServletRequestHttpServletResponse进行包装然后开始执行下一个过滤器链路,其实包装的本质也就是将HttpServletRequest的getSession进行了加强,改为通过RedisOperationsSessionRepositoryredis中获取

cQHWaeRSKgFpILA

httpsession简单图示

2、Token认证

​ 由于Session-Cookie的诸多不便的缺点,现在大部分公司采用的技术方案是TokenToken的机制和Cookie其实差不多,本质都是用来解决HTTP协议的无状态痛点。此外Token认证的方式还能解决CSRF攻击的问题。

isvqP6D47tLpNna

token简单的时序图

1、认证token

​ 一般是将用户id和用户名称和进行双向加密后返回给客户端,客户端再每次请求时都需要再请求头中带上Token。服务端接收到token后将token发往SSO服务进行校验,校验通过后会获得登录用户的权限和基本信息,登录失败后将拒绝访问。

​ 此方式和Session-Cookie的区别在于客户端的存储方式,Cookie是直接存储在浏览器中,访问的时候直接带过去,在web场景中这样是可以的,但是一旦涉及到移动端此方式就会有问题,所以对于跨平台的接口还是采用Token的方式兼容性最好

2、JWT

JWT全名为:json web token。WT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT构成:

  • 头部
  • 载荷
  • 签证

头部信息header:

加密的算法

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

载荷payload

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

载荷中有以下几种数据:

保留数据:

iss(Issuser):代表这个JWT的签发主体;
sub(Subject):代表这个JWT的主体,即它的所有人;
aud(Audience):代表这个JWT的接收对象;
exp(Expiration time):是一个时间戳,代表这个JWT的过期时间;
nbf(Not Before):是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的;
iat(Issued at):是一个时间戳,代表这个JWT的签发时间;
jti(JWT ID):是JWT的唯一标识。

私有数据:

登录人员的基本信息

签章Signature

Jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jt的第三部分

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret');

最后JWT总体=

 base64UrlEncode(header) + '.' + base64UrlEncode(payload) + '.' + signature;

JWT和普通的Token区别在于JWT的载荷是存放在客户端中的,一般的Token都是客户端存放一个GUID通过GUID去服务端中拿到对应的登录人员信息,JWT就可以将这一步直接省略掉。这样做的好处的可以减少服务端计算的压力。

​ 缺点:

  1. 服务端无法注销,由于JWT的有效性和服务端没有依赖关系,所以服务端也无法使JWT失效。
  2. JWT无法被续签。

解决方案

针对JWT无法被服务端注销问题:

  1. token存入数据库中,每次拿到token后和数据库中的token进行比对,如果没有找到则说明token已经失效,但是这样做就散失了JWT的优势,违背了JWT无状态原则
  2. 使用内存维护一个黑名单列表,需要失效则将token存放到黑名单中,每次认证需要先判断是不是已经被拉黑的token
  3. 修改JWTSecret,不推荐。会将所有之前签发的token全部失效
  4. 保持令牌的短有效期,需要经常去签发新的token

针对JWT无法续签问题:

  1. 服务端发现Token快过期就签发一个新的token给客户端。客户端判断接口是否发送新的token,如果是则用新token来替换旧的
  2. token有效期设置到半夜,保证大部分用户白天都能登录,适用于安全性要求不高的系统
  3. token机制:accessTokenrefreshTokenaccessToken有效期较短如半个小时,refreshToken有效期较长如一天。accessToken用来获取受限的服务资源,当accessToken失效时,通过refreshToken来获取新的accessToken

3、OAuth2协议

1、名词说明

  1. Third-party application:第三方应用程序,本文中又称"客户端"(client),比如打开知乎,使用第三方登录,选择qq登录,这时候知乎就是客户端。
  2. HTTP service:HTTP服务提供商,本文中简称"服务提供商",即上例的qq。
  3. Resource Owner:资源所有者,本文中又称"用户"(user),即登录用户。
  4. User Agent:用户代理,本文中就是指浏览器。
  5. Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。

一般流行的使用都是OAuth2的授权码模式授权码模式功能最完整,流程最严密

2、授权码模式流程

  1. 用户访问客户端,客户端将页面导向认证服务器认证页面,用户点击授权,认证访问会将页面导向到客户端事先指定的重定向URL,同时附上一个授权码
  2. 客户端收到验证码,向认证服务申请token:/oauth/token?response_type=code&client_id=test&redirect_uri=重定向页面链接
  3. 认证服务核对了授权码和重定向地址无误后给客户端发送两个令牌:accessTokenrefreshTokenrefreshToken拥有较长的过期时间,但accessToken失效后,通过refreshToken进行刷新。

img

OAuth2授权码模式时序图

2、RBAC模型

1.3 - What ANSI RBAC is — Apache Directory

权限系统通常基于RBAC(Role-Based Access Control)的思想设计,拥有4个关键元素:

用户 – 角色 – 权限 – 资源。

  • 资源

被安全管理的对象(Resources页面、菜单、按钮、订单等)

  • 权限

访问和操作资源的许可(Permit删除、编辑、审批等)

  • 角色

我们通过业务流程确定一个角色,实际是确定角色并角色具备的那些权限的过程,角色所以是权限的集合,是众多权限颗粒组成;

我们通过把权限给这个角色,再把角色给用户,从而实现用户的权限,因此它承担了一个桥梁的作用。

引入角色这个概念,可以帮助我们灵活的扩展,使一个用户可以具备多种角色。

  • 用户

系统实际的操作员(User

【用户(user:谁)】被赋予【角色(role:具有1-n个权限)】,通过角色关联的【权限(permit:许可)】去访问/操作【资源(resource)】

RBAC解决了什么痛点?

在传统模型中无角色概念,直接对用户进行授权,这样会导致一些问题:

  1. 配置权限很麻烦
  2. 无法快速配置类似角色的权限
  3. 用户多身份下的角色配置很麻烦

在RBAC模型中一共分为四种:RBAC0,RBAC1,RBAC2,RBAC3,其中,RBAC1RBAC2是基于RBCA0升级衍生出来的,而RBAC3则是融合了RBAC1和RBAC2的优点创造的新的模型,一般来说RBAC3是够用的。

1、RBAC0 基本模型

image-20210817150101733

RBAC0 UML图

RBAC0定义了能构成RBAC控制系统的最小的元素集合,RBAC0由四部分构成:

  1. 用户
  2. 角色
  3. 权限
  4. 会话

当一个用户被指定角色时,该用户就拥有了此角色的所有权限。RBAC0是最基础的权限模型,权限设计时将权限赋予给角色,而不是用户,一个用户可以拥有若干角色,从而使用户可以获得更广泛的权限。就此构造成“用户- 角色- 权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。角色和权限绑定,用户被赋予相应的角色,通过多对多的关系来实现授权和授权的快速变更,从而控制用户对系统的功能使用和数据访问权限。

image-20210817110540277

image-20210817110556830

2、RBAC1 权限继承

RBAC1在RBAC0的基础上增加权限继承的改进,为什么要加入权限继承呢?

​ 设想一下这个场景,公司招了一位管理人员进入,那么管理人员的权限就得是下面被管理人员的权限的超集,如果被管理人员的角色和链路很长的话,那么这个角色需要添加很多的权限,非常的麻烦。但是引入了权限继承体系就会变得很简单,管理人员的角色只需要继承所有他下面一级角色就可以了。

角色间的继承关系可分为一般继承(General)和受限继承(Limited)。

image-20210817171637743

  • 一般继承关系

要求角色继承关系是一个绝对偏序关系(A角色继承于B角色,那么B必须是A权限的一个子集,不可以冗余多余的权限),允许角色间的多继承。

  • 受限继承关系

进一步要求角色继承关系是一个树结构,实现角色间的单继承。受限继承则增强了职责关系的分离

image-20210817150124044

RBAC1 UML图

3、RBAC2 权限约束

​ 他是RBAC的约束模型,RBAC3也是在RBAC0的基础上构建的,在RBAC0的基础上增加了约束的概念,主要引入了静态职责分离SSD(Static Separation of Duty)和动态职责分离DSD(Dynamic Separation of Duty)。

  • 静态职责分离SSD

image-20210817171949267

在设置用户角色权限的时候就应该判断,如果有冲突发生在设置时候就应该拒绝。静态职责分离有以下几种

  1. 角色互斥:用户不能同时拥有两个互斥的角色,例如会计和审计就不能由同一个用户拥有
  2. 基数互斥:一个用户能够拥有的角色是有限的,一个角色拥有的权限也是有限的,如:某个角色是为CEO准备的,那就不能有多个
  3. 先决条件角色:用户想要获得较高的权限,受限需要获得低一级的权限,如:有副经理的权限才能有总经理的权限
  • 动态职责分离DSD

image-20210817171728064

在角色分配时可以将冲突的角色赋予给同一个用户,但是在用户使用系统时,一次会话中不能同时激活两个角色。

image-20210817150147162

RBAC2 UML图

4、RBAC3 综合权限模型

也就是最全面级的权限管理,它是基于RBAC0的基础上,将RBAC1和RBAC2进行整合了,最前面,也最复杂的

image-20210817150236010

RBAC3 UML图

5、表结构的设计

RBAC最简单的表结构设计如下:

一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系

image-20210817151737601

1、用户组和角色组

当用户量非常多的时候,需要用系统逐一给用户授权是一件很繁琐的事情,就需要使用用户组,除了可以给用户授权意外还可以给用户组进行授权,然后将用户加入到对应的用户组中,这样用户拥有的权限就等于用户组的权限+用户个人角色的权限之和。

image-20210817152321589

角色组不参与权限控制,当角色量较多时,可以通过树状图来展示角色,这样方便用户去使用。

2、资源细分

接下来可以再进一步对权限进行拆分,如对于上传文件的操作,菜单的访问,页面上的按钮都属于权限控制的范围,在设计上将功能操作分为一类,将文件,菜单,页面内容分成资源一类,这样可以把操作和资源进行管理,粒度更细。

image-20210817153040557

​ 全局:

image-20210817161831478

3、权限继承

如果需要对权限要求更高还可以变成RBAC3型的权限设计,即加上权限继承和权限约束。采用受限继承式的方式完成:

image-20210817154659766

​ 只需要在角色表中添加一个父角色ID就可以,当用户登录进入系统时,会通过用户ID找到对应的角色集合,对集合进行一次遍历:递归找到该角色对应父类的权限并加入集合中。再对所有角色进行遍历完成后会得到一个用户所有的权限。这样受限继承式的权限继承就完成了。

如果需要使用普通继承式,则需要再新建一个表:

image-20210817155446936

​ 这样的处理会简单一点,当获取到用户的角色集合时,遍历获取每个角色对应的父角色ID对应的权限,然后放入到权限集合中。

6、其他几种权限模型

1、ACL模型

ACL称之为权限控制列表,规定资源可以被哪些主体进行哪些操作,是ACL模型的一种灵活实现

场景:部门隔离 适用资源:客户页面、人事页面

ACL权限模型下,权限管理是围绕资源来设定的。我们可以对不同部门的页面设定可以访问的用户。配置形式如下:

ACL配置表
    
        资源: 客户页面
                主体: 销售部(组)
                操作:增删改查
        
                主体: 王总(用户)
                操作: 增删改查
        
        资源: 人事页面
                主体: 王总(组)
                操作: 增删改查

注:主体可以是用户,也可以是组。

在维护性上,一般在粗粒度和相对静态的情况下,比较容易维护。

在细粒度情况下,比如将不同的客户视为不同的资源,1000个客户就需要配置1000张ACL表。如果1000个客户的权限配置是有规律的,那么就要对每种资源做相同的操作;如果权限配置是无规律的,那么ACL不妨也是一种恰当的解决方案。

在动态情况下,权限经常变动,每添加一名员工,都需要配置所有他需要访问的资源,这在频繁变动的大型系统里,也是很难维护的。

2、DAC模型

DAC称之为自主访问控制(DAC: Discretionary Access Control

​ 系统会识别用户,然后根据被操作对象(Subject)的权限控制列表(ACL: Access Control List)或者权限控制矩阵(ACL: Access Control Matrix)的信息来决定用户的是否能对其进行哪些操作,例如读取或修改。DACACL的一种实现,强调灵活性。纯粹的ACL,权限由中心管理员统一分配,缺乏灵活性。为了加强灵活性,在ACL的基础上,DAC模型将授权的权力下放,允许拥有权限的用户,可以自主地将权限授予其他用户

​ 而拥有对象权限的用户,又可以将该对象的权限分配给其他用户,所以称之为自主控制。在文件系统的设计中常用此模式,如微软的NTFS。image-20210817170046423

DAC的缺点在于权限控制较于分散,不方便去管理,且也是用户直接和权限挂钩。适用于权限结构简单,用户类型和数量不多的场景。

3、MAC模型

MAC称之为强制访问控制(MAC: Mandatory Access Control)是ACL模型的另一种实现,主要体现在了安全性

访问权限有两个规则判断

  1. 规定资源可以被哪些类别的主体进行哪些操作
  2. 规定主体可以对哪些等级的资源进行哪些操作

1和2同时满足才可以通过权限认证

场景:保密系统 适用资源:机密档案

MACACL的另一种实现,强调安全性。MAC会在系统中,对资源与主体,都划分类别与等级。比如,等级分为:秘密级、机密级、绝密级;类别分为:军事人员、财务人员、行政人员。比如,一份机密级的财务档案,可以确保只有主体的等级是机密级,且是财务人员才能访问。如果是机密级的行政人员就无法访问。MAC的优势就是实现资源与主体的双重验证,确保资源的交叉隔离,提高安全性。

资源配置表
        资源: 财务文档
                主体: 财务人员
                等级:机密级
                操作:查看
主体配置表
    
        主体: 李女士
                类别: 财务人员
                等级:机密级

4、ABAC模型

基于属性的权限验证(ABAC: Attribute-Based Access Control) 规定哪些属性的主体可以对哪些属性的资源在哪些属性的情况下进行哪些操作

场景:防火墙 适用资源:端口访问

ABAC中主要的一些参数:

  • 主体属性,主体相关信息,如姓名,性别,职位,年龄等等
  • 资源属性,资源相关的属性,如资源创建时间,创建位置,保密等级等等
  • 情况属性,指的是客观情况的属性,如当前时间,当前位置,当前场景
  • 操作,增删改查

设定一个权限,就是定义一条含有四类属性信息的策略Policy)。

例如:

策略表
    
        效果:允许
        操作:流入
        主体:来自上海IP的客户端
        资源:所有以33开头的端口(如3306)
        情况:在北京时间 9:00~18:00
    
        效果:禁止
        操作:流出
        主体:任何
        资源:任何
        情况:任何

一个请求来到系统中,会逐条的匹配策略。匹配的规则有很多种:

  1. 如果没有匹配到,则返回默认策略,拒绝或者接受
  2. 匹配到多个策略
    1. 必须完全匹配接受
    2. 有任意一个接受则接受

3、OpenAPI安全

接口需要开放给互联网调用需要做好安全控制,不然服务会被恶意攻击拖垮,如何做好OpenAPI,我认为有以下几点需要:

  1. 安全认证
  2. 开发者门户,用于开发应用的注册和操作
  3. 接口熔断降级处理,一般来说通过Hytrix
  4. 日志记录,在输入和输出要进行日志记录

​ 首先客户端调用开放接口之前需要去开发者门户注册一个应用账户,应用会得到自身的AppKeyAppSecret,其中AppKey是相当于用户名,AppSecret相当于密码。OpenApi调用的过程如下:

image-20210818110925043

  1. 先通过HTTPS协议登录获取Access_Token(以下称之为Token)和Refresh_Token
  2. Token认证指客户端请求黑名单接口时,认证中心基于Token生成签名

Token表结构:

image-20210818111322587

接口请求:

请求参数中至少有以下几个属性:

  • 方法名,指定调用哪个接口
  • 业务参数,接口的请求参数用JSON字符串
  • token值,登录后获取到的token
  • 签名,将AppKey,APPSecret,Token和时间戳,和业务参数进行一次哈希算法得到签名值,防止请求参数被篡改
  • 时间戳,服务端用来判断请求时间是否有效作用

image-20210818111731077

服务端签名验证的具体流程:

image-20210818111810407

这篇关于单点登录和权限认证的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!