#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 НЕ РАБОТАЕТ по причинам, указанным ниже
-
Если вы напечатаете атрибуты, возвращаемые,
Oauth2UserAuthirty class
вы скоро заметите, что содержимоеJSON
данных не имеетrole
ключа, вместо этого имеетauthorities
ключ, следовательно, вам нужно использовать этот ключ для перебора списка полномочий (ролей), чтобы получить фактическое имя роли. -
Следовательно, следующие строки кода не будут работать, поскольку в
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());
}