рдПрдХ рд╕реНрдерд┐рддрд┐ рдХреА рдХрд▓реНрдкрдирд╛ рдХрд░реЗрдВ: Foobar Inc. рдХреЗ рд╡рд┐рд╢реНрд▓реЗрд╖рдХ рдХрдВрдкрдиреА рдХреА рдмрд╛рдЬрд╛рд░ рдХреА рд╕реНрдерд┐рддрд┐ рдФрд░ рд╡реНрдпрд╛рд╡рд╕рд╛рдпрд┐рдХ рдкреНрд░рдХреНрд░рд┐рдпрд╛рдУрдВ рдХрд╛ рдЧрд╣рди рдЕрдзреНрдпрдпрди рдХрд┐рдпрд╛ рдФрд░ рдЗрд╕ рдирд┐рд╖реНрдХрд░реНрд╖ рдкрд░ рдкрд╣реБрдВрдЪреЗ рдХрд┐ рд▓рд╛рдЧрддреЛрдВ рдХрд╛ рдЕрдиреБрдХреВрд▓рди рдХрд░рдиреЗ рдФрд░ рдлрд╝реЙрдмрд░ рдХреЗ рд▓рд╛рдн рдХреЛ рдмрдврд╝рд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдирдХрд╝рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЯреЗрд▓реАрдЧреНрд░рд╛рдо рдмреЙрдЯ рд╕рд╛рдереА рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ рдЬреЛ рдореБрд╢реНрдХрд┐рд▓ рд╕рдордп рдореЗрдВ рдХрд░реНрдордЪрд╛рд░рд┐рдпреЛрдВ рдХреЛ рдЦреБрд╢ рдХрд░ рд╕рдХрддрд╛ рд╣реИред
рд╕реНрд╡рд╛рднрд╛рд╡рд┐рдХ рд░реВрдк рд╕реЗ, рдлрд╝реЛрдмрд╛рд░ рдХрдкрдЯреА рдкреНрд░рддрд┐рдпреЛрдЧрд┐рдпреЛрдВ рдХреЛ рдЕрдкрдиреЗ рд╕рдВрдкрд░реНрдХреЛрдВ рдореЗрдВ рдХреЗрд╡рд▓ рдЕрдкрдиреЗ рдмреЙрдЯ рдХреЛ рдЬреЛрдбрд╝рдХрд░ рдЙрдирдХреА рдЬрд╛рдирдХрд╛рд░реА рдХрд╛ рд▓рд╛рдн рдЙрдард╛рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рдирд╣реАрдВ рджреЗ рд╕рдХрддрд╛ рд╣реИред рдЗрд╕рд▓рд┐рдП, рдмреЙрдЯ рдХреЛ рдХреЗрд╡рд▓ рдлрд╝реЛрдмрд╛рд░ рдХрд░реНрдордЪрд╛рд░рд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рдмрд╛рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ рдЬреЛ рдУрдкрдирдЖрдИрдб рдХрдиреЗрдХреНрдЯ рдкрд░ рдЖрдзрд╛рд░рд┐рдд рдХреЙрд░реНрдкреЛрд░реЗрдЯ рд╕рд┐рдВрдЧрд▓ рд╕рд╛рдЗрди-рдСрди (рдПрд╕рдПрд╕рдУ) рдХреЗ рд╕рд╛рде рдкреНрд░рдорд╛рдгрд┐рдд рдХрд░рддреЗ рд╣реИрдВред
рд╕рд┐рджреНрдзрд╛рдВрдд рд░реВрдк рдореЗрдВ
OpenId рдХрдиреЗрдХреНрдЯ (OIDC) OAuth 2.0 рд╡рд┐рдирд┐рд░реНрджреЗрд╢ рдкрд░рд┐рд╡рд╛рд░ рдкрд░ рдЖрдзрд╛рд░рд┐рдд рдПрдХ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рд╣реИред рдЗрд╕рдореЗрдВ, рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдкреНрд░рдХреНрд░рд┐рдпрд╛ рд╡рд┐рднрд┐рдиреНрди рдкрд░рд┐рджреГрд╢реНрдпреЛрдВ рд╕реЗ рдЧреБрдЬрд░ рд╕рдХрддреА рд╣реИ рдЬрд┐рд╕реЗ рдкреНрд░рд╡рд╛рд╣ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ, рдФрд░ рдЗрд╕рдореЗрдВ рддреАрди рдкрдХреНрд╖ рд╢рд╛рдорд┐рд▓ рд╣реИрдВ:
- рд╕рдВрд╕рд╛рдзрди рд╕реНрд╡рд╛рдореА - рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛;
- рдЧреНрд░рд╛рд╣рдХ - рдПрдХ рдЖрд╡реЗрджрди рдЬреЛ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХрд╛ рдЕрдиреБрд░реЛрдз рдХрд░рддрд╛ рд╣реИ;
- рдкреНрд░рд╛рдзрд┐рдХрд░рдг рд╕рд░реНрд╡рд░ (рдкреНрд░рд╛рдзрд┐рдХрд░рдг рд╕рд░реНрд╡рд░) - рдПрдХ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдЬреЛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рддрд╛ рд╣реИ рдФрд░ рдЙрд╕реЗ рдкреНрд░рдорд╛рдгрд┐рдд рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реЛрддрд╛ рд╣реИред
JWT- (JSON Web Token). OIDC, , , OIDC.
, , : Telegram- SSO. .
, . OIDC :
- (authorization code flow),
- (implicit flow),
- (hybrid flow),
- , OAuth 2.0.
тАФ HTTP, .
:
- .
- .
- .
- (, ).
- callback URL .
- ID .
- .
, , , ID , , Telegram- .
state , (XSRF). state 2 URL , 5 callback URL . , 2 state id Telegram-, 7 state id Telegram- . !
, , :
- Keycloak тАФ open source (Identity and Access Management), OAuth 2.0, Open ID Connect SAML.
- Spring Boot .
- TelegramBots тАФ Java Telegram-. тАФ Spring Boot.
- ScribeJava тАФ OAuth Java. , OAuth , Keycloak. , Keycloak - , Spring Boot тАФ . Keycloak , тАФ Telegram-, state id Telegram-, .
- Java JWT Auth0 тАФ JWT Java, ID .
:
@Component
public class Bot extends TelegramLongPollingBot {
private final OidcService oidcService;
@Override
public void onUpdateReceived(Update update) {
if (!update.hasMessage()) {
log.debug("Update has no message. Skip processing.");
return;
}
var userId = update.getMessage().getFrom().getId();
var chatId = update.getMessage().getChatId();
oidcService.findUserInfo(userId).ifPresentOrElse(
userInfo -> greet(userInfo, chatId),
() -> askForLogin(userId, chatId));
}
private void greet(UserInfo userInfo, Long chatId) {
var username = userInfo.getPreferredUsername();
var message = String.format(
"Hello, <b>%s</b>!\nYou are the best! Have a nice day!",
username);
sendHtmlMessage(message, chatId);
}
private void askForLogin(Integer userId, Long chatId) {
var url = oidcService.getAuthUrl(userId);
var message = String.format("Please, <a href=\"%s\">log in</a>.", url);
sendHtmlMessage(message, chatId);
}
}
тАФ UserInfo id Telegram- URL :
@Service
public class OidcService {
private final OAuth20Service oAuthService;
private final UserTrackerStorage userTrackers;
private final TokenStorage accessTokens;
public Optional<UserInfo> findUserInfo(Integer userId) {
return accessTokens.find(userId)
.map(UserInfo::of);
}
public String getAuthUrl(Integer userId) {
var state = UUID.randomUUID().toString();
userTrackers.put(state, userId);
return oAuthService.getAuthorizationUrl(state);
}
}
, , :
public class UserInfo {
private final String subject;
private final String preferredUsername;
static UserInfo of(OpenIdOAuth2AccessToken token) {
var jwt = JWT.decode(token.getOpenIdToken());
var subject = jwt.getSubject();
var preferredUsername = jwt.getClaim("preferred_username").asString();
return new UserInfo(subject, preferredUsername);
}
}
OAuth20Service:
@Configuration
@EnableConfigurationProperties(OidcProperties.class)
class OidcAutoConfiguration {
@Bean
OAuth20Service oAuthService(OidcProperties properties) {
return new ServiceBuilder(properties.getClientId())
.apiSecret(properties.getClientSecret())
.defaultScope("openid offline_access")
.callback(properties.getCallback())
.build(KeycloakApi.instance(properties.getBaseUrl(), properties.getRealm()));
}
}
Callback endpoint :
@Component
@Path("/auth")
public class AuthEndpoint {
private final URI botUri;
private final OidcService oidcService;
@GET
@Produces("text/plain; charset=UTF-8")
public Response auth(
@QueryParam("state") String state,
@QueryParam("code") String code) {
return oidcService.completeAuth(state, code)
.map(userInfo -> Response.temporaryRedirect(botUri).build())
.orElseGet(() -> Response.serverError().entity("Cannot complete authentication").build());
}
}
OidcService, completeAuth:
@Service
public class OidcService {
private final OAuth20Service oAuthService;
private final UserTrackerStorage userTrackers;
private final TokenStorage accessTokens;
public Optional<UserInfo> completeAuth(String state, String code) {
return userTrackers.find(state)
.map(userId -> requestAndStoreToken(code, userId))
.map(UserInfo::of);
}
private OpenIdOAuth2AccessToken requestAndStoreToken(
String code,
Integer userId) {
var token = requestToken(code);
accessTokens.put(userId, token);
return token;
}
private OpenIdOAuth2AccessToken requestToken(String code) {
try {
return (OpenIdOAuth2AccessToken) oAuthService.getAccessToken(code);
} catch (IOException | InterruptedException | ExecutionException e) {
throw new RuntimeException("Cannot get access token", e);
}
}
}
!
, , , , . , - , / .
рд╕реНрдкреНрд░рд┐рдВрдЧ рд╕рд┐рдХреНрдпреЛрд░рд┐рдЯреА рдФрд░ Google OAuth рдХреНрд▓рд╛рдЗрдВрдЯ рдЬреИрд╕реЗ рдЕрдзрд┐рдХ рдмреНрд░рд╛рдВрдЪ рд╡рд╛рд▓реЗ OAuth рдХреНрд▓рд╛рдЗрдВрдЯ рдореЗрдВ рдпреЗ рд╕рдорд╕реНрдпрд╛рдПрдБ рдкрд╣рд▓реЗ рд╣реА рд╣рд▓ рд╣реЛ рдЪреБрдХреА рд╣реИрдВ ред рд▓реЗрдХрд┐рди рдкреНрд░рджрд░реНрд╢рди рдкреНрд░рдпреЛрдЬрдиреЛрдВ рдХреЗ рд▓рд┐рдП, рд╣рдо рдареАрдХ рд╣реИрдВ :)
рд╕рднреА рд╕реНрд░реЛрддреЛрдВ GitHub рдкрд░ рдкрд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ ред