Лучшая практика аутентификации на основе токенов REST с JAX-RS и Джерси

Я ищу способ включить аутентификацию на токенах в Джерси. Я стараюсь не использовать какие-либо конкретные frameworks. Это возможно?

Мой план: пользователь подписывается на мой веб-сервис, мой веб-сервис генерирует токен, отправляет его клиенту, и клиент сохранит его. Затем клиент для каждого запроса отправляет токен вместо имени пользователя и пароля.

Я думал об использовании настраиваемого фильтра для каждого запроса и @PreAuthorize("hasRole('ROLE')") но я просто подумал, что это вызывает много запросов к базе данных, чтобы проверить, действительно ли токен.

Или не создавать фильтр и в каждом запросе поставить маркер param? Чтобы каждый API сначала проверял токен и после чего выполнял что-то для извлечения ресурса.

Как работает аутентификация на токенах

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

В нескольких словах схема аутентификации, основанная на токенах, выполняет следующие шаги:

  1. Клиент отправляет свои учетные данные (имя пользователя и пароль) на сервер.
  2. Сервер аутентифицирует учетные данные и, если они действительны, генерирует токен для пользователя.
  3. Сервер хранит ранее сгенерированный токен в некотором хранилище вместе с идентификатором пользователя и датой истечения срока действия.
  4. Сервер отправляет сгенерированный токен клиенту.
  5. Клиент отправляет маркер на сервер в каждом запросе.
  6. Сервер в каждом запросе извлекает токен из входящего запроса. С помощью маркера сервер просматривает детали пользователя для выполнения проверки подлинности.
    • Если токен действителен, сервер принимает запрос.
    • Если токен недействителен, сервер отказывается от запроса.
  7. После проверки подлинности сервер выполняет авторизацию.
  8. Сервер может предоставить конечную точку для обновления токенов.

Примечание . Шаг 3 не требуется, если сервер выпустил подписанный токен (например, JWT, который позволяет выполнять аутентификацию без сохранения ).

Что вы можете сделать с JAX-RS 2.0 (Джерси, RESTEasy и Apache CXF)

Это решение использует только API JAX-RS 2.0, избегая любого конкретного решения для конкретного поставщика . Таким образом, он должен работать с реализациями JAX-RS 2.0, такими как Jersey , RESTEasy и Apache CXF .

Стоит отметить, что если вы используете аутентификацию на основе токенов, вы не полагаетесь на стандартные механизмы безопасности веб-приложений Java EE, предлагаемые контейнером сервлетов, и настраиваются через web.xml приложения. Это обычная проверка подлинности.

Аутентификация пользователя с их именем пользователя и паролем и выдачей токена

