Предисловие: в предыдущей статье говорилось о серии разрешений и реализации шлюзов микросервисов, которые существуют изолированно, а в этой статье будут интегрированы серверные службы со шлюзами и системами разрешений. Реализация части разрешений безопасности также объясняет реализацию, основанную на предварительной аутентификации, но, поскольку она тесно связана с бизнесом, здесь нет конкретного примера. Полномочия бизнеса тесно связаны с бизнесом.В этом интеграционном проекте будет реализована эта часть проверки полномочий операций на основе конкретных бизнес-сервисов.
1. Предыдущий обзор и интегрированный дизайн
существуетПроектирование и реализация аутентификации и управления разрешениями API в микросервисной архитектуреВ серии статей объясняется авторизация и аутентификация системы Auth в микросервисной архитектуре. существуетШлюз микросервисовВ разделе объясняется шлюз микросервиса на основе компонентов netflix-zuul. Давайте взглянем на архитектурную схему этой интеграции.
Разрешения микросервисной архитектуры
Весь процесс делится на две категории:
- Пользователь не авторизован. Клиент (веб- и мобильный) инициирует запрос на вход, шлюз напрямую перенаправляет запрос на вход в службу аутентификации, а служба аутентификации проверяет идентификационную информацию пользователя (в проекте интеграции отсутствует пользовательская система, читатель может реализовать ее самостоятельно, и напрямую жестко закодировать возвращаемую информацию о пользователе) и, наконец, вернуть действительный токен клиенту.
- Пользователь вошел в систему, запрашивая другие услуги. В этом случае, когда запрос клиента достигнет шлюза, шлюз вызовет систему аутентификации, чтобы проверить действительность запрошенной личности.Если проверка не пройдена, она будет отклонена напрямую и будет возвращена ошибка 401. Если проверка пройдена, он будет перенаправлен в конкретную службу.Идентификатор пользователя в заголовке получает информацию о разрешении безопасности пользователя. Используйте аспект для проверки разрешений, требуемых интерфейсом. Если он проходит, продолжайте, в противном случае верните 403.
Первая категория на самом деле относительно проста в объясненииПроектирование и реализация аутентификации и управления разрешениями API в микросервисной архитектуреЧто касается базовой реализации, то сейчас нам нужно интегрироваться со шлюзом; во второй категории мы создаем новую внутреннюю службу для интеграции со шлюзом и системой аутентификации.
Три службы, участвующие в интеграционном проекте, представлены ниже по отдельности. Обсуждалась реализация шлюза и службы аутентификации В этой статье в основном обсуждаются изменения, необходимые для интеграции этих двух служб, а также основная реализация серверной службы.
2. реализация шлюза
Шлюз микросервисовВ основном внедрена реализация шлюза, включая сервисную маршрутизацию, несколько методов фильтрации и т.д. В этом разделе основное внимание будет уделено интеграции в практическое применение. Места, которые необходимо изменить и улучшить, следующие:
- Различать открытые интерфейсы (то есть внешний прямой доступ) и интерфейсы, для доступа к которым требуется юридическая идентификация для входа в систему.
- Открытый интерфейс высвобождается напрямую и перенаправляется в определенные службы, такие как вход в систему, токен обновления и т. д.
- Интерфейс, к которому можно получить доступ только после входа в систему с юридическим лицом, построен в соответствии с входящим токеном доступа. Заголовок в основном включает идентификатор пользователя и другую информацию, которую можно установить в службе аутентификации в соответствии с вашим фактическим бизнесом.
- Наконец, более важно ввести конфигурацию сервера ресурсов Spring Security, установить allowAll() для открытого интерфейса, а другие интерфейсы вступают в процесс проверки достоверности личности, вызывают службу аутентификации и продолжают пересылать в обычном режиме, если они переданы. в противном случае генерируется исключение, возвращается 401.
Нарисованная блок-схема выглядит следующим образом:
Блок-схема маршрутизации через шлюз
2.1 Разрешить всю реализацию
К внешнему интерфейсу можно получить прямой доступ, что может зависеть от файла конфигурации, а файл конфигурации можно динамически обновлять через центр конфигурации, поэтому не нужно беспокоиться о проблеме жесткого кода.
Определите путь, для которого требуется PermitAll, в файле конфигурации.
auth:
permitall:
-
pattern: /login/**
-
pattern: /web/public/**
Когда служба запускается, прочитайте соответствующую конфигурацию, а следующие свойства конфигурации прочитайте конфигурацию, начинающуюся с auth.
@Bean
@ConfigurationProperties(prefix = "auth")
public PermitAllUrlProperties getPermitAllUrlProperties() {
return new PermitAllUrlProperties();
}
Конечно, также требуется класс сущности, соответствующий PermitAllUrlProperties, который относительно прост и не будет указан.
2.2 Укрепить голову
Фильтр — наиболее практичная технология в технологии сервлетов.Веб-разработчики используют технологию фильтра для перехвата всех веб-ресурсов, управляемых веб-сервером. Здесь фильтр используется для улучшения заголовка, анализа токена в запросе и создания унифицированной информации заголовка.При достижении конкретной службы идентификатор пользователя в заголовке можно использовать для получения и оценки полномочий операции.
public class HeaderEnhanceFilter implements Filter {
//...
@Autowired
private PermitAllUrlProperties permitAllUrlProperties;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
//主要的过滤方法
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String authorization = ((HttpServletRequest) servletRequest).getHeader("Authorization");
String requestURI = ((HttpServletRequest) servletRequest).getRequestURI();
// test if request url is permit all , then remove authorization from header
LOGGER.info(String.format("Enhance request URI : %s.", requestURI));
//将isPermitAllUrl的请求进行传递
if(isPermitAllUrl(requestURI) && isNotOAuthEndpoint(requestURI)) {
//移除头部,但不包括登录端点的头部
HttpServletRequest resetRequest = removeValueFromRequestHeader((HttpServletRequest) servletRequest);
filterChain.doFilter(resetRequest, servletResponse);
return;
}
//判断是不是符合规范的头部
if (StringUtils.isNotEmpty(authorization)) {
if (isJwtBearerToken(authorization)) {
try {
authorization = StringUtils.substringBetween(authorization, ".");
String decoded = new String(Base64.decodeBase64(authorization));
Map properties = new ObjectMapper().readValue(decoded, Map.class);
//解析authorization中的token,构造USER_ID_IN_HEADER
String userId = (String) properties.get(SecurityConstants.USER_ID_IN_HEADER);
RequestContext.getCurrentContext().addZuulRequestHeader(SecurityConstants.USER_ID_IN_HEADER, userId);
} catch (Exception e) {
LOGGER.error("Failed to customize header for the request", e);
}
}
} else {
//为了适配,设置匿名头部
RequestContext.getCurrentContext().addZuulRequestHeader(SecurityConstants.USER_ID_IN_HEADER, ANONYMOUS_USER_ID);
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
//...
}
В приведенном выше коде показан основной поток обработки расширения заголовка.Запрос isPermitAllUrl передается напрямую, в противном случае оценивается, соответствует ли он стандартному заголовку, а затем токен авторизации анализируется для создания USER_ID_IN_HEADER. Наконец, для адаптации установите анонимный заголовок.
Следует отметить, что HeaderEnhanceFilter также зарегистрирован. Spring предоставляет класс FilterRegistrationBean, который предоставляет метод setOrder, который может установить значение сортировки для фильтра, чтобы Spring мог сортировать веб-фильтр перед его регистрацией, а затем, в свою очередь, зарегистрировать его.
2.3 Конфигурация сервера ресурсов
Используйте конфигурацию сервера ресурсов, чтобы контролировать, какие открытые конечные точки не требуют проверки личности, прямой маршрутизации и пересылки, а какие требуют идентификации loadAuthentication для вызова службы проверки подлинности.
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//...
//配置permitAll的请求pattern,依赖于permitAllUrlProperties对象
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.requestMatchers().antMatchers("/**")
.and()
.authorizeRequests()
.antMatchers(permitAllUrlProperties.getPermitallPatterns()).permitAll()
.anyRequest().authenticated();
}
//通过自定义的CustomRemoteTokenServices,植入身份合法性的相关验证
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
CustomRemoteTokenServices resourceServerTokenServices = new CustomRemoteTokenServices();
//...
resources.tokenServices(resourceServerTokenServices);
}
}
Настройка сервера ресурсов должна быть знакома всем, кто читал предыдущие статьи автора, поэтому повторяться здесь не буду. оResourceServerSecurityConfigurer
Класс конфигурации, как упоминалось в предыдущей серии статей о безопасности,ResourceServerTokenServices
Интерфейс, мы тоже его использовали в то время, но использовался дефолтныйDefaultTokenServices
. здесь по обычаюCustomRemoteTokenServices
, внедрить соответствующую проверку легитимности личности.
Конечно, эта конфигурация также вводит соответствующие зависимости Spring Cloud Security oauth2.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
2.4 Пользовательская реализация RemoteTokenServices
ResourceServerTokenServices
Одна из реализаций интерфейсаRemoteTokenServices
.
Queries the /check_token endpoint to obtain the contents of an access token.
If the endpoint returns a 400 response, this indicates that the token is invalid.
RemoteTokenServices
В основном для запроса службы аутентификации/check_token
Конечная точка для получения результата проверки токена. Если есть ошибка, токен недействителен. авторыCustomRemoteTokenServices
Реализация должна следовать этой идее. Следует отметить, что авторский проект основан на облаке Spring, а сервис аутентификации является многоэкземплярным, поэтому здесь используется лента Netflix для получения сервиса аутентификации для балансировки нагрузки. Весеннее облако
Безопасность добавляет следующую конфигурацию по умолчанию, соответствующую соответствующей конечной точке в службе аутентификации.
security:
oauth2:
client:
accessTokenUri: /oauth/token
clientId: gateway
clientSecret: gateway
resource:
userInfoUri: /user
token-info-uri: /oauth/check_token
Что касается конкретногоCustomRemoteTokenServices
Для достижения вы можете обратиться к идеям, упомянутым выше, иRemoteTokenServices
, очень просто и здесь опущено.
На этом усовершенствование службы шлюза завершено.Давайте взглянем на нашу реализацию службы аутентификации и серверной службы.
Подчеркните, почему идентификатор пользователя и другая информация, передаваемая в заголовке, должна создаваться в шлюзе? Читатели могут подумать об этом сами, в сочетании с безопасностью и другими аспектами, 😆Автор ответа пока не даст.
3. интеграция авторизации
Интеграция и модификация службы аутентификации на самом деле не так уж и велика. Определение и взаимосвязь между пользователем, ролью и разрешением ранее не реализовывались. Оператор sql в этой части уже находится в auth.sql. Поэтому, чтобы дать полный пример, автор дополнил эту часть реализации, в основном соответствующим определением интерфейса и реализацией роли пользователя, роли и разрешения роли, чтобы реализовать добавления, удаления и изменения.
Если читатели хотят обратиться к проекту интеграции для практического применения, эта часть может быть расширена в соответствии с их собственным бизнесом, включая создание токенов, а настроенная информация также может быть единообразно обработана в шлюзе, а затем передана обратно. окончание службы после окончания строительства.
Интерфейсы здесь просто перечислены те, которые нужны, а остальные интерфейсы не прописаны (из-за лени...)
Эти два интерфейса также используются серверным проектом для получения соответствующего разрешения userId.
//根据userId获取用户对应的权限
@RequestMapping(method = RequestMethod.GET, value = "/api/userPermissions?userId={userId}",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<Permission> getUserPermissions(@RequestParam("userId") String userId);
//根据userId获取用户对应的accessLevel(好像暂时没用到。。)
@RequestMapping(method = RequestMethod.GET, value = "/api/userAccesses?userId={userId}",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<UserAccess> getUserAccessList(@RequestParam("userId") String userId);
Что ж, реализация здесь закончена, подробности смотрите в реализации в проекте.
4. Реализация бэкенд-проекта
Этот раздел является примером реализации бэкенда, каковы основные функции бэкенд-проекта? Давайте рассмотрим подготовку, сделанную службой шлюза и службой аутентификации до:
- Заголовок userId, созданный шлюзом (может быть и другая информация, это просто пример), который можно получить на бэкенде
- Все запросы, пересылаемые серверной службе, проверяются на достоверность удостоверения или напрямую доступны внешнему миру.
- служба авторизации, которая предоставляет интерфейс для получения соответствующих разрешений на основе userId
По ним автор рисует общую блок-схему бэкенда:
блок-схема серверной части
Приведенная выше блок-схема на самом деле очень понятна, сначала через фильтр-фильтр, заполнивSecurityContextHolder
контекст. Во-вторых, чтобы реализовать аннотацию через аспект, нужно ли вводить обработку выражения аспекта. Если он не нужен, напрямую выполнить метод в интерфейсе, в противном случае разобрать разрешения, требуемые в аннотации, чтобы определить, есть ли разрешение на выполнение, и если да, продолжить выполнение, иначе вернуть 403 запрещено.
4.1 фильтр фильтр
Фильтр Filter, используемый шлюзом выше, перехватывает HttpServletRequest клиента.
public class AuthorizationFilter implements Filter {
@Autowired
private FeignAuthClient feignAuthClient;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("过滤器正在执行...");
// pass the request along the filter chain
String userId = ((HttpServletRequest) servletRequest).getHeader(SecurityConstants.USER_ID_IN_HEADER);
if (StringUtils.isNotEmpty(userId)) {
UserContext userContext = new UserContext(UUID.fromString(userId));
userContext.setAccessType(AccessType.ACCESS_TYPE_NORMAL);
List<Permission> permissionList = feignAuthClient.getUserPermissions(userId);
List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
for (Permission permission : permissionList) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority();
authority.setAuthority(permission.getPermission());
authorityList.add(authority);
}
CustomAuthentication userAuth = new CustomAuthentication();
userAuth.setAuthorities(authorityList);
userContext.setAuthorities(authorityList);
userContext.setAuthentication(userAuth);
SecurityContextHolder.setContext(userContext);
}
filterChain.doFilter(servletRequest, servletResponse);
}
//...
}
Приведенный выше код в основном понимает, что в соответствии с идентификатором пользователя в заголовке запроса фиктивный клиент используется для получения набора разрешений пользователя в службе аутентификации. После этого создается UserContext, который настраивается и реализует Spring Security.UserDetails, SecurityContext
интерфейс.
4.2 Реализация аннотации @PreAuth через аспекты
Для проектов на основе Spring удобнее использовать АОП-аспект Spring для реализации аннотаций, здесь мы используем пользовательские аннотации.@PreAuth
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuth {
String value();
}
Цель используется для описания области использования аннотаций. Компиляция завершается сбоем, когда она превышает область действия. Его можно использовать в методах или классах. Вступает в силу во время выполнения. Если вы не знаете об аннотациях, вы можете сами погуглить.
@Component
@Aspect
public class AuthAspect {
@Pointcut("@annotation(com.blueskykong.auth.demo.annotation.PreAuth)")
private void cut() {
}
/**
* 定制一个环绕通知,当想获得注解里面的属性,可以直接注入该注解
*
* @param joinPoint
* @param preAuth
*/
@Around("cut()&&@annotation(preAuth)")
public Object record(ProceedingJoinPoint joinPoint, PreAuth preAuth) throws Throwable {
//取出注解中的表达式
String value = preAuth.value();
//Spring EL 对value进行解析
SecurityExpressionOperations operations = new CustomerSecurityExpressionRoot(SecurityContextHolder.getContext().getAuthentication());
StandardEvaluationContext operationContext = new StandardEvaluationContext(operations);
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(value);
//获取表达式判断的结果
boolean result = expression.getValue(operationContext, boolean.class);
if (result) {
//继续执行接口内的方法
return joinPoint.proceed();
}
return "Forbidden";
}
}
Поскольку Aspect действует на bean-компоненты, сначала используйте Component, чтобы добавить этот класс в контейнер.@Pointcut
Определяет аннотацию для перехвата.@Around
Настройте объемное уведомление. Если вы хотите получить свойства в аннотации, вы можете напрямую вставить аннотацию. Выражение аспекта в основном реализовано с использованием Spring EL для анализа значения иSecurityContextHolder.getContext()
Преобразуйте его в стандартный контекст операции, затем проанализируйте выражение в аннотации и, наконец, получите результат оценки выражения.
public class CustomerSecurityExpressionRoot extends SecurityExpressionRoot {
public CustomerSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
}
CustomerSecurityExpressionRoot
Унаследовано от абстрактного классаSecurityExpressionRoot
, а фактическое выражение, которое мы используем, определено вSecurityExpressionOperations
интерфейс,SecurityExpressionRoot
понял сноваSecurityExpressionOperations
интерфейс. Однако конкретная реализация суждения здесь, Spring
Служба безопасности также вызывает Spring EL.
4.3 интерфейс контроллера
Давайте посмотрим, как окончательный интерфейс использует аннотации, реализованные выше.
@RequestMapping(value = "/test", method = RequestMethod.GET)
@PreAuth("hasAuthority('CREATE_COMPANY')") // 还可以定义很多表达式,如hasRole('Admin')
public String test() {
return "ok";
}
@PreAuth
, есть много выражений, которые можно определить, вы можете видетьSecurityExpressionOperations
методы в интерфейсе. В настоящее время автор только осозналhasAuthority()
выражение, если вы хотите поддерживать все остальные выражения, просто создайте соответствующийSecurityContextHolder
Вот и все.
4.4 Почему это сделано именно так?
Прочитав приведенный выше дизайн, некоторые читатели обязательно спросят, поскольку используется множество инструментов Spring Security, зачем вводятся такие сложные классы инструментов?
На самом деле это довольно просто, прежде всего потому, чтоSecurityExpressionOperations
Выражения, определенные в интерфейсе, достаточны и разумны и могут охватывать большинство сценариев, которые мы обычно используем; во-вторых, предыдущий дизайн автора заключался в указании необходимых разрешений непосредственно в аннотации, которая не является расширяемой, а читабельность можно проверить , наконец, Spring Security 4 представляет@PreAuthorize,@PostAuthorize
В ожидании аннотации, я изначально хотел использовать его, но попробовал сам и обнаружил, что он не очень подходит для проверки разрешений на операции на уровне интерфейса, таких как микросервисная архитектура.Более десяти фильтров слишком сложны, и это также включает в себя принципала, учетные данные и т. д. Информация, в которой реализована проверка подлинности личности в системе аутентификации. Автор считает, что реализация функции здесь не очень сложная и требует очень легковесной реализации, если читателям интересно, они могут попробовать инкапсулировать эту часть реализации в jar-пакет или Spring.
Стартер ботинка.
5. Резюме
Как и выше, мы сначала поговорим о дизайнерской идее интеграции, которая в основном включает в себя три сервиса: шлюз, аутентификацию и демонстрацию серверной части. Интегрированный проект, как правило, сложнее.Служба шлюза расширила много контента, а открытый интерфейс маршрутизируется и перенаправляется.Здесь представлен стартер Spring Security, а сервер ресурсов настроен на освобождение открытого пути;для других интерфейсов , необходимо вызвать службу авторизации.Провести проверку подлинности личности, чтобы убедиться, что запросы, поступающие к серверу, являются легальными или общедоступными интерфейсами;служба аутентификации дополняет соответствующие интерфейсы роли, разрешения и пользователя на основе предыдущих для внешних вызовов; серверная часть demo — это новый сервис, реализующий проверку разрешений на операции на уровне интерфейса, в основном с использованием пользовательских аннотаций и аспектов Spring AOP.
Из-за того, что существует много деталей реализации, эта статья ограничена по объему, и в ней перечислены и объяснены только некоторые важные реализации. Если читатель интересуется практическими приложениями, некоторая информация может быть расширена в соответствии с реальным бизнесом, например, токен, авторизованный авторизацией, информация заголовка, созданная запросом на перехват шлюза, выражения, поддерживаемые аннотацией, и т. д.
Конечно, есть еще много мест, которые можно оптимизировать.Если дизайн проекта интеграции неразумен, вы можете дать больше мнений.
Рекомендуемое чтение
- Шлюз микросервиса netflix-zuul
- Проектирование и реализация аутентификации, аутентификации и управления разрешениями API в микросервисной архитектуре (1)
- Проектирование и реализация аутентификации, аутентификации и управления разрешениями API в микросервисной архитектуре (2)
- Разработка и реализация аутентификации, аутентификации и управления разрешениями API в микросервисной архитектуре (3)
- Проектирование и реализация аутентификации, аутентификации и управления разрешениями API в микросервисной архитектуре (4)
исходный код
auth:
Гитхаб:GitHub.com/Доступный ETS2012/A…
Облако кода:git ee.com/can-ets/au-th-…
gateway and backend dmeo:
GitHub: GitHub.com/Доступный ETS2012/S…
Облако кода:git ee.com/can ets/judgment day you…