赞
踩
<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>
server: port: 8081 spring: cloud: gateway: routes: - id: product uri: http://localhost:9000 predicates: - Host=product.gblfy.com** - id: auth uri: http://localhost:5000 predicates: - Path=/oauth/token - id: skill uri: http://localhost:13000 predicates: - Path=/skill datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/auth-serv?characterEncoding=UTF-8&serverTimezone=GMT%2B8 username: root password: 123456
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"); } /** * 实现权限验证判断 */ @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 -> { 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; } }
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; } @Override public 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从数据库获取不到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); } }
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"; @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(); } }
这个类是SpringCloug Gateway 与 Oauth2整合的关键,通过构建认证过滤器 AuthenticationWebFilter 完成Oauth2.0的token校验。
AuthenticationWebFilter 通过我们自定义的 ReactiveJdbcAuthenticationManager 完成token校验。
SpringCloudAliaba 基于OAth2.0 搭建认证授权中心
模块名称product-serv
<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>
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"; } }
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);
}
}
server:
port: 9000
spring:
cloud:
nacos:
discovery:
service: product-serv
server-addr: localhost:8848
不携带token
通过网关请求产品服务,提示需要认证
http://localhost:8081/oauth/token
通过认证授权中心获取toekn
grant_type:password
client_id:app
client_secret:app
username:ziya
password:111111
发起请求,获取token
http://product.gblfy.com:8081/product/1
Authorization:Bearer d364c6cc-3c60-402f-b3d0-af69f6d6b73e
从4.6和4.7可以看出,当从授权中心获取token,携带token通过网关服务请求产品服务和直接请求产品服务效果是一样的。
从以上测试结果可以看出gateway已经启动了一个统一认证授权的作用,对获取的token进行校验。以前我们所有的模块都需要集成认证授权模块,现在呢?所有的流量都从微服务网关SpringCloud Gateway走,那认证授权也是通过gateway来做的。因此,只需要在网关集成认证授权模块,其他的都不需要集成和配置。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。