前言
目前正在出一个SpringCloud进阶
系列教程,含源码解读
, 篇幅会较多, 喜欢的话,给个关注❤️ ~
该系列默认开启Nacos
服务,还不会搭建的小伙伴可以参考往期文章~
本节重点是给大家介绍Oauth2
如何整合到SpringCloud Gateway 网关中
, 废话不多说直接开整吧~
SpringCloud Gateway 网关搭建
在最初的时候,给大家介绍的是Zull
网关,其实我们可以有更好的网关选择,就是我们的SpringCloud Gateway
,如果有条件的话,还是建议大家选择它,毕竟有强大的社区在维护,功能和性能方面表现都不错。
网关搭建
本节依然沿用上节的代码,我们新建一个网关模块,名字叫spring-cloud-oauth2-gateway
,创建好之后,复制以下依赖 pom.xml
<?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>com.pkq.cloud</artifactId> <groupId>pkq-cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-cloud-oauth2-gateway</artifactId> <dependencies> <!--网关依赖gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <!-- Nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <exclusions> <exclusion> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </exclusion> </exclusions> <version>${spring-cloud-alibaba.version}</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <exclusions> <exclusion> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </exclusion> </exclusions> <version>${spring-cloud-alibaba.version}</version> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>${alibaba.nacos.version}</version> </dependency> <!--SpringBoot2.4.x之后默认不加载bootstrap.yml文件,需要在pom里加上依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <!-- 加入 log4j2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> <exclusions> <exclusion> <groupId>*</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <!-- Druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.22</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <!-- MyBatis-Plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis.plus.version}</version> </dependency> <!-- redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--安全认证框架--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- oauth2 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> <version>9.9.3</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project> 复制代码
紧接着,我们加入配置文件 bootstrap.yml
server: port: 10002 spring: application: name: oauth2-gateway datasource: url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: ********** driver-class-name: com.mysql.cj.jdbc.Driver redis: host: 127.0.0.1 port: 6379 database: 2 cloud: nacos: discovery: #注册中心地址 server-addr: 127.0.0.1:8848 config: file-extension: yaml #配置中心地址 server-addr: 127.0.0.1:8848 thymeleaf: cache: false mybatis-plus: mapper-locations: classpath:mapper/*/*.xml,mapper/*.xml global-config: db-config: id-type: auto field-strategy: NOT_EMPTY db-type: MYSQL configuration: map-underscore-to-camel-case: true call-setters-on-nulls: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 复制代码
下面我们新建一个启动类:
/** * @Author * @Description spring cloud gateway 网关服务 * @Date */ @SpringBootApplication public class Oauth2GatewayApplication { public static void main(String[] args) { SpringApplication.run(Oauth2GatewayApplication.class, args); } } 复制代码
我们不急着继续配置,先点开启动一下,检查一下依赖是否都符合预期,如果没有任何报错,那么恭喜你可以继续下一步了
配置网关
修改bootstrap.yml
,添加如下配置
gateway: routes: # 产品服务 - id: product uri: http://localhost:10003 predicates: - Path=/api/** # 认证服务 - id: auth uri: http://localhost:10001 predicates: - Path=/** 复制代码
配置权限管理器
/** * @Author * @Description 权限管理器 * @Date */ @Slf4j @Component public class AccessManager implements ReactiveAuthorizationManager<AuthorizationContext> { private Set<String> permitAll = new ConcurrentSkipListSet<>(); private static final AntPathMatcher antPathMatcher = new AntPathMatcher(); public AccessManager() { permitAll.add("/"); permitAll.add("/error"); permitAll.add("/favicon.ico"); permitAll.add("/**/oauth/**"); } /** * 实现权限验证判断 */ @Override public Mono<AuthorizationDecision> check(Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext) { ServerWebExchange exchange = authorizationContext.getExchange(); //请求资源 String requestPath = exchange.getRequest().getURI().getPath(); // 是否直接放行 if (permitAll(requestPath)) { return Mono.just(new AuthorizationDecision(true)); } return authenticationMono.map(auth -> new AuthorizationDecision(checkAuthorities(exchange, auth, requestPath))).defaultIfEmpty(new AuthorizationDecision(false)); } /** * 校验路径 * * @param requestPath 请求路径 * @return */ private boolean permitAll(String requestPath) { return permitAll.stream() .filter(r -> antPathMatcher.match(r, requestPath)).findFirst().isPresent(); } //权限校验 private boolean checkAuthorities(ServerWebExchange exchange, Authentication auth, String requestPath) { if (auth instanceof OAuth2Authentication) { OAuth2Authentication athentication = (OAuth2Authentication) auth; String clientId = athentication.getOAuth2Request().getClientId(); log.info("clientId is {}", clientId); } Object principal = auth.getPrincipal(); log.info("principal:{}", principal.toString()); return true; } } 复制代码
配置自定义认证管理器:
/** * @Author * @Description 自定义认证管理器 * @Date */ @Slf4j public class ReactiveJdbcAuthenticationManager implements ReactiveAuthenticationManager { private TokenStore tokenStore; public ReactiveJdbcAuthenticationManager(TokenStore tokenStore){ this.tokenStore = tokenStore; } @Override public Mono<Authentication> authenticate(Authentication authentication) { return Mono.justOrEmpty(authentication) .filter(a -> a instanceof BearerTokenAuthenticationToken) .cast(BearerTokenAuthenticationToken.class) .map(BearerTokenAuthenticationToken::getToken) .flatMap((accessToken ->{ log.warn("accessToken is :{}",accessToken); OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken); //根据access_token从数据库获取不到OAuth2AccessToken if(oAuth2AccessToken == null){ return Mono.error(new InvalidTokenException("invalid access token,please check")); }else if(oAuth2AccessToken.isExpired()){ return Mono.error(new InvalidTokenException("access token has expired,please reacquire token")); } OAuth2Authentication oAuth2Authentication =this.tokenStore.readAuthentication(accessToken); if(oAuth2Authentication == null){ return Mono.error(new InvalidTokenException("Access Token 无效!")); }else { return Mono.just(oAuth2Authentication); } })).cast(Authentication.class); } } 复制代码
网关层安全配置
/** * @Author * @Description 网关层安全配置 * @Date */ @Configuration public class SecurityConfig { @Autowired private DataSource dataSource; @Autowired private AccessManager accessManager; @Bean SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception{ //token管理器 ReactiveAuthenticationManager tokenAuthenticationManager = new ReactiveJdbcAuthenticationManager(new JdbcTokenStore(dataSource)); //认证过滤器 AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(tokenAuthenticationManager); authenticationWebFilter.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter()); http .httpBasic().disable() .csrf().disable() .authorizeExchange() .pathMatchers(HttpMethod.OPTIONS).permitAll() .anyExchange().access(accessManager) .and() //oauth2认证过滤器 .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION); return http.build(); } } 复制代码
到这里,基本的配置就结束了,下面我们进入验证环节~
产品服务搭建
为了更加贴近实际,我们再新建一个产品模块,最终会通过网关去访问到这个产品服务~
我们新建一个产品模块,叫做spring-cloud-oauth2-product
, 引入以下依赖
<?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>com.pkq.cloud</artifactId> <groupId>pkq-cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-cloud-oauth2-product</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--SpringBoot2.4.x之后默认不加载bootstrap.yml文件,需要在pom里加上依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <!-- Nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <exclusions> <exclusion> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </exclusion> </exclusions> <version>${spring-cloud-alibaba.version}</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <exclusions> <exclusion> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </exclusion> </exclusions> <version>${spring-cloud-alibaba.version}</version> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>${alibaba.nacos.version}</version> </dependency> <!-- Druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.22</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <!-- MyBatis-Plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis.plus.version}</version> </dependency> <!-- redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> <version>9.9.3</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.0</version> </dependency> <!--thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project> 复制代码
同样的加入配置文件
server: port: 10003 servlet: context-path: /api spring: application: name: oauth2-product datasource: url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: ****** driver-class-name: com.mysql.cj.jdbc.Driver redis: host: 127.0.0.1 port: 6379 database: 2 cloud: nacos: discovery: #注册中心地址 server-addr: 127.0.0.1:8848 config: file-extension: yaml #配置中心地址 server-addr: 127.0.0.1:8848 thymeleaf: cache: false mybatis-plus: mapper-locations: classpath:mapper/*/*.xml,mapper/*.xml global-config: db-config: id-type: auto field-strategy: NOT_EMPTY db-type: MYSQL configuration: map-underscore-to-camel-case: true call-setters-on-nulls: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 复制代码
启动类大家自己建,然后建一个简单的controller
/** * @Author * @Description 产品信息 * @Date */ @RestController @RequestMapping("/product") public class ProductInfoController { @GetMapping("/info") public String getInfo() { return "产品信息"; } } 复制代码
测试环节
准备工作都已经结束了,为了让大家对流程上更加易懂,这里给大家看一张图,这样对整体上有清除的认知:
下面我就开始测试,首先浏览器请求这个地址:
http://localhost:10002/oauth/authorize?client_id=clientId1&response_type=code&scope=all&redirect_uri=https://www.baidu.com 复制代码
就是把上节的10001
改成网关的端口就可以了,后续流程都一样,最终我们拿到了code
之后,下面请求token
在请求产品服务之前,我们先请求真实地址
下面我们通过网关请求,这时不带token
请求带```token````
发现不带token
的请求被拦截下来了,携带的成功请求并返回正确数据。
结束语
本节到这里就结束了,其实还有很多细节,这个就需要大家根据自己的业务灵活调整了,下节我们带大家整合一下比较热门的分布式存储、搜索、分析的引擎Elasticsearch
,这个系列内容会比较多,希望能够帮助到大家吧。关注我,不迷路 ~