上回解释了一下spring-security,这回讲spring-cloud-gateway,这是所有微服务性能的核心
先说阴谋阳谋,先说结论:阴谋诛心,阳谋论事,阴谋过多则死气沉沉,沆瀣一气,人人自危,阳谋过多就积重难返,争吵不休,不利团结
以昨天对话为例:
其中的阴阳应该很容易分辨,阴话话术不给具体问题,给你扣帽子,自身逍遥事外,
让你自我怀疑,类似PUA,阳话话术老子天下无敌,啥也不惧,打架的架势
可怜现在职场中阴风阵阵,有事不说,先扣帽子,你可以发现哪儿都死气沉沉,这就
是职场现状
著名阴谋例子,阿里,出来的所谓管理是将人分成老牛型,兔子型,疯狗
型,反正都是畜生,而一旦你接受了这种设定,那组织就很稳定了
阳谋例子,三个代表,共产党是这么说的,也是这么在做,所以叫阳谋,而一旦你接
受了这种设定,那党行事快速便捷,同时也有隐患,毕竟并不是每个人都能代表群
众,出事了不利团结
讲这个是为了防止被人pua,远离过阴或过阳的人(否则要么精神上被折磨要么肉体上被折磨)
spring-cloud-gateway没什么可难的,但是在微服务体系内它是作为oauth2客户端存在的
先讲依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
</dependencies>
因为微服务种网关的安全性必须依赖认证系统,因此在微服务种作为oauth2客户端
具体做法如下:
认证服务器为https,又使用自己颁发证书的时候,加入信任列表
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Properties;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
@Data
@Component
@ConfigurationProperties(prefix = "cert")
@ConditionalOnProperty(prefix = "cert", name = "enabled", havingValue = "true")
public class CertConfig {
private String trustStore;
private String trustStorePassword;
private @Autowired ObjectMapper objectMapper;
@PostConstruct
public void init() throws FileNotFoundException, JsonProcessingException {
if(StringUtils.isEmpty(trustStore)) {
trustStore = "classpath:trust.jks";
trustStorePassword = "123456";
}
File file = ResourceUtils.getFile(trustStore);
Properties systemProps = System.getProperties();
systemProps.put("javax.net.ssl.trustStore", file.getAbsolutePath());
systemProps.put("javax.net.ssl.trustStorePassword", trustStorePassword);
System.setProperties(systemProps);
}
}
当你需要刷新token的时候,过滤器(国外某大神的作品,必须设置reuseToken为false,不然会出错,不过应该是spring security的问题)
import java.time.Duration;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* Token Relay Gateway Filter with Token Refresh. This can be removed when issue {@see https://github.com/spring-cloud/spring-cloud-security/issues/175} is closed.
* Implementierung in Anlehnung an {@link ServerOAuth2AuthorizedClientExchangeFilterFunction}
*/
@Component
public class TokenRelayWithTokenRefreshGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
private final ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
private static final Duration accessTokenExpiresSkew = Duration.ofSeconds(6);
public TokenRelayWithTokenRefreshGatewayFilterFactory(ServerOAuth2AuthorizedClientRepository authorizedClientRepository,
ReactiveClientRegistrationRepository clientRegistrationRepository) {
super(Object.class);
this.authorizedClientManager = createDefaultAuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
}
private static ReactiveOAuth2AuthorizedClientManager createDefaultAuthorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
final ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken(configurer -> configurer.clockSkew(accessTokenExpiresSkew))
.clientCredentials(configurer -> configurer.clockSkew(accessTokenExpiresSkew))
.password(configurer -> configurer.clockSkew(accessTokenExpiresSkew))
.build();
final DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
public GatewayFilter apply() {
return apply((Object) null);
}
@Override
public GatewayFilter apply(Object config) {
return (exchange,chain) -> exchange.getPrincipal()
//.log("token-relay-filter")
.filter(principal->principal instanceof OAuth2AuthenticationToken)
.cast(OAuth2AuthenticationToken.class)
.flatMap(this::authorizeClient)
.map(OAuth2AuthorizedClient::getAccessToken)
.map(token -> withBearerAuth(exchange, token))
// TODO: adjustable behavior if empty
.defaultIfEmpty(exchange).flatMap(chain::filter);
}
private ServerWebExchange withBearerAuth(ServerWebExchange exchange, OAuth2AccessToken accessToken) {
return exchange.mutate().request(r -> r.headers(headers -> headers.setBearerAuth(accessToken.getTokenValue()))).build();
}
private Mono<OAuth2AuthorizedClient> authorizeClient(OAuth2AuthenticationToken oAuth2AuthenticationToken) {
final String clientRegistrationId = oAuth2AuthenticationToken.getAuthorizedClientRegistrationId();
return Mono.defer(() -> authorizedClientManager.authorize(createOAuth2AuthorizeRequest(clientRegistrationId, oAuth2AuthenticationToken)));
}
private OAuth2AuthorizeRequest createOAuth2AuthorizeRequest(String clientRegistrationId, Authentication principal) {
return OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId).principal(principal).build();
}
}
需要在网关登出时候,必备
import java.net.URI;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler;
@EnableWebFluxSecurity
public class WebFluxSecurityConfig {
@Value("${auth.logout}")
private String logout;
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
RedirectServerLogoutSuccessHandler handler = new RedirectServerLogoutSuccessHandler();
handler.setLogoutSuccessUrl(URI.create(logout));
http.authorizeExchange((exchanges) -> exchanges
.pathMatchers("/actuator/**", "/oauth/**").permitAll()
.anyExchange().authenticated()).csrf().disable().oauth2Login().and()
.cors().and().logout().logoutSuccessHandler(handler);
return http.build();
}
}
开发时若需要显示服务列表时,可自己写一个页面列出服务,@Profile(“dev”)表示只在dev环境存在,route是自己写的类,懂的自己自然会写
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.Setter;
import sample.domain.Route;
@Controller
@ConfigurationProperties(prefix = "spring.cloud.gateway")
@Setter
@Profile("dev")
public class GatewayIndexDevController {
private List<Route> routes;
@GetMapping("/")
public String demo(Model model) {
for (Route route : routes) {
if ("auth-server".equals(route.getId())) {
continue;
}
String[] paths = route.getPredicates();
String path = paths[0];
route.setPath(path.substring(path.indexOf("/")).replace("**", "swagger-ui/index.html"));
}
model.addAttribute("routes", routes);
return "index";
}
}
由于gateway作为oauth2客户端,配置稍微复杂点,如下,意思是这样(auth-server和gateway安装在k8s)
auth:
server: https://auth.server
server-k8s: http://gateway-auth:8080
logout: ${auth.server}/oauth/exit
server:
http2:
enabled: true
ssl:
enabled: true
key-store: classpath:gateway.sample.p12
key-store-password: 123456
keyStoreType: PKCS12
spring:
thymeleaf:
cache: false
security.oauth2.client:
registration:
auth-server:
client_id: gateway-sample
client_secret: 1234
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: all
provider:
auth-server:
authorization-uri: ${auth.server}/oauth/authorize
token-uri: ${auth.server-k8s}/oauth/token
jwk-set-uri: ${auth.server-k8s}/oauth/token_key
user-info-uri: ${auth.server-k8s}/oauth/userInfo
user-name-attribute: name
cloud.gateway.routes:
- id: websocket
uri: http://192.168.108.1:8010
predicates:
- Path=/websocket/**
filters:
- StripPrefix=1
- TokenRelayWithTokenRefresh=
- RemoveRequestHeader=Cookie
最后的效果如图示:
输入地址:
跳转至auth-server
登陆之后,是swagger列表,当打开相应的连接,就是资源服务器api的swagger文档