Как установить полномочия пользователя на основе пользовательских утверждений, возвращаемых сервером oauth в spring security

#spring-security #identityserver4 #roles

#spring-безопасность #identityserver4 #роли

Вопрос:

Недавно я написал проект spring boot, в котором используется spring security oauth2, сервером аутентификации по какой-то причине является IdentityServer4, я могу успешно войти в систему и получить имя пользователя в своем проекте, но я не могу найти способ установить полномочия / роль пользователя.

request.isUserInRole всегда возвращает false. @PreAuthorize(«hasRole(‘rolename’)») всегда приводит меня к 403.

Где я могу разместить некоторый код для установки полномочий?

Сервер вернул некоторые пользовательские утверждения через конечную точку userinfo, и мой проект их получил, и я даже вижу это в основном параметре моего контроллера.

Этот метод всегда возвращает 403

 @ResponseBody
@RequestMapping("admin")
@PreAuthorize("hasRole('admin')")
public String admin(HttpServletRequest request){
    return "welcome, you are admin!"   request.isUserInRole("ROLE_admin");
}
  

application.properties

 spring.security.oauth2.client.provider.test.issuer-uri = http://localhost:5000
spring.security.oauth2.client.provider.test.user-name-attribute = name

spring.security.oauth2.client.registration.test.client-id = java
spring.security.oauth2.client.registration.test.client-secret = secret
spring.security.oauth2.client.registration.test.authorization-grant-type = authorization_code
spring.security.oauth2.client.registration.test.scope = openid profile
  

Я печатаю утверждения

 @ResponseBody
@RequestMapping()
public Object index(Principal user){
    OAuth2AuthenticationToken token = (OAuth2AuthenticationToken)user;
    return token.getPrincipal().getAttributes();
}
  

и получить результат, показывающий, что существует утверждение с именем ‘role’

 {"key":"value","role":"admin","preferred_username":"bob"}
  

Кто-нибудь может мне помочь и дать мне решение, пожалуйста?

РЕДАКТИРОВАТЬ 1: Причина в том, что клиент oauth2 удалил средство извлечения, и я должен реализовать userAuthoritiesMapper.

Наконец, я получил эту работу, добавив следующий класс:

 @Configuration
public class AppConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(this.userAuthoritiesMapper());
        //.oidcUserService(this.oidcUserService());
        super.configure(http);
    }

    private GrantedAuthoritiesMapper userAuthoritiesMapper() {
        return (authorities) -> {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

            authorities.forEach(authority -> {
                if (OidcUserAuthority.class.isInstance(authority)) {
                    OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;

                    OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
                    if (userInfo.containsClaim("role")){
                        String roleName = "ROLE_"   userInfo.getClaimAsString("role");
                        mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
                    }
                } else if (OAuth2UserAuthority.class.isInstance(authority)) {
                    OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
                    Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
                    
                    if (userAttributes.containsKey("role")){
                        String roleName = "ROLE_"   (String)userAttributes.get("role");
                        mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
                    }
                }
            });

            return mappedAuthorities;
        };
    }
}
  

Структура меняется так быстро, а демонстрации в Интернете слишком старые!

Комментарии:

1. Я думаю, что я нашел причину здесь: github.com/spring-projects/spring-security/issues/5625

2. docs.spring.io/spring-security/site/docs/current/reference/…

Ответ №1:

Я потратил несколько часов и нашел решение. Проблема заключается в безопасности spring oauth, по умолчанию она получает роли пользователей из токена, используя ключ «полномочия». Итак, я внедрил пользовательский преобразователь токенов.

Первое, что вам нужно, это пользовательский конвертер пользовательских токенов, вот класс:

 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

public class CustomUserTokenConverter implements UserAuthenticationConverter {
    private Collection<? extends GrantedAuthority> defaultAuthorities;
    private UserDetailsService userDetailsService;

