SpringCloud微服务实战——搭建企业级开发框架(二十三):Gateway+OAuth2+JWT微服务统一认证授权【一】

举报
全栈程序猿 发表于 2023/07/17 14:48:03 2023/07/17
【摘要】   OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该token(令牌)在限定时间、限定范围访问指定资源。  OAuth2中使用token验证用户登录合法性,但token最大的问题是不携带用户信息,资源服务器无法在本地进行验证,每次对于资源的访问,资源服务器都需要向认证服务器发起请...

  OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该token(令牌)在限定时间、限定范围访问指定资源。
  OAuth2中使用token验证用户登录合法性,但token最大的问题是不携带用户信息,资源服务器无法在本地进行验证,每次对于资源的访问,资源服务器都需要向认证服务器发起请求,一是验证token的有效性,二是获取token对应的用户信息。如果有大量的此类请求,无疑处理效率是很低,且认证服务器会变成一个中心节点,这在分布式架构下很影响性能。如果认证服务器颁发的是jwt格式的token,那么资源服务器就可以直接自己验证token的有效性并绑定用户,这无疑大大提升了处理效率且减少了单点隐患。
  SpringCloud认证授权解决思路:认证服务负责认证,网关负责校验认证和鉴权,其他API服务负责处理自己的业务逻辑。安全相关的逻辑只存在于认证服务和网关服务中,其他服务只是单纯地提供服务而没有任何安全相关逻辑。
微服务鉴权功能划分:

  • gitegg-oauth:Oauth2用户认证和单点登录
  • gitegg-gateway:请求转发和统一鉴权
  • gitegg-system: 读取系统配置的RBAC权限配置并存放到缓存

