Здравствуйте,
как повлиять на время жизни access_token при использовании OAuth 2.1 Jmix v.2.2.1?
Заметил, что свойство spring.security.oauth2.client-token-expiration-time-sec игнорируется.
С уважением,
Виталий
Здравствуйте,
как повлиять на время жизни access_token при использовании OAuth 2.1 Jmix v.2.2.1?
Заметил, что свойство spring.security.oauth2.client-token-expiration-time-sec игнорируется.
С уважением,
Виталий
Добрый день.
У вас используется аддон REST API и Authorization Server, я правильно понимаю? Если так, то время жизни токена задаётся примерно вот такими свойствами:
# access token time-to-live
spring.security.oauth2.authorizationserver.client.myapp.token.access-token-time-to-live=1h
# refresh token token time-to-live
spring.security.oauth2.authorizationserver.client.myapp.token.refresh-token-time-to-live=24h
Пример есть в документации.
Спасибо за ответ!
Этот пример для authorization-grant-types=authorization_code я видел.
Простите, не написал, что мы используем grant-types=client_credentials.
При этом какой параметр для указания времени жизни токена доступа использовать?
Там будет точно такой же параметр, как и для authorization code гранта. Попробуйте.
Максим, добрый день!
Спасибо!
Максим, добрый день!
Чтобы refresh_token можно было использовать после рестарта сервера, как инициализировать JdbcOAuth2AuthorizationService?
Добрый день.
Чтобы использовать JdbcOAuth2AuthorizationService
нужно определить данный бин в своём приложении. Тогда он будет использоваться вместо дефолтного, который создаётся автоконфигурацией аддона.
@Bean
JdbcOAuth2AuthorizationService oAuth2AuthorizationService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
}
Джавадок к этому классу говорит, что вам нужно создать таблицу в БД и даёт ссылку на файл, где можно найти скрипт создания. Можно, например, поместить этот скрипт в новый liquibase changeset. Для postgres:
<changeSet id="5" author="jdbc-token-store">
<sql>
CREATE TABLE oauth2_authorization (
id varchar(100) NOT NULL,
registered_client_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
authorization_grant_type varchar(100) NOT NULL,
authorized_scopes varchar(1000) DEFAULT NULL,
attributes text DEFAULT NULL,
state varchar(500) DEFAULT NULL,
authorization_code_value text DEFAULT NULL,
authorization_code_issued_at timestamp DEFAULT NULL,
authorization_code_expires_at timestamp DEFAULT NULL,
authorization_code_metadata text DEFAULT NULL,
access_token_value text DEFAULT NULL,
access_token_issued_at timestamp DEFAULT NULL,
access_token_expires_at timestamp DEFAULT NULL,
access_token_metadata text DEFAULT NULL,
access_token_type varchar(100) DEFAULT NULL,
access_token_scopes varchar(1000) DEFAULT NULL,
oidc_id_token_value text DEFAULT NULL,
oidc_id_token_issued_at timestamp DEFAULT NULL,
oidc_id_token_expires_at timestamp DEFAULT NULL,
oidc_id_token_metadata text DEFAULT NULL,
refresh_token_value text DEFAULT NULL,
refresh_token_issued_at timestamp DEFAULT NULL,
refresh_token_expires_at timestamp DEFAULT NULL,
refresh_token_metadata text DEFAULT NULL,
user_code_value text DEFAULT NULL,
user_code_issued_at timestamp DEFAULT NULL,
user_code_expires_at timestamp DEFAULT NULL,
user_code_metadata text DEFAULT NULL,
device_code_value text DEFAULT NULL,
device_code_issued_at timestamp DEFAULT NULL,
device_code_expires_at timestamp DEFAULT NULL,
device_code_metadata text DEFAULT NULL,
PRIMARY KEY (id)
);
</sql>
</changeSet>
В application.properties
надо указать студии, чтобы она не удаляла эту таблицу
main.datasource.studio.liquibase.exclude-prefixes = oauth2_
И ещё один момент. Из-за этого бага надо создать в проекте свой немного переопределённый AuthorizationServiceOpaqueTokenIntrospector
.
import io.jmix.authserver.introspection.AuthorizationServiceOpaqueTokenIntrospector;
import io.jmix.authserver.introspection.TokenIntrospectorRolesHelper;
import io.jmix.authserver.introspection.UserDetailsOAuth2AuthenticatedPrincipal;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
import org.springframework.stereotype.Component;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
@Component
public class MyAuthorizationServiceOpaqueTokenIntrospector extends AuthorizationServiceOpaqueTokenIntrospector {
private final OAuth2AuthorizationService authorizationService;
private final TokenIntrospectorRolesHelper introspectorRolesHelper;
public MyAuthorizationServiceOpaqueTokenIntrospector(OAuth2AuthorizationService authorizationService,
TokenIntrospectorRolesHelper introspectorRolesHelper) {
super(authorizationService, introspectorRolesHelper);
this.authorizationService = authorizationService;
this.introspectorRolesHelper = introspectorRolesHelper;
}
@Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
if (authorization == null) {
throw new BadOpaqueTokenException("Authorization for provided access token not found");
}
OAuth2Authorization.Token<OAuth2Token> accessToken = authorization.getToken(token);
if (accessToken == null || !accessToken.isActive()) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
}
String principalName = authorization.getPrincipalName();
Collection<GrantedAuthority> authorities = new ArrayList<>();
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorization.getAuthorizationGrantType())) {
Object principal = authorization.getAttribute(Principal.class.getCanonicalName());
if (principal instanceof Authentication) {
principalName = ((Authentication) principal).getName();
authorities.addAll(((Authentication) principal).getAuthorities());
}
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(authorization.getAuthorizationGrantType())) {
principalName = authorization.getPrincipalName();
try {
authorities.addAll(introspectorRolesHelper.getClientGrantedAuthorities(principalName));
} catch (UsernameNotFoundException e) {
throw new BadOpaqueTokenException("User " + principalName + " not found");
}
}
return new UserDetailsOAuth2AuthenticatedPrincipal(principalName, authorization.getAttributes(), authorities);
}
}
Вот проект с данными изменениями: jdbc-token-store.zip (97.8 КБ)
Максим, спасибо огромное за помощь!
Я понимаю, что для использования password grantType
надо доработать MyAuthorizationServiceOpaqueTokenIntrospector.
Но, например, при запросе /rest/entities/User с полученным acces_token уже в методе OAuth2Authorization findBy класса JdbcOAuth2AuthorizationService получаю исключение:
java.lang.IllegalArgumentException: The class with com.company.sample.entity.User and name of com.company.sample.entity.User is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See Add Allow List to Jackson Support · Issue #4370 · spring-projects/spring-security · GitHub for details
Помог маппинг класса User c org.springframework.security.core.userdetails.User.class.
@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY
)
@JsonIgnoreProperties(
ignoreUnknown = true
)
@JsonSubTypes({
@JsonSubTypes.Type(value = org.springframework.security.core.userdetails.User.class, name = "user")
})
@JmixEntity
@Entity
@Table(name = "USER_", indexes = {
@Index(name = "IDX_USER__ON_USERNAME", columnList = "USERNAME", unique = true)
})
public class User implements JmixUserDetails, HasTimeZone {
. . . .
@gorbunkov Здравствуйте!
А как правильно деактивировать access_ / refresh_ токены, полученные с помощью Resource owner password credentials grant при удалении / деактивации User в коде слушателя onUserChangedBeforCommit?
Добрый день!
Нужно, чтобы ваша реализация OAuth2AuthorizationService
“перестала” знать обо всех авторизациях, связанных с текущим пользователем.
Если у себя в проекте вы используете JdbcOAuth2AuthorizationService
, то, наверное, можно попробовать в нужный момент удалять все записи из таблицы oauth2_authorization
где principal_name
равен юзернейму деактивируемого пользователя.
Спасибо за помощь!