gateway-统一权限-认证

这篇具有很好参考价值的文章主要介绍了gateway-统一权限-认证。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

int## 基础名词概念
**权限:**属于系统的安全范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全控制策略用户可以访问而且只能访问自己被授权的资源,主要包括用户身份认证和请求鉴权两部分,简称认证鉴权
认证判断一个用户是否为合法用户的处理过程,最常用的简单身份认证是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确
gateway-统一权限-认证

鉴权:即访问控制,控制谁能访问那些资源;进行身份认证后需要分配权限可访问的系统资源,对于某些资源没有权限是无法访问的,如下图所示

gateway-统一权限-认证
权限控制:用户是某个角色、或拥有某个资源时,才可访问系统资源我们称之为权限控制,权限控制分为下列2类型:
基于角色
RBAC基于角色的访问控制是以角色为中心进行访问控制,比如:主体的角色为总经理可以查询企业运营报表,查询员工薪资信息等,访问控制流程如下:
gateway-统一权限-认证
基于资源
RBAC基于资源的访问控制,是以资源中心进行访问控制,企业中常用的权限管理方法,实现思路是:将系统操作的每个URL配置在资源表中,将资源对应到角色,将角色分配给用户,用户访问系统功能通过Filter进行过滤,过滤器获取到用户的url,只要访问的url是用户分配角色中的URL是用户分配角色的url则进行访问,其具体流程如下:
gateway-统一权限-认证
匿名资源:无需认证鉴权就可以访问的资源
公共资源:只需登录既可以访问的资源

多平台权限控制

xxxx作为一个SaaS平台,商家提供运营主体信息后,运营平台会为商家开通系统,各个商家平台都需要在运营平台的管理下去工作:
1、运营平台可以管理所有商家平台的企业信息
2、运营平台可以管理所有商家平台的资源信息
3、运营平台可以管理所有商家平台的角色信息
4、运营平台可以管理商家平台的用户信息

第二章 基础信息简介

在开始做权限开发之前我们需要看下权限设计的数据库结构:
gateway-统一权限-认证
通过上图,我们可以得到如下的信息:
一个企业可以有多个用户
一个用户可以有多个角色
一个角色可以有多个资源
这个是经典的权限设计,也就是:企业,用户,角色,资源通过它们可以来完成整个权限的控制。

企业信息

商家想申请入驻平台,首先在申请页面【也可以后端录入】进行信息填写,填写完成【运营平台】对商家资质进行审核,审核通过后商家即可入职使用,如图所示:

gateway-统一权限-认证
数据库结构设计

