Как проверить информацию о пользователях токена OAuth 2.0 в аннотации @PreAuthorize в REST-сервисе Spring Boot - PullRequest
2 голосов
/ 09 июля 2019

Мне нужно проверить аннотацию @PreAuthorize.Что-то вроде:

@PreAuthorize("hasRole('ROLE_VIEWER') or hasRole('ROLE_EDITOR')")

Это нормально, но мне также нужно проверить некоторые пользовательские данные, хранящиеся в маркере OAuth 2.0, с данными в пути запроса, поэтому мне нужно сделать что-то вроде (oauthToken.userDetails простопример:

@PreAuthorize("#pathProfileId.equals(oauthToken.userDetails.profileId)")

(profileId не userId или userName, это данные пользователя, которые мы добавляем в токен OAuth при его создании)

Какой самый простой способ сделатьСвойства токена OAuth, видимые в языке выражений безопасности предварительно авторизованных аннотаций?

1 Ответ

0 голосов
/ 24 июля 2019

У вас есть две опции:

1-

Установка UserDetailsService экземпляр в DefaultUserAuthenticationConverter и установить преобразователь на JwtAccessTokenConverter поэтому, когда spring вызывает extractAuthentication метод из DefaultUserAuthenticationConverter, он находит (userDetailsService! = null) , поэтому он получает весь UserDetails объект, вызывая реализацию loadUserByUsername при вызове этой строки:

userDetailsService.loadUserByUsername ((String) map.get (USERNAME))

реализовано в следующем методе внутри класса Spring org. springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter.java , но просто добавьте его, чтобы уточнить, как Spring получает основной объект из карты (сначала получая его по имени пользователя, и если userDetailsService не имеет значение null, поэтому он получает весь объект):

//Note: This method implemented by spring but just putting it to show where spring exctract principal object and how extracting it
public Authentication extractAuthentication(Map<String, ?> map) {
        if (map.containsKey(USERNAME)) {
            Object principal = map.get(USERNAME);
            Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
            if (userDetailsService != null) {
                UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
                authorities = user.getAuthorities();
                principal = user;
            }
            return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
        }
        return null;
    }

Итак, что вам нужно реализовать в вашем микросервисе:

@Bean//this method just used with token store bean example: new JwtTokenStore(tokenEnhancer());
public JwtAccessTokenConverter tokenEnhancer() {
    /**
    * CustomTokenConverter is a class extends JwtAccessTokenConverter 
    * which override "enhance" to add extra information to OAuth2AccessToken after
    * authenticate the user and get it by loadUserByUsername implementation 
    * like profileId in your case
    **/  
    JwtAccessTokenConverter converter = new CustomTokenConverter();

    DefaultAccessTokenConverter datc = new DefaultAccessTokenConverter();
    datc.setUserTokenConverter(userAuthenticationConverter());
    converter.setAccessTokenConverter(datc);

    //Other method code implementation....
}

@Autowired
private UserDetailsService userDetailsService;

@Bean
public UserAuthenticationConverter userAuthenticationConverter() {
    DefaultUserAuthenticationConverter duac = new DefaultUserAuthenticationConverter();
    duac.setUserDetailsService(userDetailsService);
    return duac;
 }

Примечание: этот первый способ будет попадать в базу данных при каждом запросе, поэтому он загружает пользователя по имени пользователя и получает объект UserDetails, поэтому он назначает его основному объекту при аутентификации.


2-

Если по какой-либо причине вы видите, что лучше не попадать в базу данных при каждом запросе и нет проблем с выполнением данных, таких как profileId из токена, переданного в запросе.

Предполагая, что вы знаете, что старыйПолномочия, назначенные пользователю при создании токена oauth2, всегда будут в токене до тех пор, пока он не станет недействительным, даже после того, как вы измените его в базе данных для пользователя, который передает токен в запросе, чтобы пользователь мог вызвать метод, который ему больше не разрешен после извлечения токена, и онбыло разрешено до извлечения токена.

Таким образом, это означает, что если права пользователя изменились после создания токена, новые права доступа не будут проверяться @PreAuthorize, так как он не удален или не добавлен в токен, и вам придется подождать до старого токенастановится недействительным или истек срок действия, поэтому пользователь вынужден выполнить tСлужба снова для получения нового токена oauth.

В любом случае, во втором варианте вам нужно только переопределить extractAuthentication метод внутри CustomTokenConverter класс расширяет JwtAccessTokenConverter и забудьте о настройке конвертера токенов доступа converter.setAccessTokenConverter из tokenEnhancer () метод в первом варианте, и вот весь CustomTokenConverter, который вы можете использовать для чтения данных из токена и возврата принципалаобъект не просто строка username:

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

public class CustomTokenConverter extends JwtAccessTokenConverter {

    // This is the method you need to override to read data direct from token passed in request
    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
        OAuth2Authentication authentication = super.extractAuthentication(map);

        Object userIdObj = map.get(AuthenticationUtils.USER_ID);
        UUID userId = userIdObj != null ? UUID.fromString(userIdObj.toString()) : null;
        Object profileIdObj = map.get(AuthenticationUtils.PROFILE_ID);
        UUID profileId = profileIdObj != null ? UUID.fromString(profileIdObj.toString()) : null;
        Object firstNameObj = map.get(AuthenticationUtils.FIRST_NAME);
        String firstName = firstNameObj != null ? String.valueOf(firstNameObj) : null;
        Object lastNameObj = map.get(AuthenticationUtils.LAST_NAME);
        String lastName = lastNameObj != null ? String.valueOf(lastNameObj) : null;

        JwtUser principal = new JwtUser(userId, profileId, authentication.getUserAuthentication().getName(), "N/A", authentication.getUserAuthentication().getAuthorities(), firstName, lastName);

        authentication = new OAuth2Authentication(authentication.getOAuth2Request(),
                new UsernamePasswordAuthenticationToken(principal, "N/A", authentication.getUserAuthentication().getAuthorities()));
        return authentication;
    }

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        JwtUser user = (JwtUser) authentication.getPrincipal();
        Map<String, Object> info = new LinkedHashMap<>(accessToken.getAdditionalInformation());
        if (user.getId() != null)
            info.put(AuthenticationUtils.USER_ID, user.getId());
        if (user.getProfileId() != null)
            info.put(AuthenticationUtils.PROFILE_ID, user.getProfileId());
        if (isNotNullNotEmpty(user.getFirstName()))
            info.put(AuthenticationUtils.FIRST_NAME, user.getFirstName());
        if (isNotNullNotEmpty(user.getLastName()))
            info.put(AuthenticationUtils.LAST_NAME, user.getLastName());

        DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
        customAccessToken.setAdditionalInformation(info);
        return super.enhance(customAccessToken, authentication);
    }

    private boolean isNotNullNotEmpty(String str) {
        return Optional.ofNullable(str).map(String::trim).map(string -> !str.isEmpty()).orElse(false);
    }

}

Наконец: угадайте, откуда я знаю, что вы спрашиваете о JWT, используемом с OAuth2?

Потому что я частьвашей компании: P, и вы знаете, что: P

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...