美文网首页个人学习
Spring Cloud Gateway + Oauth2 搭建

Spring Cloud Gateway + Oauth2 搭建

作者: 百里有声 | 来源:发表于2022-04-17 11:17 被阅读0次

    源代码链接

    在SpringCloud中,实现授权功能有两种实现方式:

    1.网关授权

    基于网关授权我们又叫【基于路径匹配器授权】,请求在经过网关的时候校验当前请求的路径是否在用户拥有的资源路径中。
    主要利用 ReactiveAuthorizationManager#check(Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext) 方法进行校验。
    这种方法主要是基于用户拥有的资源路径进行考量。

    2.微服务授权

    微服务授权我们又叫【基于方法拦截】,在需要进行权限校验的方法上加上SpringSecurity注解,判断当前用户是否有访问此方法的权限。
    1.在secruity的配置类上加上注解 @EnableGlobalMethodSecurity(prePostEnabled = true) 启用springsecruity前/后注解
    2.在需要权限的方法或者类上添加权限注解@PreAuthorize("hasAuthority('USER')")
    当然也可以使用自定义注解或使用AOP进行拦截校验,这几种实现方式我们都统称为基于方法拦截。
    这种方法一般会基于用户拥有的资源标识进行考量。

    书接上文 oauth2 业务模块鉴权搭建 实现了在调用业务模块的接口时进行鉴权也就是【微服务授权】的方式。
    接下来搭建【网关授权】

    对认证服务器的数据库进行修改,添加路径的配置

    数据库Sql结构
    搭建认证服务的过程

    认证服务器增加配置,将resource path和角色对应的内容读取到redis

    gateway服务将从redis进行路径和角色的判断

    @Configuration
    public class ResourceApiPathConfig {
    
        @Resource
        private DataSource dataSource;
    
        @Resource
        RedisTemplate<String, Map<String, List<String>>> redisTemplate;
    
        @Bean
        public RedisResourceService resourcePathDetails() throws UnknownHostException {
            return new RedisResourceService(dataSource, redisTemplate);
        }
    }
    
    public class RedisResourceService {
    
        private RedisTemplate<String, Map<String, List<String>>> redisTemplate;
        private DataSource dataSource;
        static final String AUTH_TO_RESOURCE = "auth_resource:";
    
        public RedisResourceService(DataSource _dataSource,
                RedisTemplate<String, Map<String, List<String>>> _redisTemplate) {
            this.dataSource = _dataSource;
            this.redisTemplate = _redisTemplate;
            initData();
        }
    
        @SuppressWarnings("unchecked")
        private void initData() {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            Map<String, List<String>> resourceRolesMap = new TreeMap<>();
            List<Map<String, Object>> list = jdbcTemplate.queryForList(
                    "SELECT CONCAT(\"/\",b.service,b.url) AS url,GROUP_CONCAT(c.name) AS role_names from rbac_role_resource a left join rbac_resource b ON a.resource_id = b.id left join rbac_role c ON a.role_id = c.id GROUP BY b.url");
    
            for (Map<String, Object> m : list) {
                System.out.print(m.get("url").toString());
                resourceRolesMap.put(m.get("url").toString(), Arrays.asList(m.get("role_names").toString().split(",")));
            }
            redisTemplate.opsForHash().putAll(AUTH_TO_RESOURCE, resourceRolesMap);
            // System.out.print("******************************set_ResourcePath");
        }
    }
    

    创建gateway

    http -d https://start.spring.io/starter.zip javaVersion==17 groupId==com.my.demo artifactId==gatewayService name==gateway-service baseDir==gateway-service bootVersion==2.6.6.RELEASE dependencies==cloud-gateway
    

    修改POM

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.6.6</version>
            <relativePath /> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.my.demo</groupId>
        <artifactId>gatewayService</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>gateway-service</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>17</java.version>
            <spring-cloud.version>2021.0.1</spring-cloud.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
                <version>2.2.5.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.12.0</version>
            </dependency>
    
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.0.6</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    
    

    修改application.properties 配置里routes[0]的project-service可以仍采用 业务服务 里搭建的服务

    server.port=8501
    spring.application.name=gateway-service
    
    spring.cloud.gateway.routes[0].id=project-service
    spring.cloud.gateway.routes[0].uri=http://localhost:8601
    spring.cloud.gateway.routes[0].predicates[0]=Path=/project/**
    spring.cloud.gateway.routes[0].filters=StripPrefix=1
    
    spring.cloud.gateway.routes[1].id=oauth-service
    spring.cloud.gateway.routes[1].uri=http://localhost:8509
    spring.cloud.gateway.routes[1].predicates[0]=Path=/oauth/**
    
    security.oauth2.client.scope=server
    security.oauth2.client.client-id=client
    security.oauth2.client.client-secret=123456
    security.oauth2.client.access-token-uri=http://localhost:8509/oauth/token
    security.oauth2.client.user-authorization-uri=http://localhost:8509/oauth/authorize
     
    security.oauth2.authorization.check-token-access=http://localhost:8509/oauth/check_token
    
    security.oauth2.resource.token-info-uri=http://localhost:8509/oauth/check_token
    security.oauth2.resource.user-info-uri=http://localhost:8509/oauth/check_user
    security.oauth2.resource.prefer-token-info=true
    secure.ignore.urls="/actuator/**","/oauth/**"
    
    spring.redis.database=0  
    spring.redis.host=127.0.0.1
    spring.redis.port=6379 
    spring.redis.password=
    spring.redis.timeout=2000
    

    配置gateway的鉴权

    public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
    
        private RedisTemplate redisTemplate;
        private RedisConnectionFactory redisConnectionFactory;
        static final String AUTH_TO_RESOURCE = "auth_resource:";
    
        @Override
        public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
    
            ServerWebExchange exchange = authorizationContext.getExchange();
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            
            String authorizationToken = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION); // 从Header里取出token的值
            if (StringUtils.isBlank(authorizationToken)) {
                authorizationToken = request.getQueryParams().getFirst("access_token");
            }
    
            if (StringUtils.isBlank(authorizationToken)) {
                log.warn("当前请求头Authorization中的值不存在");
                return Mono.just(new AuthorizationDecision(false));
            }
    
            RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
            String token = authorizationToken.replace(OAuth2AccessToken.BEARER_TYPE + " ", "");
            OAuth2Authentication oAuth2Authentication = redisTokenStore.readAuthentication(token);
            Collection<GrantedAuthority> authorities = oAuth2Authentication.getAuthorities(); // 取到角色
            Map<String, List<String>> resourceRolesMap = redisTemplate.opsForHash().entries(AUTH_TO_RESOURCE);
            List<String> pathAuthorities = resourceRolesMap.get(path);
            for (GrantedAuthority authority : authorities) {
                if (pathAuthorities.contains(authority.getAuthority())) {
                    return Mono.just(new AuthorizationDecision(true));
                }
            }
            return Mono.just(new AuthorizationDecision(false));
    
        }
    }
    
    
    @AllArgsConstructor
    @Configuration
    public class ResourceServerConfig {
    
        private final AuthorizationManager authorizationManager;
        private final IgnoreUrlsConfig ignoreUrlsConfig;
    
        @Bean
        public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
            List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
            http.authorizeExchange().pathMatchers(ArrayUtil.toArray(ignoreUrls, String.class)).permitAll()// 白名单配置
                    .anyExchange().access(authorizationManager)// 鉴权管理器配置
                    .and().exceptionHandling().and().csrf().disable();
            return http.build();
        }
    }
    
    

    通过 HTTPie 测试

    
    //获取access_token
    D:\Project\oauthService>http http://127.0.0.1:8509/oauth/token client_secret==123456 grant_type==password username==Test password==123456 client_id==client scope==server
    HTTP/1.1 200
    Cache-Control: no-store
    Connection: keep-alive
    Content-Type: application/json;charset=UTF-8
    Keep-Alive: timeout=60
    Pragma: no-cache
    Transfer-Encoding: chunked
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    X-XSS-Protection: 1; mode=block
    
    {
        "access_token": "ec9055f9-8be7-4c40-98ad-d7f81c31137d",
        "expires_in": 43199,
        "refresh_token": "c113adb9-e192-49a2-bbf5-6cb11b8a3a3e",
        "scope": "server",
        "token_type": "bearer"
    }
    
    
    //通过网关访问project-service的鉴权方法
    D:\Project\oauthService>http -v --auth-type=bearer --auth=ec9055f9-8be7-4c40-98ad-d7f81c31137d http://127.0.0.1:8501/project/config/set
    GET /project/config/set HTTP/1.1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Authorization: Bearer ec9055f9-8be7-4c40-98ad-d7f81c31137d
    Connection: keep-alive
    Host: 127.0.0.1:8501
    User-Agent: HTTPie/3.1.0
    
    
    
    HTTP/1.1 200 OK
    Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    Content-Type: text/plain;charset=UTF-8
    Expires: 0
    Pragma: no-cache
    Referrer-Policy: no-referrer
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    X-XSS-Protection: 1; mode=block
    content-length: 69
    
    ec9055f9-8be7-4c40-98ad-d7f81c31137dconfig project have authorization
    
    
    //通过网关访问project-service的鉴权错误的方法
    D:\Project\oauthService>http -v --auth-type=bearer --auth=ec9055f9-8be7-4c40-98ad-d7f81c31137d http://127.0.0.1:8501/project/config/get
    GET /project/config/get HTTP/1.1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Authorization: Bearer ec9055f9-8be7-4c40-98ad-d7f81c31137d
    Connection: keep-alive
    Host: 127.0.0.1:8501
    User-Agent: HTTPie/3.1.0
    
    
    
    HTTP/1.1 500 Internal Server Error
    Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    Content-Length: 144
    Content-Type: application/json
    Expires: 0
    Pragma: no-cache
    Referrer-Policy: no-referrer
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    X-XSS-Protection: 1 ; mode=block
    
    {
        "error": "Internal Server Error",
        "path": "/project/config/get",
        "requestId": "b9250aa3-2",
        "status": 500,
        "timestamp": ""
    }
    

    源代码链接

    相关文章

      网友评论

        本文标题:Spring Cloud Gateway + Oauth2 搭建

        本文链接:https://www.haomeiwen.com/subject/hvakertx.html