    private final String AUTHORITIES = "role";
    private final String USERNAME = "preferred_username";
    private final String USER_IDENTIFIER = "sub";

    public CustomUserTokenConverter() {
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    public void setDefaultAuthorities(String[] defaultAuthorities) {
        this.defaultAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.arrayToCommaDelimitedString(defaultAuthorities));
    }

    public Map<String, ?> convertUserAuthentication(Authentication authentication) {
        Map<String, Object> response = new LinkedHashMap();
        response.put(USERNAME, authentication.getName());
        if (authentication.getAuthorities() != null amp;amp; !authentication.getAuthorities().isEmpty()) {
            response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
        }

        return response;
    }

    public Authentication extractAuthentication(Map<String, ?> map) {
        if (map.containsKey(USER_IDENTIFIER)) {
            Object principal = map.get(USER_IDENTIFIER);
            Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);
            if (this.userDetailsService != null) {
                UserDetails user = this.userDetailsService.loadUserByUsername((String)map.get(USER_IDENTIFIER));
                authorities = user.getAuthorities();
                principal = user;
            }

            return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
        } else {
            return null;
        }
    }

    private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
        if (!map.containsKey(AUTHORITIES)) {
            return this.defaultAuthorities;
        } else {
            Object authorities = map.get(AUTHORITIES);
            if (authorities instanceof String) {
                return AuthorityUtils.commaSeparatedStringToAuthorityList((String)authorities);
            } else if (authorities instanceof Collection) {
                return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection)authorities));
            } else {
                throw new IllegalArgumentException("Authorities must be either a String or a Collection");
            }
        }
    }
}
  

Вам нужен пользовательский преобразователь токенов, вот:

 import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
        OAuth2Authentication authentication = super.extractAuthentication(claims);
        authentication.setDetails(claims);
        return authentication;
    }


}
  

И, наконец, ваша конфигурация ResourceServerConfiguration выглядит следующим образом:

 import hello.helper.CustomAccessTokenConverter;
import hello.helper.CustomUserTokenConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(final HttpSecurity http) throws Exception {
        // @formatter:off
        http.authorizeRequests()
                .anyRequest().access("hasAnyAuthority('Admin')");
    }
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("arawaks");
    }

    @Bean
    @Primary
    public RemoteTokenServices tokenServices() {
        final RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setClientId("resourceId");
        tokenServices.setClientSecret("resource.secret");
        tokenServices.setCheckTokenEndpointUrl("http://localhost:5001/connect/introspect");
        tokenServices.setAccessTokenConverter(accessTokenConverter());
        return tokenServices;
    }


    @Bean
    public CustomAccessTokenConverter accessTokenConverter() {
        final CustomAccessTokenConverter converter = new CustomAccessTokenConverter();
        converter.setUserTokenConverter(new CustomUserTokenConverter());
        return converter;
    }

}
  

Комментарии:

1. Ваш ответ кажется правильным! Я протестирую это позже и БЛАГОДАРЮ ВАС!

2. Я пытаюсь решить эту проблему, добавив утверждение с именем «authorities» со значением = [«ROLE_admin»], но spring security по-прежнему возвращает 403, токен: «preferred_username»:»bob»,»authorities»:»[«ROLE_admin»]»

3. Вам не нужно добавлять префикс ROLE_ к вашим ролям, мое первое решение было примерно таким, в IdentityServer4, в реализации ProfileService, я добавил утверждение «полномочия», и оно работает без изменения стандартного преобразователя токенов spring security oauth2 и с теми же именами ролей и без префикса ROLE_.

4. утверждение властей, похоже, было проигнорировано, я пробовал много форматов значения утверждения, но безуспешно. и я использую пакет spring-boot-starter-oauth2-client, в нем нет класса UserAuthenticationConverter.

5. Я решил проблему и см. Редактирование моего сообщения.

Ответ №2:

