Я изучаю Spring OAuth и у меня возникли некоторые проблемы с CORS и предполётными запросами, возможно, кто-то может мне помочь.
В качестве основы я взял пример проекта из книги "Cloud Native Java": https://github.com/cloud-native-java/edge
Для моего вопроса важны две части: служба SSO шлюза (greetings-client) и служба авторизации (служба аутентификации).
Вот конфигурация SSO:
@Configuration
@EnableOAuth2Sso
class SsoConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.antMatchers( "/", "/app.js", "/login**", "/webjars/**").permitAll().anyRequest()
.authenticated().and().logout().logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
EdgeСервис имеет простую часть пользовательского интерфейса.При непосредственном вызове он пытается сделать запрос к / user конечной точке, которая защищена, для получения информации о Принципале.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>Edge Service</title>
<meta name="description" content=""/>
<meta name="viewport" content="width=device-width"/>
<base href="/"/>
<script type="text/javascript"
src="/webjars/jquery/jquery.min.js"></script>
<script type="text/javascript"
src="/webjars/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript"
src="/webjars/angularjs/angular.min.js"></script>
</head>
<body ng-app="app" ng-controller="home as home">
<div class="container" ng-show="!home.authenticated">
<a href="/login">Login </a>
</div>
<div class="container" ng-show="home.authenticated">
<!--1-->
Logged in as:
<b><span ng-bind="home.user"></span></b> <br/>
Token:
<b><span ng-bind="home.token"></span> </b><br/>
Greeting from Zuul Route: <b>
<span ng-bind="home.greetingFromZuulRoute"></span></b> <br/>
Greeting from Edge Service (Feign):
<b><span ng-bind="home.greetingFromEdgeService"></span></b><br/>
</div>
<!--2-->
<script type="text/javascript" src="app.js"></script>
</body>
</html>
И javascript:
var app = angular.module("app", []);
//<1>
app.factory('oauth', function () {
return {details: null, name: null, token: null};
});
app.run(['$http', '$rootScope', 'oauth', function ($http, $rootScope, oauth) {
$http.get("/user").success(function (data) {
oauth.details = data.userAuthentication.details;
oauth.name = oauth.details.name;
oauth.token = data.details.tokenValue;
// <2>
$http.defaults.headers.common['Authorization'] = 'bearer ' + oauth.token;
// <3>
$rootScope.$broadcast('auth-event', oauth.token);
});
}]);
app.controller("home", function ($http, $rootScope, oauth) {
var self = this;
self.authenticated = false;
// <4>
$rootScope.$on('auth-event', function (evt, ctx) {
self.user = oauth.details.name;
self.token = oauth.token;
self.authenticated = true;
var name = window.prompt('who would you like to greet?');
// <5>
$http.get('/greetings-service/greet/' + name)
.success(function (greetingData) {
self.greetingFromZuulRoute = greetingData.greeting;
})
.error(function (e) {
console.log('oops!' + JSON.stringify(e));
});
// <6>
$http.get('/lets/greet/' + name)
.success(function (greetingData) {
self.greetingFromEdgeService = greetingData.greeting;
})
.error(function (e) {
console.log('oops!' + JSON.stringify(e));
});
});
});
Таким образом, ожидается , что процедура входа в систему инициируется и появляется форма входа.
Фактический результат: браузер перенаправляется на сервер авторизации и вызывает ошибку CORS
Доступ к XMLHttpRequest в 'http://localhost:9191/uaa/oauth/authorize?client_id=html5&redirect_uri=http://localhost:8082/login&response_type=code&state=1zegi7' (перенаправлен из' http://localhost:8082/user') из источника 'http://localhost:8082' заблокирован политикой CORS: Ответ на запрос предполетной проверки не проходитпроверка контроля доступа: у него нет статуса HTTP ok.
Здесь: localhost: 8082 - служба шлюза, localhost: 9191 - сервер авторизации.
В консоли обозревателя я вижу, чтобыл запрос OPTIONS.
С другой стороны, если я явно вызываю конечную точку / login (предоставлена Spring), она работает как положено - появляется форма входа в систему, и после проверки учетных данных меня перенаправляютобратно в домстр.
Служба шлюза имеет простой фильтр сервлетов, где я явно устанавливаю заголовок ACCESS_CONTROL_ALLOW_ORIGIN.
@Component
class CorsFilter implements Filter {
private final Log log = LogFactory.getLog(getClass());
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = HttpServletResponse.class.cast(res);
HttpServletRequest request = HttpServletRequest.class.cast(req);
log.info(request.getMethod());
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
Вопросы:
- Какправильно обрабатывать запросы предварительной проверки в таком случае?
- Разве фильтр сервлета не должен обрабатывать запрос OPTIONS?(Я не вижу в логах)
Что я пробовал:
- Использовать явный фильтр сервлетов (показан выше)
Используйте метод HttpSecurity.cors () в сочетании с компонентом CorsConfigurationSource:
@Configuration
@EnableOAuth2Sso
class SsoConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().antMatcher("/**").authorizeRequests()
.antMatchers( "/", "/app.js", "/login**", "/webjars/**").permitAll().anyRequest()
.authenticated().and().logout().logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedMethods(Arrays.asList("GET","POST","OPTIONS"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
ОБНОВЛЕНИЕ: Вот свойства, которые я использовал для шлюза SSO:
spring.application.name=greetings-client
server.port=${PORT:8082}
security.oauth2.resource.userInfoUri=http://auth-service/uaa/user
spring.mvc.dispatch-options-request=true
zuul.routes.hi.path=/lets/**
zuul.routes.hi.serviceId=greetings-service
management.security.enabled=false
zuul.ignoredServices=*
eureka.instance.preferIpAddress=true
eureka.instance.leaseRenewalIntervalInSeconds=10
А вот свойства для службы аутентификации:
server.port=${PORT:9191}
spring.application.name=auth-service
server.context-path=/uaa
security.sessions=if_required
logging.level.org.springframework.security=DEBUG
spring.jpa.hibernate.ddl-auto=create
spring.jpa.generate-ddl=true
eureka.instance.preferIpAddress=true
eureka.instance.leaseRenewalIntervalInSeconds=10
и настройки сервера авторизации:
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final ClientDetailsService clientDetailsService;
@Autowired
public AuthorizationServerConfiguration(
AuthenticationManager authenticationManager,
ClientDetailsService clientDetailsService) {
this.authenticationManager = authenticationManager;
this.clientDetailsService = clientDetailsService;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// <1>
clients.withClientDetails(this.clientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
// <2>
endpoints.authenticationManager(this.authenticationManager);
}
}