前言
目前正在出一个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,这个系列内容会比较多,希望能够帮助到大家吧。关注我,不迷路 ~
相关文章
项目源码(源码已更新 欢迎star⭐️)
往期并发编程内容推荐
- Java多线程专题之线程与进程概述
- Java多线程专题之线程类和接口入门
- Java多线程专题之进阶学习Thread(含源码分析)
- Java多线程专题之Callable、Future与FutureTask(含源码分析)
- 面试官: 有了解过线程组和线程优先级吗
- 面试官: 说一下线程的生命周期过程
- 面试官: 说一下线程间的通信
- 面试官: 说一下Java的共享内存模型
- 面试官: 有了解过指令重排吗,什么是happens-before
- 面试官: 有了解过volatile关键字吗 说说看
- 面试官: 有了解过Synchronized吗 说说看
- Java多线程专题之Lock锁的使用
- 面试官: 有了解过ReentrantLock的底层实现吗?说说看
- 面试官: 有了解过CAS和原子操作吗?说说看
- Java多线程专题之线程池的基本使用
- 面试官: 有了解过线程池的工作原理吗?说说看
- 面试官: 线程池是如何做到线程复用的?有了解过吗,说说看
- 面试官: 阻塞队列有了解过吗?说说看
- 面试官: 阻塞队列的底层实现有了解过吗? 说说看
- 面试官: 同步容器和并发容器有用过吗? 说说看
- 面试官: CopyOnWrite容器有了解过吗? 说说看
- 面试官: Semaphore在项目中有使用过吗?说说看(源码剖析)
- 面试官: Exchanger在项目中有使用过吗?说说看(源码剖析)
- 面试官: CountDownLatch有了解过吗?说说看(源码剖析)
- 面试官: CyclicBarrier有了解过吗?说说看(源码剖析)
- 面试官: Phaser有了解过吗?说说看
- 面试官: Fork/Join 有了解过吗?说说看(含源码分析)
- 面试官: Stream并行流有了解过吗?说说看