По-видимому, ответ @ (EDIT 1) wjsgzcn НЕ РАБОТАЕТ по причинам, указанным ниже

  1. Если вы напечатаете атрибуты, возвращаемые, Oauth2UserAuthirty class вы скоро заметите, что содержимое JSON данных не имеет role ключа, вместо этого имеет authorities ключ, следовательно, вам нужно использовать этот ключ для перебора списка полномочий (ролей), чтобы получить фактическое имя роли.

  2. Следовательно, следующие строки кода не будут работать, поскольку в role JSON данных, возвращаемых oauth2UserAuthority.getAttributes();

      OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
     Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
     if (userAttributes.containsKey("role")){
         String roleName = "ROLE_"   (String)userAttributes.get("role");
         mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
     }
      

Поэтому вместо этого используйте следующее, чтобы получить фактическую роль из GetAttributes

 if (userAttributes.containsKey("authorities")){
   ObjectMapper objectMapper = new ObjectMapper();
   ArrayList<Role> authorityList = 
   objectMapper.convertValue(userAttributes.get("authorities"), new 
   TypeReference<ArrayList<Role>>() {});
   log.info("authList: {}", authorityList);
   for(Role role: authorityList){
      String roleName = "ROLE_"   role.getAuthority();
      log.info("role: {}", roleName);
      mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
   }
}
  

Где Role это класс pojo, например

 @Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
    @JsonProperty
    private String authority;
}
  

Таким образом, вы сможете получить префикс ROLE_ post, который является фактической ролью, предоставленной пользователю после успешной аутентификации на сервере авторизации, и клиенту возвращаются LIST предоставленные полномочия (роли).

Теперь полный GrantedAuthoritesMapper вид выглядит следующим образом:

 private GrantedAuthoritiesMapper userAuthoritiesMapper() {
    return (authorities) -> {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

            authorities.forEach(authority -> {
                if (OidcUserAuthority.class.isInstance(authority)) {
                    OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;

                    OidcIdToken idToken = oidcUserAuthority.getIdToken();
                    OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
                    
                    // Map the claims found in idToken and/or userInfo
                    // to one or more GrantedAuthority's and add it to mappedAuthorities
                    if (userInfo.containsClaim("authorities")){
                        ObjectMapper objectMapper = new ObjectMapper();
                        ArrayList<Role> authorityList = objectMapper.convertValue(userInfo.getClaimAsMap("authorities"), new TypeReference<ArrayList<Role>>() {});
                        log.info("authList: {}", authorityList);
                        for(Role role: authorityList){
                            String roleName = "ROLE_"   role.getAuthority();
                            log.info("role: {}", roleName);
                            mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
                        } 
                    }

                } else if (OAuth2UserAuthority.class.isInstance(authority)) {
                    OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
                    Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
                    log.info("userAttributes: {}", userAttributes);
                    // Map the attributes found in userAttributes
                    // to one or more GrantedAuthority's and add it to mappedAuthorities
                    if (userAttributes.containsKey("authorities")){
                        ObjectMapper objectMapper = new ObjectMapper();
                        ArrayList<Role> authorityList = objectMapper.convertValue(userAttributes.get("authorities"), new TypeReference<ArrayList<Role>>() {});
                        log.info("authList: {}", authorityList);
                        for(Role role: authorityList){
                            String roleName = "ROLE_"   role.getAuthority();
                            log.info("role: {}", roleName);
                            mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
                        } 
                    }
                }
            });
            log.info("The user authorities: {}", mappedAuthorities);
            return mappedAuthorities;
    };
}
  

Теперь вы можете использовать userAuthorityMapper в своем oauth2Login следующим образом

 @Override
    public void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**").authorizeRequests()
            .antMatchers("/", "/login**").permitAll()
            .antMatchers("/clientPage/**").hasRole("CLIENT")
            .anyRequest().authenticated()
            .and()
            .oauth2Login()
                .userInfoEndpoint()
                .userAuthoritiesMapper(userAuthoritiesMapper());
    }