Аутентификация JWT без сохранения состояния Spring Security - как получить другие поля jwt?

У нас есть микросервисная архитектура, использующая JWT для аутентификации между сервисами. Я хочу легко получить больше полей из JWT. В настоящее время Spring Security напрямую раскрывает только органы власти.

Пример JWT, который создается нашим пограничным сервисом / шлюзом API для передачи последующим сервисам (некоторые поля настраиваются для нашего приложения):

{
  "projectId": "project1",
  "group": "client",
  "iss": "login.company.com",
  "aud": "company.com",
  "sub": "testguy",
  "exp": 1461074284992,
  "projectRoleId": "ADMINPAG",
  "contentAccessGroupId": "CAG1",
  "authorities": [
    "client",
    "PROJECT_ADD_USER",
    "PROJECT_ADD_CAG",
    "PROJECT_DOCUMENT_VIEW",
    "PROJECT_EDIT_CAG",
    "PROJECT_LIST_USERS",
    "PROJECT_SEARCH"
  ],
  "user_name": "[email protected]"
}

На удивление легко заставить это работать с Spring Boot:

compile("org.springframework.boot:spring-boot-starter-security:1.3.1")
compile('org.springframework.security.oauth:spring-security-oauth2:2.0.8.RELEASE')
compile('org.springframework.security:spring-security-jwt:1.0.3.RELEASE')

1) Аннотировать класс приложения

@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyApp {

2) Добавьте свойство в application.yml для настройки игнорируемых путей и ключа JWT:

security:
  basic:
    enabled: false
  oauth2:
    resource.jwt.keyValue: itsasecret
  ignored:
    - /
    - /swagger-ui.html
    - /webjars/**
    - /swagger-resources
    - /swagger/**

Это приводит к тому, что пользователь автоматически входит в систему при обнаружении JWT. Я могу заявить об их разрешениях с @PreAuthorize("hasAuthority('PROJECT_SEARCH')") просто отлично.

Осталось только одно: я хочу иметь возможность получать другие поля из JWT (например, «sub» - идентификатор пользователя). Эта информация, похоже, хранится в поле OAuth2AccessToken # getAdditionalInformation (). Пользовательская реализация UserDetails здесь будет работать нормально, но я бы хотел иметь возможность создавать UserDetails из JWT напрямую, а не через UserDetailsService (который создает его на основе имени пользователя).

Моя основная цель - иметь возможность ссылаться на некоторые из этих полей JWT из аннотации PreAuthorize, чтобы, например, утверждать, что текущий идентификатор пользователя совпадает с идентификатором, переданным в аргументе userId. Если это вообще возможно, мне бы хотелось, чтобы изменения конфигурации, необходимые для этого, можно было бы изолировать от класса автоконфигурации в общем модуле, чтобы нам не приходилось касаться всех наших сервисов. Сама структура JWT на 100% гибкая.


person Dan    schedule 19.04.2016    source источник


Ответы (1)


Нашел решение в Spring Cloud Docs.

Я хотел здесь JwtAccessTokenConverterConfigurer.

Приведенный ниже класс определяется автоматически и создаст для вас пользовательский объект OAuth2Authentication после чтения JWT:

@Component
public class JwtConverter extends DefaultAccessTokenConverter implements
        JwtAccessTokenConverterConfigurer {

    @Override
    public void configure(JwtAccessTokenConverter converter) {
        converter.setAccessTokenConverter(this);
    }

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
        OAuth2Authentication auth = super.extractAuthentication(map);
        MyAppCustomUserDetails details = new MyAppCustomUserDetails();
        //populate details from the provided map, which contains the whole JWT.
        auth.setDetails(details);
        return auth;
    }
}

Единственное, что здесь следует отметить, это то, что для доступа к моему настраиваемому объекту сведений о пользователе требуется дополнительный переход. Фактически он создается как поле в объекте сведений о пользователе по умолчанию, называемом decodedDetails. Я только что сделал собственный преобразователь аргументов метода, чтобы помочь в получении этого:

@Configuration
@ConditionalOnBean(JwtConverter.class)
public class WebConfigurer extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(currentUserHandlerMethodArgumentResolver());
        super.addArgumentResolvers(argumentResolvers);
    }

    @Bean
    public HandlerMethodArgumentResolver currentUserHandlerMethodArgumentResolver() {
        return new HandlerMethodArgumentResolver() {
            @Override
            public boolean supportsParameter(MethodParameter parameter) {
                return parameter.getParameterType().equals(MyAppCustomUserDetails.class);
            }

            @Override
            public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
                return ((OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails()).getDecodedDetails();
            }
        };
    }

}

Это просто позволяет вам передавать ваши пользовательские данные в качестве аргумента вашим контроллерам и автоматически заполнять их из JWT:

@RequestMapping("/currentUser")
public UserInfoResponse getCurrentUser(MyAppCustomUserDetails user){
//do stuff
}
person Dan    schedule 22.04.2016