Введение: в последней статье мы извлекли отдельную службу аутентификации.Основным содержанием этой главы является интеграция Oauth2 с Spring Cloud Gateway.
Раздел концепции
После того, как шлюз интегрирует Oauth2.0, наша архитектура процесса будет такой, как указано выше. Основная логика следующая:
1. Клиентское приложение запрашивает у сервера аутентификации получение access_token через API-шлюз.http://localhost:8090/auth-service/oauth/token
2. Сервер аутентификации возвращает access_token
{
"access_token": "f938d0c1-9633-460d-acdd-f0693a6b5f4c",
"token_type": "bearer",
"refresh_token": "4baea735-3c0d-4dfd-b826-91c6772a0962",
"expires_in": 43199,
"scope": "web"
}
3. Клиент имеет access_token для доступа к серверной службе через шлюз API.
4. После того, как API-шлюз получит access_token, он передаетAuthenticationWebFilter
Авторизоваться на access_token
5. Шлюз API перенаправляет внутренний запрос, а внутренняя служба запрашивает сервер аутентификации Oauth2 для получения текущего пользователя.
В предыдущей статье мы построили отдельный сервис аутентификации и авторизации Oauth2 и реализовали базовую функциональную структуру, на этот раз мы в основном реализуем четвертый пункт, как выполнять фильтрацию и проверку access_token после того, как SpringCloud интегрирует Oauth2.
пример кода
импортные компоненты
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
В основном вводится пакет jar, связанный с oauth2, а также необходимо представить пакет jar, связанный с базой данных, поскольку наш токен хранится в базе данных, чтобы проверить действительность токена на уровне шлюза, токен должен быть извлекается из базы данных в первую очередь.
модификация конфигурации bootstrap.yml
spring:
application:
name: cloud-gateway
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://xx.0.xx.xx:3306/oauth2_config?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: xxxxx
password: xxxxxxx
driver-class-name: com.mysql.jdbc.Driver
В основном настройте адрес подключения к базе данных oauth2
Пользовательский класс управления интерфейсом проверки подлинности
В среде webFlux путем реализацииReactiveAuthenticationManager
Интерфейс Пользовательское управление интерфейсом аутентификации, потому что наш токен хранится в jdbc, поэтому имя называетсяReactiveJdbcAuthenticationManager
@Slf4j
public class ReactiveJdbcAuthenticationManager implements ReactiveAuthenticationManager {
private TokenStore tokenStore;
public JdbcAuthenticationManager(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.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);
}
}
Конфигурация безопасности уровня шлюза
@Configuration
public class SecurityConfig {
private static final String MAX_AGE = "18000L";
@Autowired
private DataSource dataSource;
@Autowired
private AccessManager accessManager;
/**
* 跨域配置
*/
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
@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()
// 跨域过滤器
.addFilterAt(corsFilter(), SecurityWebFiltersOrder.CORS)
//oauth2认证过滤器
.addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION);
return http.build();
}
}
Этот класс является ключом к интеграции SpringClug Gateway с Oauth2 путем создания фильтров аутентификации.AuthenticationWebFilter
Завершите проверку токена Oauth2.0.AuthenticationWebFilter
через наш обычайReactiveJdbcAuthenticationManager
Завершите проверку токена.
Мы также добавили сюдаCORS
Фильтры и диспетчер разрешенийAccessManager
Менеджер прав
@Slf4j
@Component
public class AccessManager implements ReactiveAuthorizationManager<AuthorizationContext> {
private Set<String> permitAll = new ConcurrentHashSet<>();
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;
}
}
Основное предназначение — отфильтровать статические ресурсы, в дальнейшем сюда же можно будет вынести некоторые проверки разрешений интерфейса.
контрольная работа
-
Вызов службы авторизации через шлюз для получения access_token
-
Добавить аутентификацию для доступа к серверным службам в заголовке
-
Фильтр шлюза для проверки токена
-
Проверка диспетчера разрешений
-
Перейдите на сервер аутентификации, чтобы проверить текущего пользователя.
-
вернуть нормальный результат
-
Умышленно написать неверный access_token, вернуть ответ об ошибке
-
Удалите access_token из заголовка запроса и верните напрямую
401 Unauthorized
Суммировать
Выполнив описанные выше шаги, мы интегрировали Spring Cloud Gateway с Oauth2.0, так что весь наш проект в основном завершен, и мы оптимизируем его на следующих нескольких этапах Добро пожаловать, продолжайте обращать внимание.
Серия статей SpringCloud Alibaba