Создайте метод ресурса JAX-RS, который получает и проверяет учетные данные (имя пользователя и пароль) и выдает токен пользователю:

 @Path("/authentication") public class AuthenticationEndpoint { @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response authenticateUser(@FormParam("username") String username, @FormParam("password") String password) { try { // Authenticate the user using the credentials provided authenticate(username, password); // Issue a token for the user String token = issueToken(username); // Return the token on the response return Response.ok(token).build(); } catch (Exception e) { return Response.status(Response.Status.FORBIDDEN).build(); } } private void authenticate(String username, String password) throws Exception { // Authenticate against a database, LDAP, file or whatever // Throw an Exception if the credentials are invalid } private String issueToken(String username) { // Issue a token (can be a random String persisted to a database or a JWT token) // The issued token must be associated to a user // Return the issued token } } 

Если при проверке учетных данных выбрасываются какие-либо исключения, возвращается ответ со статусом 403 (Запрещено).

Если учетные данные успешно подтверждены, ответ со статусом 200 (OK) будет возвращен, и выданный токен будет отправлен клиенту в полезной нагрузке ответа. Клиент должен отправить маркер на сервер в каждом запросе.

При использовании application/x-www-form-urlencoded клиент должен отправить учетные данные в следующем формате в полезной нагрузке запроса:

 username=admin&password=123456 

Вместо параметров формы можно вставить имя пользователя и пароль в class:

 public class Credentials implements Serializable { private String username; private String password; // Getters and setters omitted } 

И затем потребляйте его как JSON:

 @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response authenticateUser(Credentials credentials) { String username = credentials.getUsername(); String password = credentials.getPassword(); // Authenticate the user, issue a token and return a response } 

Используя этот подход, клиент должен отправить учетные данные в следующем формате в полезной нагрузке запроса:

 { "username": "admin", "password": "123456" } 

Извлечение маркера из запроса и его проверка

Клиент должен отправить токен в стандартный заголовок HTTP- Authorization запроса. Например:

 Authorization: Bearer  

Имя стандартного HTTP-заголовка является неудачным, поскольку оно содержит информацию аутентификации , а не авторизацию . Тем не менее, это стандартный HTTP-заголовок для отправки учетных данных на сервер.

JAX-RS предоставляет @NameBinding , мета-аннотацию, используемую для создания других аннотаций для привязки фильтров и перехватчиков к classам ресурсов и методам. Определите аннотацию @Secured следующим образом:

 @NameBinding @Retention(RUNTIME) @Target({TYPE, METHOD}) public @interface Secured { } 

Вышеуказанная аннотация с привязкой имени будет использоваться для украшения classа фильтра, который реализует ContainerRequestFilter , позволяя вам перехватить запрос до того, как он будет обработан методом ресурсов. ContainerRequestContext можно использовать для доступа к заголовкам HTTP-запросов, а затем извлечь токен:

 @Secured @Provider @Priority(Priorities.AUTHENTICATION) public class AuthenticationFilter implements ContainerRequestFilter { private static final String REALM = "example"; private static final String AUTHENTICATION_SCHEME = "Bearer"; @Override public void filter(ContainerRequestContext requestContext) throws IOException { // Get the Authorization header from the request String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); // Validate the Authorization header if (!isTokenBasedAuthentication(authorizationHeader)) { abortWithUnauthorized(requestContext); return; } // Extract the token from the Authorization header String token = authorizationHeader .substring(AUTHENTICATION_SCHEME.length()).trim(); try { // Validate the token validateToken(token); } catch (Exception e) { abortWithUnauthorized(requestContext); } } private boolean isTokenBasedAuthentication(String authorizationHeader) { // Check if the Authorization header is valid // It must not be null and must be prefixed with "Bearer" plus a whitespace // The authentication scheme comparison must be case-insensitive return authorizationHeader != null && authorizationHeader.toLowerCase() .startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " "); } private void abortWithUnauthorized(ContainerRequestContext requestContext) { // Abort the filter chain with a 401 status code response // The WWW-Authenticate header is sent along with the response requestContext.abortWith( Response.status(Response.Status.UNAUTHORIZED) .header(HttpHeaders.WWW_AUTHENTICATE, AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\"") .build()); } private void validateToken(String token) throws Exception { // Check if the token was issued by the server and if it's not expired // Throw an Exception if the token is invalid } } 

Если во время проверки маркера возникают какие-либо проблемы, возвращается ответ со статусом 401 (Несанкционированный). В противном случае запрос перейдет к методу ресурса.

Защита конечных точек REST

Чтобы связать фильтр проверки подлинности с методами ресурсов или classами ресурсов, аннотируйте их с @Secured annotations @Secured созданной выше. Для методов и / или classов, которые аннотируются, фильтр будет выполнен. Это означает, что такие конечные точки будут достигнуты только в том случае, если запрос выполняется с действительным токеном.

Если некоторым методам или classам не требуется аутентификация, просто не комментируйте их:

 @Path("/example") public class ExampleResource { @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Response myUnsecuredMethod(@PathParam("id") Long id) { // This method is not annotated with @Secured // The authentication filter won't be executed before invoking this method ... } @DELETE @Secured @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Response mySecuredMethod(@PathParam("id") Long id) { // This method is annotated with @Secured // The authentication filter will be executed before invoking this method // The HTTP request must be performed with a valid token ... } } 

В приведенном выше примере фильтр будет выполняться только для mySecuredMethod(Long) поскольку он аннотируется с помощью @Secured .

Идентификация текущего пользователя

Очень вероятно, что вам нужно будет узнать пользователя, который выполняет запрос, снова для вашего REST API. Для его достижения можно использовать следующие подходы:

Переопределение контекста безопасности текущего запроса

В вашем методе ContainerRequestFilter.filter(ContainerRequestContext) для текущего запроса может быть установлен новый экземпляр SecurityContext . Затем переопределите SecurityContext.getUserPrincipal() , возвращая экземпляр Principal :

 final SecurityContext currentSecurityContext = requestContext.getSecurityContext(); requestContext.setSecurityContext(new SecurityContext() { @Override public Principal getUserPrincipal() { return () -> username; } @Override public boolean isUserInRole(String role) { return true; } @Override public boolean isSecure() { return currentSecurityContext.isSecure(); } @Override public String getAuthenticationScheme() { return AUTHENTICATION_SCHEME; } }); 

Используйте токен для поиска идентификатора пользователя (имени пользователя), который будет именем Principal .

Внедрение SecurityContext в любой class ресурсов JAX-RS:

 @Context SecurityContext securityContext; 

То же самое можно сделать в методе ресурса JAX-RS:

 @GET @Secured @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Response myMethod(@PathParam("id") Long id, @Context SecurityContext securityContext) { ... } 

И затем получите Principal :

 Principal principal = securityContext.getUserPrincipal(); String username = principal.getName(); 

Использование CDI (интродукция контекста и зависимостей)

Если по какой-то причине вы не хотите переопределять SecurityContext , вы можете использовать CDI (Context и Dependency Injection), который предоставляет полезные функции, такие как события и производители.

Создайте квалификатор CDI:

 @Qualifier @Retention(RUNTIME) @Target({ METHOD, FIELD, PARAMETER }) public @interface AuthenticatedUser { } 

В созданном выше AuthenticationFilter @AuthenticatedUser Event аннотированное с помощью @AuthenticatedUser :

 @Inject @AuthenticatedUser Event userAuthenticatedEvent; 

Если аутентификация удалась, запустите событие, передающее имя пользователя в качестве параметра (помните, токен выдается для пользователя, и токен будет использоваться для поиска идентификатора пользователя):

 userAuthenticatedEvent.fire(username); 

Очень вероятно, что есть class, который представляет пользователя в вашем приложении. Назовем этот class User .

Создайте компонент CDI для обработки события аутентификации, найдите экземпляр User с соответствующим именем пользователя и назначьте его в поле производителя authenticatedUser :

 @RequestScoped public class AuthenticatedUserProducer { @Produces @RequestScoped @AuthenticatedUser private User authenticatedUser; public void handleAuthenticationEvent(@Observes @AuthenticatedUser String username) { this.authenticatedUser = findUser(username); } private User findUser(String username) { // Hit the the database or a service to find a user by its username and return it // Return the User instance } } 

Поле authenticatedUser создает экземпляр User который может быть введен в контейнерные управляемые компоненты, такие как службы JAX-RS, компоненты CDI, servlets и EJB. Используйте следующий fragment кода, чтобы ввести экземпляр User (на самом деле это прокси-сервер CDI):

 @Inject @AuthenticatedUser User authenticatedUser; 

Обратите внимание, что аннотация CDI @Produces отличается от annotations JAX-RS @Produces :

  • CDI: javax.enterprise.inject.Produces
  • JAX-RS: javax.ws.rs.Produces

Убедитесь, что вы используете аннотацию CDI @Produces в компоненте AuthenticatedUserProducer .

Ключевым моментом здесь является bean, аннотированный с помощью @RequestScoped , позволяющий обмениваться данными между фильтрами и вашими бобами. Если вы не хотите использовать события, вы можете изменить фильтр для хранения аутентифицированного пользователя в компоненте с включенным запросом, а затем прочитать его из classов ресурсов JAX-RS.

По сравнению с подходом, который переопределяет SecurityContext , подход CDI позволяет получить аутентифицированного пользователя из компонентов, отличных от ресурсов и поставщиков JAX-RS.

Поддержка ролевой авторизации

Дополнительную информацию о том, как поддерживать авторизацию на основе ролей, см. В моем другом ответе .

Выдача токенов

Токен может быть:

  • Opaque: не показывает никаких деталей, кроме самого значения (например, случайной строки)
  • Автономный: содержит информацию о самом марке (например, JWT).

См. Подробности ниже:

Случайная строка как токен

Маркер может быть выпущен путем создания случайной строки и сохранения ее в базе данных вместе с идентификатором пользователя и датой истечения срока действия. Хороший пример того, как создать случайную строку в Java, можно увидеть здесь . Вы также можете использовать:

 Random random = new SecureRandom(); String token = new BigInteger(130, random).toString(32); 

JWT (JSON Web Token)

JWT (JSON Web Token) является стандартным методом для надежного представления претензий между двумя сторонами и определяется RFC 7519 .

Это самодостаточный токен, который позволяет хранить данные в претензиях . Эти претензии сохраняются в полезной нагрузке маркера, которая является JSON, закодированной как Base64 . Вот некоторые заявки, зарегистрированные в RFC 7519 и что они означают (прочитайте полный RFC для получения дополнительной информации):

  • iss : Принципал, выдавший токен.
  • sub : Principal, который является предметом JWT.
  • exp : дата окончания токена.
  • nbf : время, когда токен начнет принимать для обработки.
  • iat : Время, на которое был выдан токен.
  • jti : уникальный идентификатор для токена.

Имейте в виду, что вы не должны хранить конфиденциальные данные, такие как пароли, в токене.

Полезная нагрузка может быть прочитана клиентом, а целостность токена может быть легко проверена путем проверки его подписи на сервере. Подпись – это то, что мешает подделке токена.

Вам не нужно будет сохранять токены JWT, если вам не нужно их отслеживать. Хотя, сохраняя токены, у вас будет возможность аннулировать и отменить доступ к ним. Чтобы сохранить следы токенов JWT, вместо того, чтобы сохранять весь токен на сервере, вы можете сохранить идентификатор маркера (требование jti ) вместе с некоторыми другими данными, такими как пользователь, которому вы выдали токен, дату истечения срока и т. Д.

При сохранении токенов всегда рекомендуется удалять старые, чтобы ваша firebase database не увеличивалась бесконечно.

Использование JWT

Существует несколько библиотек Java для выдачи и проверки токенов JWT, таких как:

  • jjwt
  • Java-JWT
  • jose4j

Чтобы найти другие полезные ресурсы для работы с JWT, посмотрите на http://jwt.io .

Обработка обновления токена с помощью JWT

Принять только действительные (и не истекшие) токены для обновления. Ответственность клиента заключается в том, чтобы обновить токены до истечения срока действия, указанного в заявлении об exp .

Вы должны помешать токенам обновляться на неопределенный срок. Ниже вы найдете несколько подходов, которые вы могли бы рассмотреть.

Вы можете сохранить трек обновления токена, добавив две претензии к вашему токену (имена претензий зависят от вас):

  • refreshLimit : указывает, сколько раз токен может быть обновлен.
  • refreshCount : указывает, сколько раз токен был обновлен.

Так что только обновите токен, если выполняются следующие условия:

  • Токен не истек ( exp >= now ).
  • Количество refreshCount < refreshLimit обновления токена меньше количества раз, когда токен может быть обновлен ( refreshCount < refreshLimit ).

И при обновлении токена:

  • Обновите дату истечения срока действия ( exp = now + some-amount-of-time ).
  • refreshCount++ обновления refreshCount++ ( refreshCount++ ).

В качестве альтернативы для отслеживания количества напитков вам может потребоваться заявка, указывающая абсолютную дату истечения срока действия (которая очень похожа на refreshLimit описанную выше). До абсолютной даты истечения срока допустимо любое количество прохладительных напитков.

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

Наилучший подход зависит от ваших требований.

Обработка аннулирования токена с помощью JWT

Если вы хотите отменить токены, вы должны следить за ними. Вам не нужно хранить весь токен на стороне сервера, хранить только идентификатор маркера (который должен быть уникальным) и некоторые метаданные, если вам нужно. Для идентификатора маркера вы можете использовать UUID .

Требование jti должно использоваться для хранения идентификатора маркера на токене. При проверке маркера убедитесь, что он не был отозван, проверяя значение требования jti против идентификаторов маркера, которые у вас есть на стороне сервера.

В целях безопасности отмените все токены для пользователя, когда они меняют свой пароль.

Дополнительная информация

  • Неважно, какой тип аутентификации вы решите использовать. Всегда делайте это на верхней части HTTPS-соединения, чтобы предотвратить атаку « человек-в-середине» .
  • Взгляните на этот вопрос из Information Security за дополнительной информацией о токенах.
  • В этой статье вы найдете полезную информацию об аутентификации на токенах.

Этот ответ касается авторизации, и это дополнение моего предыдущего ответа об аутентификации

Зачем нужен другой ответ? Я попытался расширить свой предыдущий ответ, добавив подробности о том, как поддерживать annotations JSR-250. Однако исходный ответ стал слишком длинным и превысил максимальную длину в 30 000 символов . Поэтому я переместил все данные авторизации на этот ответ, оставив другой ответ сосредоточенным на выполнении аутентификации и выдаче токенов.


Поддержка ролевой авторизации с @Secured annotations @Secured

Помимо streamа проверки подлинности, указанного в другом ответе , авторизация на основе ролей может поддерживаться в конечных точках REST.

Создайте перечисление и определите роли в соответствии с вашими потребностями:

 public enum Role { ROLE_1, ROLE_2, ROLE_3 } 

Измените аннотацию привязки @Secured созданную ранее, для поддержки ролей:

 @NameBinding @Retention(RUNTIME) @Target({TYPE, METHOD}) public @interface Secured { Role[] value() default {}; } 

Затем аннотируйте classы ресурсов и методы с помощью @Secured для выполнения авторизации. Аннотации метода переопределяют annotations classов:

 @Path("/example") @Secured({Role.ROLE_1}) public class ExampleResource { @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Response myMethod(@PathParam("id") Long id) { // This method is not annotated with @Secured // But it's declared within a class annotated with @Secured({Role.ROLE_1}) // So it only can be executed by the users who have the ROLE_1 role ... } @DELETE @Path("{id}") @Produces(MediaType.APPLICATION_JSON) @Secured({Role.ROLE_1, Role.ROLE_2}) public Response myOtherMethod(@PathParam("id") Long id) { // This method is annotated with @Secured({Role.ROLE_1, Role.ROLE_2}) // The method annotation overrides the class annotation // So it only can be executed by the users who have the ROLE_1 or ROLE_2 roles ... } } 

Создайте фильтр с приоритетом AUTHORIZATION , который выполняется после фильтра приоритета AUTHENTICATION определенного ранее.

ResourceInfo может использоваться для получения ресурса Method и Class ресурсов, который будет обрабатывать запрос, а затем извлечь из них annotations @Secured :

 @Secured @Provider @Priority(Priorities.AUTHORIZATION) public class AuthorizationFilter implements ContainerRequestFilter { @Context private ResourceInfo resourceInfo; @Override public void filter(ContainerRequestContext requestContext) throws IOException { // Get the resource class which matches with the requested URL // Extract the roles declared by it Class resourceClass = resourceInfo.getResourceClass(); List classRoles = extractRoles(resourceClass); // Get the resource method which matches with the requested URL // Extract the roles declared by it Method resourceMethod = resourceInfo.getResourceMethod(); List methodRoles = extractRoles(resourceMethod); try { // Check if the user is allowed to execute the method // The method annotations override the class annotations if (methodRoles.isEmpty()) { checkPermissions(classRoles); } else { checkPermissions(methodRoles); } } catch (Exception e) { requestContext.abortWith( Response.status(Response.Status.FORBIDDEN).build()); } } // Extract the roles from the annotated element private List extractRoles(AnnotatedElement annotatedElement) { if (annotatedElement == null) { return new ArrayList(); } else { Secured secured = annotatedElement.getAnnotation(Secured.class); if (secured == null) { return new ArrayList(); } else { Role[] allowedRoles = secured.value(); return Arrays.asList(allowedRoles); } } } private void checkPermissions(List allowedRoles) throws Exception { // Check if the user contains one of the allowed roles // Throw an Exception if the user has not permission to execute the method } } 

Если у пользователя нет разрешения на выполнение операции, запрос прерывается с 403 (Запрещено).

Чтобы узнать пользователя, выполняющего запрос, см. Мой предыдущий ответ . Вы можете получить его из SecurityContext (который должен быть уже установлен в ContainerRequestContext ) или использовать его с помощью CDI, в зависимости от подхода, для которого вы идете.

Если аннотация @Secured не имеет объявленных ролей, вы можете предположить, что все @Secured аутентификацию пользователи могут получить доступ к этой конечной точке, не обращая внимания на роли, которые есть у пользователей.

Поддержка ролевой авторизации с аннотациями JSR-250

В качестве альтернативы для определения ролей в annotations @Secured как показано выше, вы можете рассмотреть annotations JSR-250, такие как @RolesAllowed , @PermitAll и @DenyAll .

JAX-RS не поддерживает такие annotations из коробки, но это может быть достигнуто с помощью фильтра. Вот несколько соображений, которые следует учитывать, если вы хотите поддержать их всех:

  • @DenyAll по методу имеет приоритет над @RolesAllowed и @PermitAll в classе.
  • @RolesAllowed на методе имеет приоритет над @PermitAll в classе.
  • @PermitAll по методу имеет приоритет над @RolesAllowed в classе.
  • @DenyAll не может быть привязан к classам.
  • @RolesAllowed в classе имеет приоритет над @PermitAll в classе.

Таким образом, фильтр авторизации, который проверяет annotations JSR-250, может быть таким:

 @Provider @Priority(Priorities.AUTHORIZATION) public class AuthorizationFilter implements ContainerRequestFilter { @Context private ResourceInfo resourceInfo; @Override public void filter(ContainerRequestContext requestContext) throws IOException { Method method = resourceInfo.getResourceMethod(); // @DenyAll on the method takes precedence over @RolesAllowed and @PermitAll if (method.isAnnotationPresent(DenyAll.class)) { refuseRequest(); } // @RolesAllowed on the method takes precedence over @PermitAll RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class); if (rolesAllowed != null) { performAuthorization(rolesAllowed.value(), requestContext); return; } // @PermitAll on the method takes precedence over @RolesAllowed on the class if (method.isAnnotationPresent(PermitAll.class)) { // Do nothing return; } // @DenyAll can't be attached to classes // @RolesAllowed on the class takes precedence over @PermitAll on the class rolesAllowed = resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class); if (rolesAllowed != null) { performAuthorization(rolesAllowed.value(), requestContext); } // @PermitAll on the class if (resourceInfo.getResourceClass().isAnnotationPresent(PermitAll.class)) { // Do nothing return; } // Authentication is required for non-annotated methods if (!isAuthenticated(requestContext)) { refuseRequest(); } } /** * Perform authorization based on roles. * * @param rolesAllowed * @param requestContext */ private void performAuthorization(String[] rolesAllowed, ContainerRequestContext requestContext) { if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) { refuseRequest(); } for (final String role : rolesAllowed) { if (requestContext.getSecurityContext().isUserInRole(role)) { return; } } refuseRequest(); } /** * Check if the user is authenticated. * * @param requestContext * @return */ private boolean isAuthenticated(final ContainerRequestContext requestContext) { // Return true if the user is authenticated or false otherwise // An implementation could be like: // return requestContext.getSecurityContext().getUserPrincipal() != null; } /** * Refuse the request. */ private void refuseRequest() { throw new AccessDeniedException( "You don't have permissions to perform this action."); } } 

Примечание . Вышеупомянутая реализация основана на модели RolesAllowedDynamicFeature . Если вы используете Джерси, вам не нужно писать собственный фильтр, просто используйте существующую реализацию.

  • Неисправность получения ClaimsPrincipal при использовании EasyAuth для аутентификации против AAD в Azure App Service в веб-приложении Asp.Net Core
  • Окончательное руководство по аутентификации веб-сайта на основе форм
  • Сохранять / назначать роли пользователей, прошедших проверку подлинности
  • passport.js RESTful auth
  • Omniauth: обратный вызов не срабатывает, возвращает отказ с «недопустимыми учетными данными»
  • ASP.NET Identity Cookie через поддомены
  • Понимание пассива сериализации десериализации
  • Идентификационные данные пользователя MVC 5 Access Identity
  • Конечные точки Google и аутентификация пользователя
  • Проверка подлинности браузера с использованием Selenium
  • Аутентификация RESTful
  • Давайте будем гением компьютера.