一、鉴权配置

  1. GitEgg-Platform工程下新建gitegg-platform-oauth2工程,用于统一管理OAuth2版本,及统一配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>GitEgg-Platform</artifactId>
        <groupId>com.gitegg.platform</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gitegg-platform-oauth2</artifactId>
    <name>${project.artifactId}</name>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-swagger</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
  1. 在gitegg-oauth工程中引入需要的库
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>GitEgg-Cloud</artifactId>
        <groupId>com.gitegg.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gitegg-oauth</artifactId>
    <name>${project.artifactId}</name>
    <packaging>jar</packaging>

    <dependencies>
        <!-- gitegg-platform-boot -->
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-boot</artifactId>
            <version>${gitegg.project.version}</version>
        </dependency>
        <!-- gitegg-platform-cloud -->
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-cloud</artifactId>
            <version>${gitegg.project.version}</version>
        </dependency>
        <!-- gitegg-platform-oauth2 -->
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-oauth2</artifactId>
            <version>${gitegg.project.version}</version>
        </dependency>
        <!-- gitegg数据库驱动及连接池 -->
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-db</artifactId>
        </dependency>
        <!-- gitegg mybatis-plus -->
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-mybatis</artifactId>
        </dependency>
        <!-- 验证码 -->
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-captcha</artifactId>
        </dependency>
        <!-- gitegg-service-system 的fegin公共调用方法 -->
        <dependency>
            <groupId>com.gitegg.cloud</groupId>
            <artifactId>gitegg-service-system-api</artifactId>
            <version>${gitegg.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

</project>
  1. JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。首先我们使用keytool生成RSA证书gitegg.jks,复制到gitegg-oauth工程的resource目录下,CMD命令行进入到JDK安装目录的bin目录下, 使用keytool命令生成gitegg.jks证书
keytool -genkey -alias gitegg -keyalg RSA -keystore gitegg.jks
  1. 新建GitEggUserDetailsServiceImpl.java实现SpringSecurity获取用户信息接口,用于SpringSecurity鉴权时获取用户信息
package com.gitegg.oauth.service;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.gitegg.oauth.enums.AuthEnum;
import com.gitegg.platform.base.constant.AuthConstant;
import com.gitegg.platform.base.domain.GitEggUser;
import com.gitegg.platform.base.enums.ResultCodeEnum;
import com.gitegg.platform.base.result.Result;
import com.gitegg.service.system.api.feign.IUserFeign;

import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;

/**
 *  实现SpringSecurity获取用户信息接口
 *
 * @author gitegg
 */
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class GitEggUserDetailsServiceImpl implements UserDetailsService {

    private final IUserFeign userFeign;

    private final HttpServletRequest request;

    @Override
    public GitEggUserDetails loadUserByUsername(String username) {

        // 获取登录类型,密码,二维码,验证码
        String authLoginType = request.getParameter(AuthConstant.AUTH_TYPE);

        // 获取客户端id
        String clientId = request.getParameter(AuthConstant.AUTH_CLIENT_ID);

        // 远程调用返回数据
        Result<Object> result;

        // 通过手机号码登录
        if (!StringUtils.isEmpty(authLoginType) && AuthEnum.PHONE.code.equals(authLoginType))
        {
            String phone = request.getParameter(AuthConstant.PHONE_NUMBER);
            result = userFeign.queryUserByPhone(phone);
        }
        // 通过账号密码登录
        else if(!StringUtils.isEmpty(authLoginType) && AuthEnum.QR.code.equals(authLoginType))
        {
            result = userFeign.queryUserByAccount(username);
        }
        else
        {
            result = userFeign.queryUserByAccount(username);
        }

        // 判断返回信息
        if (null != result && result.isSuccess()) {
            GitEggUser gitEggUser = new GitEggUser();
            BeanUtil.copyProperties(result.getData(), gitEggUser, false);
            if (gitEggUser == null || gitEggUser.getId() == null) {
                throw new UsernameNotFoundException(ResultCodeEnum.INVALID_USERNAME.msg);
            }

            if (CollectionUtils.isEmpty(gitEggUser.getRoleIdList())) {
                throw new UserDeniedAuthorizationException(ResultCodeEnum.INVALID_ROLE.msg);
            }

            return new GitEggUserDetails(gitEggUser.getId(), gitEggUser.getTenantId(), gitEggUser.getOauthId(),
                gitEggUser.getNickname(), gitEggUser.getRealName(), gitEggUser.getOrganizationId(),
                gitEggUser.getOrganizationName(),
                    gitEggUser.getOrganizationIds(), gitEggUser.getOrganizationNames(), gitEggUser.getRoleId(), gitEggUser.getRoleIds(), gitEggUser.getRoleName(), gitEggUser.getRoleNames(),
                gitEggUser.getRoleIdList(), gitEggUser.getRoleKeyList(), gitEggUser.getResourceKeyList(),
                gitEggUser.getDataPermission(),
                gitEggUser.getAvatar(), gitEggUser.getAccount(), gitEggUser.getPassword(), true, true, true, true,
                AuthorityUtils.createAuthorityList(gitEggUser.getRoleIdList().toArray(new String[gitEggUser.getRoleIdList().size()])));
        } else {
            throw new UsernameNotFoundException(result.getMsg());
        }
    }

}
  1. 新建AuthorizationServerConfig.java用于认证服务相关配置,正式环境请一定记得修改gitegg.jks配置的密码,这里默认为123456。TokenEnhancer 为登录用户的扩展信息,可以自己定义。
package com.gitegg.oauth.config;

import java.security.KeyPair;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import com.anji.captcha.service.CaptchaService;
import com.gitegg.oauth.granter.GitEggTokenGranter;
import com.gitegg.oauth.service.GitEggClientDetailsServiceImpl;
import com.gitegg.oauth.service.GitEggUserDetails;
import com.gitegg.platform.base.constant.AuthConstant;
import com.gitegg.platform.base.constant.TokenConstant;
import com.gitegg.service.system.api.feign.IUserFeign;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

/**
 * 认证服务配置
 */
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final DataSource dataSource;

    private final AuthenticationManager authenticationManager;

    private final UserDetailsService userDetailsService;

    private final IUserFeign userFeign;

    private final RedisTemplate redisTemplate;

    private final CaptchaService captchaService;

    @Value("${captcha.type}")
    private String captchaType;

    /**
     * 客户端信息配置
     */
    @Override
    @SneakyThrows
    public void configure(ClientDetailsServiceConfigurer clients) {
        GitEggClientDetailsServiceImpl jdbcClientDetailsService = new GitEggClientDetailsServiceImpl(dataSource);
        jdbcClientDetailsService.setFindClientDetailsSql(AuthConstant.FIND_CLIENT_DETAILS_SQL);
        jdbcClientDetailsService.setSelectClientDetailsSql(AuthConstant.SELECT_CLIENT_DETAILS_SQL);
        clients.withClientDetails(jdbcClientDetailsService);
    }

    /**
     * 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer());
        tokenEnhancers.add(jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        // 获取自定义tokenGranter
        TokenGranter tokenGranter = GitEggTokenGranter.getTokenGranter(authenticationManager, endpoints, redisTemplate,
            userFeign, captchaService, captchaType);

        endpoints.authenticationManager(authenticationManager)
                .accessTokenConverter(jwtAccessTokenConverter())
                .tokenEnhancer(tokenEnhancerChain)
                .userDetailsService(userDetailsService)
            .tokenGranter(tokenGranter)
                /**
                 *
                 * refresh_token有两种使用方式:重复使用(true)、非重复使用(false),默认为true
                 * 1.重复使用:access_token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准
                 * 2.非重复使用:access_token过期刷新时, refresh_token过期时间延续,在refresh_token有效期内刷新而无需失效再次登录
                 */
                .reuseRefreshTokens(false);
    }

    /**
     * 允许表单认证
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    /**
     * 使用非对称加密算法对token签名
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyPair());
        return converter;
    }

    /**
     * 从classpath下的密钥库中获取密钥对(公钥+私钥)
     */
    @Bean
    public KeyPair keyPair() {
        KeyStoreKeyFactory factory = new KeyStoreKeyFactory(
                new ClassPathResource("gitegg.jks"), "123456".toCharArray());
        KeyPair keyPair = factory.getKeyPair(
                "gitegg", "123456".toCharArray());
        return keyPair;
    }

    /**
     * JWT内容增强
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
            Map<String, Object> map = new HashMap<>(2);
            GitEggUserDetails user = (GitEggUserDetails) authentication.getUserAuthentication().getPrincipal();
            map.put(TokenConstant.TENANT_ID, user.getTenantId());
            map.put(TokenConstant.OAUTH_ID, user.getOauthId());
            map.put(TokenConstant.USER_ID, user.getId());
            map.put(TokenConstant.ORGANIZATION_ID, user.getOrganizationId());
            map.put(TokenConstant.ORGANIZATION_NAME, user.getOrganizationName());
            map.put(TokenConstant.ORGANIZATION_IDS, user.getOrganizationIds());
            map.put(TokenConstant.ORGANIZATION_NAMES, user.getOrganizationNames());
            map.put(TokenConstant.ROLE_ID, user.getRoleId());
            map.put(TokenConstant.ROLE_NAME, user.getRoleName());
            map.put(TokenConstant.ROLE_IDS, user.getRoleIds());
            map.put(TokenConstant.ROLE_NAMES, user.getRoleNames());
            map.put(TokenConstant.ACCOUNT, user.getAccount());
            map.put(TokenConstant.REAL_NAME, user.getRealName());
            map.put(TokenConstant.NICK_NAME, user.getNickname());
            map.put(TokenConstant.ROLE_ID_LIST, user.getRoleIdList());
            map.put(TokenConstant.ROLE_KEY_LIST, user.getRoleKeyList());
            //不把权限菜单放到jwt里面,当菜单太多时,会导致jwt长度不可控
//            map.put(TokenConstant.RESOURCE_KEY_LIST, user.getResourceKeyList());
            map.put(TokenConstant.DATA_PERMISSION, user.getDataPermission());
            map.put(TokenConstant.AVATAR, user.getAvatar());
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
            return accessToken;
        };
    }
}


  1. Gateway在认证授权时需要RSA的公钥来验证签名是否合法,所以这里新建GitEggOAuthController的getKey接口用于Gateway获取RSA公钥
    @GetMapping("/public_key")
    public Map<String, Object> getKey() {
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAKey key = new RSAKey.Builder(publicKey).build();
        return new JWKSet(key).toJSONObject();
    }
  1. 新建ResourceServerConfig.java资源服务器配置,放开public_key的读取权限
	@Override
	@SneakyThrows
	public void configure(HttpSecurity http) {
		http.headers().frameOptions().disable();
		http.formLogin()
			.and()
			.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
			.and()
			.authorizeRequests()
			.antMatchers(
				"/oauth/public_key").permitAll()
			.anyRequest().authenticated()
			.and()
			.csrf().disable();
	}
  1. 在gitegg-service-system新建InitResourceRolesCacheRunner.java实现CommandLineRunner接口,用于系统启动时加载RBAC权限配置信息到缓存
package com.gitegg.service.system.component;

import java.util.*;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import com.gitegg.platform.base.constant.AuthConstant;
import com.gitegg.service.system.entity.Resource;
import com.gitegg.service.system.service.IResourceService;

import cn.hutool.core.collection.CollectionUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 容器启动完成加载资源权限数据到缓存
 */
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component
public class InitResourceRolesCacheRunner implements CommandLineRunner {

