文章目录
- 一、网关搭建
- 1. 引入依赖
- 2. 配置文件
- 3. 增加权限管理器
- 4. 自定义认证接口管理类
- 5. 增加网关层的安全配置
- 6. 搭建授权认证中心
- 二、搭建产品服务
- 2.1. 创建boot项目
- 2.2. 引入依赖
- 2.3. controller
- 2.4. 启动类
- 2.5. 配置
- 四、测试验证
- 4.1. 启动nacos
- 4.2. 启动认证中心
- 4.3. 启动产品服务
- 4.3. 请求认证授权中心
- 4.4. 网关请求产品模块
- 4.5. 获取token
- 4.6. 携带token请求产品服务
- 4.7. 直接请求产品服务
- 4.8. 请求结果比对
- 五、总结
一、网关搭建
1. 引入依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.2.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><spring.cloud-version>Hoxton.SR9</spring.cloud-version></properties><dependencies><!--安全认证框架--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--security-oauth2整合--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-resource-server</artifactId></dependency><!--oauth2--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency></dependencies><dependencyManagement><!--https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E--><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>
2. 配置文件
server:port: 8081
spring:cloud:gateway:routes:- id: producturi: http://localhost:9000predicates:- Host=product.gblfy.com**- id: authuri: http://localhost:5000predicates:- Path=/oauth/token- id: skilluri: http://localhost:13000predicates:- Path=/skilldatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/auth-serv?characterEncoding=UTF-8&serverTimezone=GMT%2B8username: rootpassword: 123456
3. 增加权限管理器
package com.gblfy.gatewayserv.config;import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;@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("/**/v2/api-docs/**");permitAll.add("/**/swagger-resources/**");permitAll.add("/webjars/**");permitAll.add("/doc.html");permitAll.add("/swagger-ui.html");permitAll.add("/**/oauth/**");permitAll.add("/**/current/get");}/*** 实现权限验证判断*/@Overridepublic 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 -> {return 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.toString());return true;}
}
4. 自定义认证接口管理类
package com.gblfy.gatewayserv.config;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import reactor.core.publisher.Mono;public class ReactiveJdbcAuthenticationManager implements ReactiveAuthenticationManager {Logger logger= LoggerFactory.getLogger(ReactiveJdbcAuthenticationManager.class);private TokenStore tokenStore;public ReactiveJdbcAuthenticationManager(TokenStore tokenStore){this.tokenStore = tokenStore;}@Overridepublic Mono<Authentication> authenticate(Authentication authentication) {return Mono.justOrEmpty(authentication).filter(a -> a instanceof BearerTokenAuthenticationToken).cast(BearerTokenAuthenticationToken.class).map(BearerTokenAuthenticationToken::getToken).flatMap((accessToken ->{logger.info("accessToken is :{}",accessToken);OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken);//根据access_token从数据库获取不到OAuth2AccessTokenif(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);}
}
5. 增加网关层的安全配置
package com.gblfy.gatewayserv.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;import javax.sql.DataSource;@Configuration
public class SecurityConfig {private static final String MAX_AGE = "18000L";@Autowiredprivate DataSource dataSource;@Autowiredprivate AccessManager accessManager;@BeanSecurityWebFilterChain 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();}
}
这个类是SpringCloug Gateway 与 Oauth2整合的关键,通过构建认证过滤器 AuthenticationWebFilter 完成Oauth2.0的token校验。
AuthenticationWebFilter 通过我们自定义的 ReactiveJdbcAuthenticationManager 完成token校验。
6. 搭建授权认证中心
SpringCloudAliaba 基于OAth2.0 搭建认证授权中心
二、搭建产品服务
2.1. 创建boot项目
模块名称product-serv
2.2. 引入依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.2.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.gblfy</groupId><artifactId>product-serv</artifactId><version>1.0-SNAPSHOT</version><!--https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E--><properties><java.version>1.8</java.version><spring.cloud-version>Hoxton.SR9</spring.cloud-version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--服务注册发现--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency></dependencies><dependencyManagement><dependencies><!--spring-cloud 版本控制--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring.cloud-version}</version><type>pom</type><scope>import</scope></dependency><!--spring-cloud-alibaba 版本控制--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2.2.6.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
2.3. controller
package com.gblfy.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ProductController {//http://localhost:9000/product/" + productId@GetMapping("/product/{productId}")public String getProductName(@PathVariable Integer productId) {return "IPhone 12";}
}
2.4. 启动类
package com.gblfy;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class ProductAplication {public static void main(String[] args) {SpringApplication.run(ProductAplication.class);}
}
2.5. 配置
server:port: 9000
spring:cloud:nacos:discovery:service: product-servserver-addr: localhost:8848
四、测试验证
4.1. 启动nacos
4.2. 启动认证中心
4.3. 启动产品服务
4.3. 请求认证授权中心
不携带token
4.4. 网关请求产品模块
通过网关请求产品服务,提示需要认证
4.5. 获取token
http://localhost:8081/oauth/token
通过认证授权中心获取toekn
grant_type:password
client_id:app
client_secret:app
username:ziya
password:111111
发起请求,获取token
4.6. 携带token请求产品服务
http://product.gblfy.com:8081/product/1
Authorization:Bearer d364c6cc-3c60-402f-b3d0-af69f6d6b73e
4.7. 直接请求产品服务
4.8. 请求结果比对
从4.6和4.7可以看出,当从授权中心获取token,携带token通过网关服务请求产品服务和直接请求产品服务效果是一样的。
五、总结
从以上测试结果可以看出gateway已经启动了一个统一认证授权的作用,对获取的token进行校验。以前我们所有的模块都需要集成认证授权模块,现在呢?所有的流量都从微服务网关SpringCloud Gateway走,那认证授权也是通过gateway来做的。因此,只需要在网关集成认证授权模块,其他的都不需要集成和配置。