Единый вход (SSO) — это процесс аутентификации, который позволяет пользователям получать доступ к нескольким системам с помощью одного входа. В этой статье будет глубоко проанализирован принцип единого входа и подробно описано, как реализовать единый вход в среду Spring Cloud. С помощью конкретных архитектурных схем и примеров кода мы продемонстрируем рабочий механизм и преимущества SSO, чтобы помочь разработчикам лучше понять и применять эту технологию.
Единый вход (SSO) — это механизм аутентификации, который позволяет пользователям получать доступ к нескольким взаимно доверенным прикладным системам после прохождения одной проверки личности. Это упрощает операции пользователя, улучшает взаимодействие с пользователем и снижает сложность управления несколькими системами аутентификации. В современных распределенных системах и микросервисных архитектурах единый вход особенно важен, поскольку он позволяет сократить количество повторных операций входа в систему, унифицировать вход для аутентификации пользователей, а также повысить безопасность и управляемость системы.
Основной принцип единого входа заключается в обеспечении доступа к нескольким системам путем совместного использования статуса аутентификации. Его основные шаги включают в себя:
Принципиальная схема архитектуры единого входа выглядит следующим образом:
Реализация единого входа в среде Spring Cloud требует рассмотрения следующих шагов:
шаг | описывать |
---|---|
Создать центр сертификации | Создайте специальную службу аутентификации, отвечающую за вход пользователей и генерацию токенов. Эта функциональность может быть достигнута с помощью Spring Security и OAuth2. |
Настройка службы шлюза | Маршрутизация запросов и проверка токена реализуются через Spring Cloud Gateway или Zuul. |
Интеграция сервисов приложений | Интегрируйте каждую службу приложений с центром сертификации, чтобы гарантировать, что каждый запрос проверяется Token. |
Центр аутентификации отвечает за аутентификацию пользователей и генерацию токенов. Полномочия аутентификации могут быть реализованы с использованием Spring Security и OAuth2.
Добавьте в проект зависимость OAuth2:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
Создайте и настройте сервер аутентификации OAuth2:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client-id")
.secret("{noop}client-secret")
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.scopes("read", "write")
.redirectUris("http://localhost:8081/login/oauth2/code/custom");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(new InMemoryTokenStore());
}
}
Создайте и настройте Spring Security:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("{noop}password")
.roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Служба шлюза отвечает за маршрутизацию запросов и проверку токенов. Для этого мы используем Spring Cloud Gateway.
Добавьте в проект зависимость службы шлюза:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
Добавьте конфигурацию шлюза в application.yml:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user/**
filters:
- TokenRelay
security:
oauth2:
client:
provider:
custom:
authorization-uri: http://localhost:8080/oauth/authorize
token-uri: http://localhost:8080/oauth/token
user-info-uri: http://localhost:8080/userinfo
jwk-set-uri: http://localhost:8080/.well-known/jwks.json
registration:
custom:
client-id: client-id
client-secret: client-secret
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: read,write
Каждый микросервис необходимо настроить как сервер ресурсов, чтобы обеспечить проверку токена каждого запроса.
Добавьте зависимость сервера ресурсов OAuth2 к каждой службе:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
Добавьте конфигурацию сервера ресурсов OAuth2 в файл application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/
Создайте и настройте Spring Security, чтобы обеспечить проверку токенов всех запросов:
@Configuration
@EnableWebSecurity
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
Процесс реализации единого входа включает в себя клиентское приложение, запрашивающее аутентификацию из центра аутентификации и получающее токен, а затем передающее токен каждому микросервису для доступа к ресурсам. Ниже приведены подробные инструкции по вызову кода для единого входа, включая пример кода для получения кода авторизации, запроса токена доступа и использования токена для доступа к защищенному ресурсу.
Сначала клиентскому приложению необходимо направить пользователя в центр сертификации SSO для входа в систему и получения кода авторизации.
Этого можно добиться с помощью перенаправления браузера:
@GetMapping("/login")
public void login(HttpServletResponse response) throws IOException {
String authorizationUri = "http://localhost:8080/oauth/authorize";
String clientId = "client-id";
String redirectUri = "http://localhost:8081/callback";
String responseType = "code";
String scope = "read write";
String authUrl = authorizationUri + "?response_type=" + responseType
+ "&client_id=" + clientId
+ "&redirect_uri=" + URLEncoder.encode(redirectUri, "UTF-8")
+ "&scope=" + URLEncoder.encode(scope, "UTF-8");
response.sendRedirect(authUrl);
}
После успешного входа пользователя в центр сертификации центр сертификации перенаправит его обратно в клиентское приложение с кодом авторизации.
Клиентскому приложению необходимо использовать этот код авторизации для запроса токена доступа:
@GetMapping("/callback")
public String callback(@RequestParam("code") String code, Model model) {
String tokenUri = "http://localhost:8080/oauth/token";
String clientId = "client-id";
String clientSecret = "client-secret";
String redirectUri = "http://localhost:8081/callback";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(clientId, clientSecret);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("code", code);
params.add("redirect_uri", redirectUri);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<Map> response = restTemplate.exchange(tokenUri, HttpMethod.POST, request, Map.class);
Map<String, Object> responseBody = response.getBody();
String accessToken = (String) responseBody.get("access_token");
model.addAttribute("accessToken", accessToken);
return "home";
}
После получения токена доступа клиентское приложение может использовать его для доступа к защищенным ресурсам.
Вот пример кода использования RestTemplate для доступа к защищенному ресурсу:
@GetMapping("/resource")
public String getResource(@RequestParam("accessToken") String accessToken, Model model) {
String resourceUri = "http://localhost:8082/resource";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<String> request = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(resourceUri, HttpMethod.GET, request, String.class);
String resource = response.getBody();
model.addAttribute("resource", resource);
return "resource";
}
Примерная структура проекта выглядит следующим образом:
/sso-auth-server
├── src/main/java/com/example/auth
│ ├── AuthorizationServerConfig.java
│ ├── SecurityConfig.java
│ └── Application.java
└── src/main/resources
└── application.yml
/sso-gateway
├── src/main/java/com/example/gateway
│ └── Application.java
└── src/main/resources
└── application.yml
/sso-user-service
├── src/main/java/com/example/user
│ ├── ResourceServerConfig.java
│ └── Application.java
└── src/main/resources
└── application.yml
/sso-client
├── src/main/java/com/example/client
│ ├── SsoController.java
│ └── Application.java
└── src/main/resources
└── application.yml
Данная структура состоит из четырех основных частей:
Преимущества | описывать |
---|---|
Улучшенный пользовательский интерфейс | Пользователям достаточно войти в систему только один раз, чтобы получить доступ ко всем системам, избегая громоздких операций повторных входов в систему. |
Улучшения безопасности | Унифицированный вход для аутентификации и централизованное управление могут повысить безопасность системы и снизить риск независимого управления каждой системой аутентификационной информацией. |
Управление упрощено | Централизованное управление идентификационной информацией пользователей и логикой аутентификации упрощает обслуживание и управление системой. |
Повышение производительности | Это сокращает время, которое пользователи тратят на переключение между различными системами, тем самым повышая общую эффективность работы. |
Сокращение затрат на разработку | Разработчикам необходимо реализовать логику аутентификации и авторизации только один раз, что может сэкономить затраты на разработку и поддержку нескольких систем аутентификации. |
Единое управление пользователями | Пользовательские данные и разрешения хранятся и управляются централизованно, что делает обновление информации о пользователях и изменение разрешений более удобным и эффективным. |
Улучшите аудит и соблюдение требований | Централизованные механизмы аутентификации и авторизации помогают отслеживать поведение пользователей и соответствовать нормативным требованиям и требованиям аудита. |
Легко интегрироваться и расширяться | Новые прикладные системы можно легко интегрировать без необходимости настройки отдельных процессов входа и аутентификации для каждой новой системы. |
Единый вход (SSO) играет важную роль в современных распределенных системах. Он не только улучшает взаимодействие с пользователем, но и повышает безопасность системы. Реализация единого входа через Spring Cloud позволяет в полной мере использовать экосистему и мощные функции Spring для достижения эффективной аутентификации личности и управления авторизацией.
В реальных приложениях разработчики должны выбирать подходящие решения по внедрению, основанные на конкретных потребностях и архитектуре системы, и постоянно оптимизировать их для повышения производительности и безопасности системы. Вот некоторые ключевые моменты:
Благодаря вышеуказанным мерам общая производительность системы единого входа может быть эффективно улучшена для удовлетворения бизнес-потребностей и требований безопасности предприятия.