    private final RedisTemplate redisTemplate;

    private final IResourceService resourceService;

    /**
     * 是否开启租户模式
     */
    @Value(("${tenant.enable}"))
    private Boolean enable;

    @Override
    public void run(String... args) {

        log.info("InitResourceRolesCacheRunner running");

        // 查询系统角色和权限的关系
        List<Resource> resourceList = resourceService.queryResourceRoleIds();

        // 判断是否开启了租户模式,如果开启了,那么角色权限需要按租户进行分类存储
        if (enable) {
            Map<Long, List<Resource>> resourceListMap =
                resourceList.stream().collect(Collectors.groupingBy(Resource::getTenantId));
            resourceListMap.forEach((key, value) -> {
                String redisKey = AuthConstant.TENANT_RESOURCE_ROLES_KEY + key;
                redisTemplate.delete(redisKey);
                addRoleResource(redisKey, value);
                System.out.println(redisTemplate.opsForHash().entries(redisKey).size());
            });
        } else {
            redisTemplate.delete(AuthConstant.RESOURCE_ROLES_KEY);
            addRoleResource(AuthConstant.RESOURCE_ROLES_KEY, resourceList);
        }
    }

    private void addRoleResource(String key, List<Resource> resourceList) {
        Map<String, List<String>> resourceRolesMap = new TreeMap<>();
        Optional.ofNullable(resourceList).orElse(new ArrayList<>()).forEach(resource -> {
            // roleId -> ROLE_{roleId}
            List<String> roles = Optional.ofNullable(resource.getRoleIds()).orElse(new ArrayList<>()).stream()
                .map(roleId -> AuthConstant.AUTHORITY_PREFIX + roleId).collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(roles)) {
                resourceRolesMap.put(resource.getResourceUrl(), roles);
            }
        });
        redisTemplate.opsForHash().putAll(key, resourceRolesMap);
    }
}
  1. 新建网关服务gitegg-gateway,作为Oauth2的资源服务、客户端服务使用,对访问微服务的请求进行转发、统一校验认证和鉴权操作,引入相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>GitEgg-Cloud</artifactId>
        <groupId>com.gitegg.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gitegg-gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-base</artifactId>
            <version>${gitegg.project.version}</version>
        </dependency>
        <!-- Nacos 服务注册发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- Nacos 分布式配置 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- OpenFeign 微服务调用解决方案 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-oauth2</artifactId>
            <version>${gitegg.project.version}</version>
        </dependency>
        <!-- gitegg cache自定义扩展 -->
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-cache</artifactId>
            <version>${gitegg.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-ui</artifactId>
        </dependency>
    </dependencies>

</project>

未完,因篇幅限制20000字,剩余内容请看下一章: SpringCloud微服务实战——搭建企业级开发框架(二十三):Gateway+OAuth2+JWT微服务统一认证授权【二】

GitEgg-Cloud是一款基于SpringCloud整合搭建的企业级微服务应用开发框架,开源项目地址:

Gitee: https://gitee.com/wmz1930/GitEgg

GitHub: https://github.com/wmz1930/GitEgg

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。