上一篇文章《实战Spring Cloud Oauth2系列(十一)添加微服务网关及业务服务》我们做了一个初步的案例,也就是通过spring cloud gateway来访问这个product服务,这篇文章我们在spring cloud gateway里面集成一下oauth2.0的拦截,让其所有的访问都拦截校验一遍这个accesstoken,这样子就可以达到很安全的目的。
在spring cloud gateway里面拦截这个accesstoken的话,其实最主要的还是依靠的是spring security来进行拦截,所以这里首先我们需要添加一个SecurityConfig,代码示例如下:
package org.shop.gateway.server.config;
import org.shop.gateway.server.manager.AccessManager;
import org.shop.gateway.server.manager.AuthorizationManager;
import org.shop.gateway.server.manager.RedisTokenAuthenticationManager;
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.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
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;
@Configuration
public class SecurityConfig {
@Autowired
private AuthorizationManager authorizationManager;
@Autowired
private RedisTokenAuthenticationManager redisTokenAuthenticationManager;
@Bean
SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception {
// 认证过滤器
AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(redisTokenAuthenticationManager);
authenticationWebFilter.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());
http.httpBasic().disable().csrf().disable().authorizeExchange().pathMatchers(HttpMethod.OPTIONS).permitAll()
.anyExchange().access(authorizationManager).and()
// oauth2认证过滤器
.addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION);
return http.build();
}
}在这里我们主要是声明一个认证过滤器,也就是拦截所有的请求,然后交给这个认证过滤器来进行处理。这里认证过滤器的话,就是这个AuthorizationManager类,AuthorizationManager这个类的完整代码如下:
package org.shop.gateway.server.manager;
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;
import javax.annotation.PostConstruct;
@Slf4j
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
//设置白名单
private Set<String> permitAll = new ConcurrentSkipListSet<>();
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
@PostConstruct
public void initPermitAll() {
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();
log.info("当前请求的资源路径是:{}",requestPath);
// 是否直接放行
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;
}
}这里主要是做的拦截,匹配每一个url,遇到url是白名单的,则放行,如果不在白名单内,那么就使用oauth进行验证下token信息,这里还涉及到一个RedisTokenAuthenticationManager这个类,这个类主要是由于我们把oauth生成的token存放在redis中的,所以需要从redis中取对应的token。RedisTokenAuthenticationManager这个类的完整代码如下:
package org.shop.gateway.server.manager;
import javax.annotation.PostConstruct;
import org.shop.common.model.rule.CustomAuthenticationKeyGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class RedisTokenAuthenticationManager implements ReactiveAuthenticationManager {
@Autowired
private RedisConnectionFactory connectionFactory;
private TokenStore tokenStore;
@PostConstruct
public void initTokenStore() {
RedisTokenStore tokenStore = new RedisTokenStore(connectionFactory);
tokenStore.setAuthenticationKeyGenerator(new CustomAuthenticationKeyGenerator());
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.info("accessToken is :{}", accessToken);
OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken);
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);
}
}有了这三个类,我们就完整的实现了在spring cloud gateway里面拦截oauth2.0客户端的需求,然后我们启动下gateway,oauth,product这三个服务,首先申请一个token:
http://127.0.0.1:8081/oauth/token?grant_type=password&scope=all&client_id=book&client_secret=book&username=wang&password=wang&device_type=102
申请完token之后,我们请求product服务:
http://127.0.0.1:8080/product/hello
当我们在header头里面带上token的时候就会看到请求成功了,没有带上或者带上错误的token,则请求会失败,程序会报错(报错我们在后面的文章处理)
如果我们输入错误的token,则会显示失败的信息:
以上就是在springf cloud gateway里面添加oauth2.0拦截的案例,最后附上源码,登录后即可下载。












还没有评论,来说两句吧...