CREATE TABLE `tab_enterprise` (
  `id` bigint(18) NOT NULL,
  `enterprise_id` bigint(18) NOT NULL COMMENT '商户ID【系统内部识别使用】',
  `enterprise_name` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '企业名称',
  `enterprise_no` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '工商号',
  `province` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '地址(省)',
  `area` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '地址(区)',
  `city` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '地址(市)',
  `address` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '详细地址',
  `status` varchar(8) COLLATE utf8_bin NOT NULL COMMENT '状态(试用:trial,停用:stop,正式:official)',
  `proposer_Id` bigint(18) DEFAULT NULL COMMENT '申请人Id',
  `enable_flag` varchar(18) CHARACTER SET utf8 NOT NULL COMMENT '是否有效',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '创建时间',
  `expire_time` datetime NOT NULL COMMENT '到期时间 (试用下是默认七天后到期,状态改成停用)',
  `web_site` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '商户门店web站点',
  `sharding_id` bigint(18) NOT NULL COMMENT '分库id',
  `app_web_site` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '商户h5web站点',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='企业账号管理';

实现细节
对于这个功能的CRUD这里就不做赘述,这里主要思考2个问题:
为什么我们要为商家绑定域名?
通过一个图来分析下整个的工作流程
gateway-统一权限-认证
员工在浏览器中发起ppsk.shop.eehp.cn的访问请求
阿里云域名解析会把ppsk.shop.eehp网址解析到阿里云ECS服务器101.101.108.2
阿里云ECS服务器【101.101.108.2】宿主机会把信息转发到docker-nginx服务器
docker-nginx服务器配置的serverName【*.shop.eehp.cn】转发到gateway服务
gateway服务根据ppsk.shop.eehp.cn兑换企业号100001
根据企业号100001访问目标的商家A
域名和企业号如何建立关联
在security模块的initEnterpriseWeb方法,这里主要有四个方法:
init:初始化企业站点信息到redis,此方法上有==@PostConstruct==注解,表示项目启动时即加载信息
addWebSiteforRedis:添加缓存中的站点,当我们【新增】企业主体信息时调用此方法
deleteWebSiteForRedis:移除缓存中的站点,当我们【删除,仅用】企业主体信息时调用此方法
updateWebSiteforRedis:更新缓存中的站点,当我们修改禁用企业主体信息时调用此方法

/**
 * @ClassName initEnterpriseWebSIteInfo.java
 * @Description 初始化企业站点信息到redis
 */
@Component
public class InitEnterpriseSite {

    @Autowired
    IEnterpriseService enterpriseService;

    @Autowired
    RedissonClient redissonClient;

    /**
     *获得两时间的秒间隔
     */
    public Long secondInterval(Date date1, Date date2) {
        long secondInterval = (date2.getTime() - date1.getTime()) / 1000;
        return secondInterval;
    }

    /***
     * @description 初始化企业站点信息到redis
     */
    @PostConstruct
    public void init(){
        QueryWrapper<Enterprise> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(Enterprise::getEnableFlag, SuperConstant.YES)
            .and(wrapper->wrapper
                .eq(Enterprise::getStatus,SuperConstant.TRIAL)
                .or()
                .eq(Enterprise::getStatus,SuperConstant.OFFICIAL));
        List<Enterprise> list = enterpriseService.list(queryWrapper);
        List<EnterpriseVo> enterpriseVos = BeanConv.toBeanList(list, EnterpriseVo.class);
        for (EnterpriseVo enterpriseVo : enterpriseVos) {
            String webSiteKey = SecurityCacheConstant.WEBSITE+enterpriseVo.getWebSite();
            RBucket<EnterpriseVo> webSiteBucket = redissonClient.getBucket(webSiteKey);
            String appWebSiteKey = SecurityCacheConstant.APP_WEBSITE+enterpriseVo.getAppWebSite();
            RBucket<EnterpriseVo> appWebSiteBucket = redissonClient.getBucket(appWebSiteKey);
            Long secondInterval = this.secondInterval(new Date(), enterpriseVo.getExpireTime());
            if (secondInterval.longValue()>0){
                webSiteBucket.set(enterpriseVo,secondInterval, TimeUnit.SECONDS);
                appWebSiteBucket.set(enterpriseVo,secondInterval, TimeUnit.SECONDS);
            }
        }
    }

    /***
     * @description 添加缓存中的站点
     * @param enterpriseVo 企业号
     * @return:
     */
    public void addWebSiteforRedis(EnterpriseVo enterpriseVo){
        String webSiteKey = SecurityCacheConstant.WEBSITE+enterpriseVo.getWebSite();
        RBucket<EnterpriseVo> webSiteBucket = redissonClient.getBucket(webSiteKey);
        String appWebSiteKey = SecurityCacheConstant.APP_WEBSITE+enterpriseVo.getAppWebSite();
        RBucket<EnterpriseVo> appWebSiteBucket = redissonClient.getBucket(appWebSiteKey);
        Long secondInterval = this.secondInterval(new Date(), enterpriseVo.getExpireTime());
        if (secondInterval.longValue()>0){
            webSiteBucket.trySet(enterpriseVo,secondInterval, TimeUnit.SECONDS);
            appWebSiteBucket.trySet(enterpriseVo,secondInterval, TimeUnit.SECONDS);
        }
    }

    /***
     * @description 移除缓存中的站点
     * @param enterpriseVo 企业号
     * @return:
     */
    public void deleteWebSiteforRedis( EnterpriseVo enterpriseVo){
        String webSiteKey = SecurityCacheConstant.WEBSITE+enterpriseVo.getWebSite();
        RBucket<EnterpriseVo> webSiteBucket = redissonClient.getBucket(webSiteKey);
        String appWebSiteKey = SecurityCacheConstant.APP_WEBSITE+enterpriseVo.getAppWebSite();
        RBucket<EnterpriseVo> appWebSiteBucket = redissonClient.getBucket(appWebSiteKey);
        webSiteBucket.delete();
        appWebSiteBucket.delete();
    }


    /***
     * @description 更新缓存中的站点
     * @param enterpriseVo 企业号
     * @return:
     */
    public void updataWebSiteforRedis(EnterpriseVo enterpriseVo){
        String webSiteKey = SecurityCacheConstant.WEBSITE+enterpriseVo.getWebSite();
        RBucket<EnterpriseVo> webSiteBucket = redissonClient.getBucket(webSiteKey);
        String appWebSiteKey = SecurityCacheConstant.APP_WEBSITE+enterpriseVo.getAppWebSite();
        RBucket<EnterpriseVo> appWebSiteBucket = redissonClient.getBucket(appWebSiteKey);
        Long secondInterval = this.secondInterval(new Date(), enterpriseVo.getExpireTime());
        if (secondInterval.longValue()>0){
            webSiteBucket.set(enterpriseVo,secondInterval, TimeUnit.SECONDS);
            appWebSiteBucket.set(enterpriseVo,secondInterval, TimeUnit.SECONDS);
        }
    }

考虑到企业表【Enterprise】做CRUD的时候会影响缓存的更新,需要在EnterpriseFaceImpl中做同步的处理

@Override
public EnterpriseVo createEnterprise(EnterpriseVo eterperiseVo) {
    Enterprise enterpriseResult = EnterpriseService.createEnterprise(eterperiseVo);
    //同步缓存
    if (!EmptyUtil.isNullOrEmpty(enterpriseResult)){
        initEnterpriseWebSiteInfo.addWebSiteforRedis(eterperiseVo.getWebSite(),eterperiseVo);
    }
    return BeanConv.toBean(enterpriseResult,EnterpriseVo.class);
}

@Override
public Boolean updateEnterprise(EnterpriseVo enterpriseVo) {
    Boolean flag = EnterpriseService.updateEnterprise(enterpriseVo);
    //同步缓存
    if (flag){
        if (enterpriseVo.getEnableFlag().equals(SuperConstant.YES)){
            initEnterpriseWebSiteInfo.updataWebSiteforRedis(enterpriseVo.getWebSite(),enterpriseVo);
        }else {
            initEnterpriseWebSiteInfo.deleteWebSiteforRedis(enterpriseVo.getWebSite(),enterpriseVo);
        }
    }
    return flag;
}

@Override
public Boolean deleteEnterprise(String[] checkedIds) {
    //同步缓存
    for (String checkedId : checkedIds) {
        Enterprise enterprise = EnterpriseService.getById(checkedId);
        EnterpriseVo enterpriseVo = BeanConv.toBean(enterprise, EnterpriseVo.class);
        initEnterpriseWebSiteInfo.deleteWebSiteforRedis(enterprise.getWebSite(),enterpriseVo);
    }
    Boolean flag =  EnterpriseService.deleteEnterprise(checkedIds);
    return flag;
}
CREATE TABLE `tab_resource` (
  `id` bigint(18) NOT NULL COMMENT '主键',
  `parent_id` bigint(18) DEFAULT NULL COMMENT '父Id',
  `resource_name` varchar(36) DEFAULT NULL COMMENT '资源名称',
  `request_path` varchar(200) DEFAULT NULL COMMENT '资源路径',
  `icon` varchar(20) DEFAULT NULL COMMENT '图标',
  `is_leaf` varchar(18) DEFAULT NULL COMMENT '是否叶子节点',
  `resource_type` varchar(36) DEFAULT NULL COMMENT '资源类型',
  `sort_no` int(11) DEFAULT NULL COMMENT '排序',
  `description` varchar(200) DEFAULT NULL COMMENT '描述',
  `system_code` varchar(36) DEFAULT NULL COMMENT '系统归属',
  `is_system_root` varchar(18) DEFAULT NULL COMMENT '是否根节点',
  `enable_flag` varchar(18) DEFAULT NULL COMMENT '是否有效',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_time` datetime DEFAULT NULL COMMENT '创建时间',
  `sharding_id` bigint(18) DEFAULT NULL,
  `label` varchar(200) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='资源表';

为了解决信息系统中的访问控制管理的问题,适当简化授权工作量,提高权限管理效率,需要建立基于角色的多系统授权管理模型,其业务管理模式如下:
运营平台系统管理员负责角色的权限及用户权限及用户分配。
运营平台系统管理员负责角色的权限匹配,同时赋予商家管理员对角色分配用户的权限,定义标准角色,实现权限管理的部分下放
在此模式下,系统管理员不再兼任单位管理员工作,需要实现权限的多级下放,其架构设计如图所示
gateway-统一权限-认证

数据库结构:
角色表:

CREATE TABLE `tab_role` (
  `id` bigint(18) NOT NULL COMMENT '主键',
  `role_name` varchar(36) DEFAULT NULL COMMENT '角色名称',
  `label` varchar(36) DEFAULT NULL COMMENT '角色标识',
  `description` varchar(200) DEFAULT NULL COMMENT '角色描述',
  `sort_no` int(36) DEFAULT NULL COMMENT '排序',
  `enable_flag` varchar(18) DEFAULT NULL COMMENT '是否有效',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_time` datetime DEFAULT NULL COMMENT '创建时间',
  `sharding_id` bigint(18) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='角色表';

角色资源表:

CREATE TABLE `tab_role_resource` (
  `id` bigint(18) NOT NULL,
  `enable_flag` varchar(18) DEFAULT NULL,
  `role_id` bigint(18) DEFAULT NULL,
  `resource_id` bigint(18) DEFAULT NULL,
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_time` datetime DEFAULT NULL COMMENT '创建时间',
  `sharding_id` bigint(18) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='角色资源表';

用户信息

xxx系统中用户分为:运营商员工、商家平台员工,其信息的维护规则如下:
运营平台系统管理员:可以定义,管理所有的用户,并且从角色中选择权限
运营平台系统管理员:负责定义角色,同时赋予商家管理员对角色,商家管理员分配用户的权限(定义标准角色,实现权限管理的部分下放)。
多个运营商之间的员工信息是相互隔绝的

数据库结构
用户表:

CREATE TABLE `tab_user` (
  `id` bigint(18) NOT NULL COMMENT '主键',
  `store_id` bigint(32) DEFAULT NULL COMMENT '门店Id',
  `enterprise_id` bigint(18) NOT NULL COMMENT '商户号',
  `username` varchar(36) DEFAULT NULL COMMENT '登录名称',
  `real_name` varchar(36) DEFAULT NULL COMMENT '真实姓名',
  `password` varchar(150) DEFAULT NULL COMMENT '密码',
  `sex` varchar(11) DEFAULT NULL COMMENT '性别',
  `mobil` varchar(36) DEFAULT NULL COMMENT '电话',
  `email` varchar(36) DEFAULT NULL COMMENT '邮箱',
  `discount_limit` decimal(10,2) DEFAULT NULL COMMENT '折扣上线',
  `reduce_limit` decimal(10,2) DEFAULT NULL COMMENT '减免金额上线',
  `duties` varchar(36) DEFAULT NULL COMMENT '职务',
  `sort_no` int(11) DEFAULT NULL COMMENT '排序',
  `enable_flag` varchar(18) DEFAULT NULL COMMENT '是否有效',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_time` datetime DEFAULT NULL COMMENT '创建时间',
  `sharding_id` bigint(18) DEFAULT NULL COMMENT '分库id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户表';

用户角色表

CREATE TABLE `tab_user_role` (
  `id` bigint(36) NOT NULL,
  `enable_flag` varchar(18) DEFAULT NULL,
  `user_id` bigint(18) DEFAULT NULL,
  `role_id` bigint(18) DEFAULT NULL,
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_time` datetime DEFAULT NULL COMMENT '创建时间',
  `sharding_id` bigint(18) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户角色表';

统一权限认证

认证:判断一个用户是否为合法用户的处理过程,最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户和口令一致,来判断用户身份是否正确,如下图所示:
gateway-统一权限-认证
集成的方式:本权限是基于spring-cloud-gateway网关来做权限的控制,因为gateway是基于响应式webflux【响应式编程】的机制进行处理的,所以这里的用法和原本httpservlet是有所区别的,首先我们来看一下整体的模块依赖处理:
gateway-统一权限-认证
商家发起请求到gateway-shop网关
gateway网关调用model-security-client,进行认证或鉴权过滤器
model-security-client作为服务消费者通过model-security-interface接口进行用户认证、鉴权接口调用
model-security-producer作为服务生产者进行当前用户登录、角色、权限的查询
model-security-client:模块是本权限系统核心,他提供了具体的认证、鉴权的逻辑,如果一个gateway想要实现权限的控制只需要依赖此客户端
gateway-统一权限-认证
gateway-统一权限-认证

认证流程总述

gateway-统一权限-认证
认证总体流程如下:
用户在登录页选择登录方式
判断登录方式是短信登录、账号密码登录,进行域名校验,兑换企业ID【enterpriseid】
通过服务鉴权转换器ServerAuthenticationConverter构建权限对象Authentication
Authentication对象交于认证管理器【ReactiveAuthenticationManager】进行认证

服务鉴权转换器
gateway-统一权限-认证
ServerAuthenticationConverter:主要是负责表单的自动转换,在spring-security中的默认的登录页面是long页面,我们需要从表单中获取用户名和密码或者用户短信验证码

package com.xxxx.restkeeper.converter;

import com.itheima.restkeeper.converter.LoginConverter;
import com.itheima.restkeeper.utils.EmptyUtil;
import com.itheima.restkeeper.utils.RegisterBeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @ClassName ReactiveFormLoginAuthenticationConverter.java
 * @Description 自定义表单转换
 */
@Component
public class ReactiveServerAuthenticationConverter implements ServerAuthenticationConverter {

    //登录方式
    private String loginTypeParameter = "loginType";

    //站点类型
    private String siteTypeParameter = "siteType";

    @Autowired
    RegisterBeanHandler registerBeanHandler;

    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange) {
        String loginType = exchange.getRequest().getHeaders().getFirst("loginType");
        String siteType = exchange.getRequest().getHeaders().getFirst("siteType");
        if (EmptyUtil.isNullOrEmpty(loginType)){
            throw  new BadCredentialsException("客户登陆异常");
        }
        LoginConverter loginConverter = registerBeanHandler.getBean(loginType, LoginConverter.class);
        return loginConverter.convert(exchange,loginType,siteType);
    }
}

**LoginConverter:**登录转换接口定义

import org.springframework.security.core.Authentication;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @ClassName LoginTypeConverterHandler.java
 * @Description 登录类型转换接口
 */
public interface LoginConverter {

    /***
     * @description 登录转换
     * @param exchange
     * @param loginType
     * @param siteType
     * @return
     */
    public Mono<Authentication> convert(ServerWebExchange exchange,
                                        String loginType,
                                        String siteType);
}

**MobilLoginConverter:**手机验证码登录转换器

import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @ClassName SystemMobilLoginConverter.java
 * @Description 手机登录
 */
@Component("mobilLogin")
public class MobilLoginConverter implements LoginConverter {

    //手机
    private String mobileParameter = "mobile";

    //验证码
    private String authCodeParameter = "authCode";

    @Autowired
    RedissonClient redissonClient;

    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange,
                                        String loginType,
                                        String siteType) {
        String hostName = exchange.getRequest().getURI().getHost();
        String key = null;
        if (siteType.equals(SuperConstant.WEBSITE)){
            key = SecurityCacheConstant.WEBSITE+hostName;
        }else if (siteType.equals(SuperConstant.APP_WEBSITE)){
            key = SecurityCacheConstant.APP_WEBSITE+hostName;
        }else {
            return  Mono.error(new BadCredentialsException("站点类型未定义"));
        }
        //域名校验
        RBucket<EnterpriseVo> bucket = redissonClient.getBucket(key);
        EnterpriseVo enterpriseVo = bucket.get();
        if (EmptyUtil.isNullOrEmpty(enterpriseVo)){
            return  Mono.error(new BadCredentialsException("Invalid hostName"));
        }
        //获得enterpriseId
        String enterpriseId = String.valueOf(enterpriseVo.getEnterpriseId());
        return exchange.getFormData().map( data -> {
            String mobile = data.getFirst(this.mobileParameter);
            String authCode = data.getFirst(this.authCodeParameter);
            if (EmptyUtil.isNullOrEmpty(mobile)||
                    EmptyUtil.isNullOrEmpty(authCode)){
                throw  new BadCredentialsException("客户登陆异常");
            }
            String principal = mobile+":"+enterpriseId+":"+loginType+":"+siteType;
            return new UsernamePasswordAuthenticationToken(principal, authCode);
        });
    }
}

**UsernameLoginConverter:**用户名密码登录

import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @ClassName SysUsernameLoginConverterHandler.java
 * @Description 系统账号密码转换
 */
@Component("usernameLogin")
public class UsernameLoginConverter implements LoginConverter {

    //账号
    private String usernameParameter = "username";

    //密码
    private String passwordParameter = "password";

    @Autowired
    RedissonClient redissonClient;

    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange,
                                        String loginType,
                                        String siteType) {
        String hostName = exchange.getRequest().getURI().getHost();
        String key = null;
        if (siteType.equals(SuperConstant.WEBSITE)){
            key = SecurityCacheConstant.WEBSITE+hostName;
        }else if (siteType.equals(SuperConstant.APP_WEBSITE)){
            key = SecurityCacheConstant.APP_WEBSITE+hostName;
        }else {
            return  Mono.error(new BadCredentialsException("站点类型未定义"));
        }
        //域名校验
        RBucket<EnterpriseVo> bucket = redissonClient.getBucket(key);
        EnterpriseVo enterpriseVo = bucket.get();
        if (EmptyUtil.isNullOrEmpty(enterpriseVo)){
            return  Mono.error(new BadCredentialsException("Invalid hostName"));
        }
        //获得enterpriseId
        String enterpriseId = String.valueOf(enterpriseVo.getEnterpriseId());
        return exchange.getFormData().map( data -> {
            String username = data.getFirst(this.usernameParameter);
            String password = data.getFirst(this.passwordParameter);
            if (EmptyUtil.isNullOrEmpty(username)||
                EmptyUtil.isNullOrEmpty(password)){
                throw  new BadCredentialsException("用户登陆异常");
            }
            String principal = username+":"+enterpriseId+":"+loginType+":"+siteType;
            return new UsernamePasswordAuthenticationToken(principal, password);
        });
    }
}

用户信息明细

gateway-统一权限-认证
ReactiveUserDetailsServiceImpl:主要负责认证过程,对于用户信息的获得,这里分为四种获得方式
user账户登录
user手机登录
customer账户登录
customer手机登录
统一调用UserAdapterFace或者CustomerAdapterFace进行用户登录消息的获得方式

import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.HashSet;

/**
 * @ClassName ReactiveUserDetailsServiceImpl.java
 * @Description 支持flum的身份类实现ReactiveUserDetailsService接口
 */
@Component("reactiveUserDetailsService")
@Slf4j
public class ReactiveUserDetailsServiceImpl implements ReactiveUserDetailsService{

    //调用RPC原创服务
    @DubboReference(version = "${dubbo.application.version}", check = false)
    UserAdapterFace userAdapterFace;

    //调用RPC原创服务
    @DubboReference(version = "${dubbo.application.version}", check = false)
    CustomerAdapterFace customerAdapterFace;

    //验证身份
    @Override
    public Mono<UserDetails> findByUsername(String principal) {
        String[] principals = principal.split(":");
        if (principals.length!=4){
            log.warn("用户:{}登录信息不完整",principal);
            return Mono.empty();
        }
        String mobile =principals[0];
        String username =principals[0];
        Long enterpriseId =Long.valueOf(principals[1]);
        String loginType =principals[2];
        String siteType =principals[3];
        UserVo userVo = null;
        //user账户登录
        if (loginType.equals(SuperConstant.USERNAME_LOGIN)
            &&siteType.equals(SuperConstant.WEBSITE)){
            userVo = userAdapterFace.findUserByUsernameAndEnterpriseId(username, enterpriseId);
        }
        //user手机登录
        if (loginType.equals(SuperConstant.MOBIL_LOGIN)
            &&siteType.equals(SuperConstant.WEBSITE)){
            userVo = userAdapterFace.findUserByMobilAndEnterpriseId(mobile, enterpriseId);
        }
        //customer账户登录
        if (loginType.equals(SuperConstant.USERNAME_LOGIN)
            &&siteType.equals(SuperConstant.APP_WEBSITE)){
            userVo = customerAdapterFace.findCustomerByUsernameAndEnterpriseId(username, enterpriseId);
        }
        //customer手机登录
        if (loginType.equals(SuperConstant.MOBIL_LOGIN)
            &&siteType.equals(SuperConstant.APP_WEBSITE)){
            userVo = customerAdapterFace.findCustomerByMobilAndEnterpriseId(mobile, enterpriseId);
        }
        if (EmptyUtil.isNullOrEmpty(userVo)){
            log.warn("用户:{}不存在",principal);
            return Mono.empty();
        }
        UserAuth userAuth = new UserAuth(
            userVo.getUsername(),
            userVo.getPassword(),
            new HashSet<>(),
            userVo.getId(),
            userVo.getShardingId(),
            userVo.getEnterpriseId(),
            userVo.getStoreId(),
            userVo.getJwtToken(),
            userVo.getRealName(),
            userVo.getSex(),
            userVo.getMobil(),
            userVo.getEmail(),
            userVo.getDiscountLimit(),
            userVo.getReduceLimit(),
            userVo.getDuties(),
            userVo.getCreatedTime(),
            userVo.getUpdatedTime()
        );
        return Mono.just(userAuth);
    }
}

认证管理器
gateway-统一权限-认证
**JwtReactiveAuthenticationManager:**查询用户明细信息之后,与请求传递过来的密码或者短信验证码进行比对,如果比对成功则表示登录成功

import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

/**
 * @ClassName JwtUserDetailsRepositoryReactiveAuthenticationManager.java
 * @Description 认证管理器
 */
@Component
public class JwtReactiveAuthenticationManager implements ReactiveAuthenticationManager {

    //密码编辑者
    private PasswordEncoder passwordEncoder = PasswordEncoderFactories
            .createDelegatingPasswordEncoder();

    //调度程序
    private Scheduler scheduler = Schedulers.parallel();

    //用户明细信息服务
    @Autowired
    private ReactiveUserDetailsService reactiveUserDetailsService;

    @Autowired
    RedissonClient redissonClient;

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        final String principal = authentication.getName();
        final String password = (String) authentication.getCredentials();
        String[] principals = principal.split(":");
        //mobile+":"+enterpriseId+":"+loginType+":"+siteType
        String mobile =principals[0];
        String enterpriseId =principals[1];
        String loginType =principals[2];
        String siteType =principals[3];
        Mono<UserDetails> userDetailsMono = this.reactiveUserDetailsService.findByUsername(principal);
        //密码校验
        if (loginType.equals(SuperConstant.USERNAME_LOGIN)){
            return userDetailsMono.publishOn(this.scheduler)
                //密码比较
                .filter(u -> this.passwordEncoder.matches(password, u.getPassword()))
                //失败处理
                .switchIfEmpty(Mono.defer(()->
                    Mono.error(new BadCredentialsException("Invalid Credentials"))))
                //成功处理
                .map(u ->
                    new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()));
        }
        //短信校验
        if (loginType.equals(SuperConstant.MOBIL_LOGIN)){
            //redis中获得验证码
            String key = SmsCacheConstant.LOGIN_CODE+principals[0];
            RBucket<String> bucket = redissonClient.getBucket(key);
            String authCode = bucket.get();
            if (EmptyUtil.isNullOrEmpty(authCode)){
                Mono.error(new BadCredentialsException("Invalid Credentials"));
            }
            return userDetailsMono.publishOn(this.scheduler)
                //密码比较
                .filter(u -> authCode.equals(password))
                //失败处理
                .switchIfEmpty(Mono.defer(()->
                    Mono.error(new BadCredentialsException("Invalid Credentials"))))
                //成功处理
                .map(u ->
                    new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()));
        }
        throw new BadCredentialsException("Invalid Credentials");
    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        this.passwordEncoder = passwordEncoder;
    }

}

认证成功
gateway-统一权限-认证

import io.netty.util.CharsetUtil;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * @ClassName JsonServerAuthenticationSuccessHandler.java
 * @Description 登录成功handler
 */
@Component
public class JsonServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {

    @Autowired
    JwtTokenManager jwtTokenManager;

    @DubboReference(version = "${dubbo.application.version}", check = false)
    UserAdapterFace userAdapterFace;

    @Override
    public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, 
                                              Authentication authentication) {
        ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");
        UserAuth authUser = (UserAuth) authentication.getPrincipal();
        //构建userVo返回对象
        UserVo userVo = UserVo.builder()
                .id(authUser.getId())
                .username(authUser.getUsername())
                .reduceLimit(authUser.getReduceLimit())
                .discountLimit(authUser.getDiscountLimit())
                .enterpriseId(authUser.getEnterpriseId())
                .storeId(authUser.getStoreId())
                .build();
        //处理角色构建
        List<RoleVo> roleByUserId = userAdapterFace.findRoleByUserId(userVo.getId());
        Set<String> roles =  new HashSet<>();
        for (RoleVo roleVo : roleByUserId) {
            roles.add(roleVo.getLabel());
        }
        //处理资源构建
        List<ResourceVo> resourceByUserId = userAdapterFace.findResourceByUserId(userVo.getId());
        Set<String> resources = new HashSet<>();
        for (ResourceVo resourceVo : resourceByUserId) {
            resources.add(resourceVo.getRequestPath());
        }
        //用户指定角色、资源
        userVo.setRoles(roles);
        userVo.setResources(resources);
        //构建JWT令牌
        Map<String,Object> claims = new HashMap<>();
        String userVoJsonString= JSONObject.toJSONString(userVo);
        claims.put("currentUser",userVoJsonString);
        String jwtToken = jwtTokenManager.issuedToken("system",
                jwtTokenManager.getJwtProperties().getTtl(),
                authUser.getId().toString(),
                claims);
        userVo.setJwtToken(jwtToken);
        //返回信息给前端
        ResponseWrap<UserVo> responseWrap = ResponseWrapBuild.build(AuthEnum.SUCCEED, userVo);
        String result = JSONObject.toJSONString(responseWrap);
        DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(CharsetUtil.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
}

认证失败
认证失败:只需要返回错误信息即可文章来源地址https://www.toymoban.com/news/detail-415083.html

import io.netty.util.CharsetUtil;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

/**
 * @ClassName JsonServerAuthenticationFailureHandler.java
 * @Description 登录失败
 */
@Component
public class JsonServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {

    @Override
    public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
        //指定应答状态
        ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");
        //返回信息给前段
        ResponseWrap<UserAuth> responseWrap = ResponseWrapBuild.build(AuthEnum.FAIL, null);
        String result = JSONObject.toJSONString(responseWrap);
        DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(CharsetUtil.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
}

到了这里,关于gateway-统一权限-认证的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Gateway+Springsecurity+OAuth2.0+JWT 实现分布式统一认证授权!

    目录 1. OAuth2.0授权服务 2. 资源服务 3. Gateway网关 4. 测试   在SpringSecurity+OAuth2.0 搭建认证中心和资源服务中心-CSDN博客 ​​​​​​ 基础上整合网关和JWT实现分布式统一认证授权。   大致流程如下: 1、客户端发出请求给网关获取令牌 2、网关收到请求,直接转发给授权服务

    2024年01月24日
    浏览(56)
  • Spring Gateway+Security+OAuth2+RBAC 实现SSO统一认证平台

    背景:新项目准备用SSO来整合之前多个项目的登录和权限,同时引入网关来做后续的服务限流之类的操作,所以搭建了下面这个系统雏形。 : Spring Gateway, Spring Security, JWT, OAuth2, Nacos, Redis, Danymic datasource, Javax, thymeleaf 如果对上面这些技术感兴趣,可以继续往下阅读 如

    2024年02月13日
    浏览(56)
  • 5.微服务项目实战---Gateway--服务网关,实现统一认证、鉴权、监控、路由转发等

    大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用 这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。   这样的架构,会存在着诸多的问题: 客户端多次请求不同的微服务,

    2024年02月16日
    浏览(48)
  • 微服务架构-服务网关(Gateway)-权限认证(分布式session替代方案)

    前面我们了解了Gateway组件的过滤器,这一节我们就探讨一下Gateway在分布式环境中的一个具体用例-用户鉴权。 从我们开始学JavaEE的时候,就被洗脑式灌输了一种权限验证的标准做法,那就是将用户的登录状态保存到HttpSession中,比如在登录成功后保存一对key-value值到session,

    2024年02月16日
    浏览(45)
  • 网络安全day1-基础入门概念名词 超详细!!!

    目录 一、域名 1.域名概念: 2.二级域名和多级域名:​编辑 3.域名对安全测试的意义: 二、DNS 1.域名系统 2.本地Hosts与DNS的关系 3.CDN概念以及和DNS的关系: 4.常见的DNS安全攻击 三、脚本语言  四、后门 1.什么是后门?有哪些后门? 2.后门在安全测试中的实际意义? 3.关于后门需

    2024年02月07日
    浏览(51)
  • 最强的单点登录认证系统,基于RBAC统一权限控制,实现用户生命周期管理,开源、安全

    MaxKey 单点登录认证系统,谐音马克思的钥匙寓意是最大钥匙,是 业界领先的IAM-IDaas身份管理和认证产品 ,支持OAuth 2.x/OpenID Connect、SAML 2.0、JWT、CAS、SCIM等标准协议,提供 安全、标准和开放 的用户身份管理(IDM)、身份认证(AM)、单点登录(SSO)、RBAC权限管理和资源管理等。 MaxKey注

    2024年02月03日
    浏览(57)
  • 权限认证SpringCloud GateWay、SpringSecurity、OAuth2.0、JWT一网打尽!

    1.它是如何工作的? ​ 客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被虚线分割的原因是,过滤器可以在代理请求发送之前和

    2024年04月08日
    浏览(47)
  • 【RuoYi-Cloud项目研究】【ruoyi-gateway模块】网关的AuthFilter完成“认证”,注意是认证而不是权限

    过滤器的功能是检验经过网关的每一个请求,检查 token 中的信息是否有效。 注意是“认证检查”,而不是“权限” ,权限是在每个服务的Controller上贴权限注解 1、在用户完成登录后,程序会把用户相关的用户、角色、权限等信息临时存储在 redis 中,并把token返回给终端用户

    2024年02月07日
    浏览(40)
  • 区块链基本概念和名词解释

    区块链基本概念和名词解释 P2P 共识算法 梅克尔-帕特里夏树 从零开始搭建区块链 至今(2022)从业已经10年了,作为一个IT老鸟,见证了移动互联时代的崛起,甚至参与其中充当一颗光荣的螺丝钉。其间各种各样的所谓新技术、新框架、新工具层出不穷,有的昙花一现,有的

    2024年01月17日
    浏览(63)
  • 微服务-统一网关Gateway

    对用户请求做身份认证、权限校验 将用户请求路由到微服务,并实现负载均衡 对用户请求做限流 创建新module,命名为Gateway,引入依赖(1.SpringCloudGateway依赖;2.Eureka客户端依赖或者nacos的服务发现依赖)。在本案例中使用的是Eureka。 配置Application.yml的网关服务 路由id:路由

    2024年02月08日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包