JAX-RS ve Jersey ile REST jeton tabanlı kimlik doğrulaması için en iyi uygulama


460

Jersey'de jeton tabanlı kimlik doğrulamayı etkinleştirmenin bir yolunu arıyorum. Belirli bir çerçeve kullanmamaya çalışıyorum. Mümkün mü?

Planım: Bir kullanıcı web hizmetime kaydoluyor, web hizmetim bir jeton oluşturuyor, istemciye gönderiyor ve istemci onu koruyacak. Daha sonra istemci, her istek için, kullanıcı adı ve şifre yerine jetonu gönderir.

Her istek için özel bir filtre kullanmayı düşünüyordum ve @PreAuthorize("hasRole('ROLE')") bu sadece jetonun geçerli olup olmadığını kontrol etmek için veritabanına isteklerin bir sürü neden olduğunu düşündüm.

Ya da filtre oluşturmayın ve her talepte bir param jetonu koyun? Böylece her API ilk olarak jetonu kontrol eder ve sonra kaynağı almak için bir şey yürütür.

Yanıtlar:


1390

Belirteç tabanlı kimlik doğrulaması nasıl çalışır?

Belirteç tabanlı kimlik doğrulamasında istemci, belirteç adı verilen bir veri parçası için sabit kimlik bilgilerini (kullanıcı adı ve parola gibi) değiştirir . Her istek için, sabit kimlik bilgilerini göndermek yerine, istemci kimlik doğrulama ve ardından yetkilendirme yapmak için belirteci sunucuya gönderir.

Birkaç kelimeyle, jetonlara dayalı bir kimlik doğrulama şeması aşağıdaki adımları izler:

  1. İstemci kimlik bilgilerini (kullanıcı adı ve şifre) sunucuya gönderir.
  2. Sunucu kimlik bilgilerini doğrular ve geçerliyse kullanıcı için bir belirteç oluşturur.
  3. Sunucu, önceden oluşturulan belirteci, kullanıcı tanımlayıcısı ve bir son kullanma tarihi ile birlikte bir miktar depolama alanında saklar.
  4. Sunucu, oluşturulan belirteci istemciye gönderir.
  5. İstemci, her istekte belirteci sunucuya gönderir.
  6. Sunucu, her istekte jetonu gelen istekten alır. Belirteçle, sunucu kimlik doğrulaması gerçekleştirmek için kullanıcı ayrıntılarını arar.
    • Belirteç geçerliyse, sunucu isteği kabul eder.
    • Simge geçersizse, sunucu isteği reddeder.
  7. Kimlik doğrulama gerçekleştirildikten sonra, sunucu yetkilendirme gerçekleştirir.
  8. Sunucu, simgeleri yenilemek için bir uç nokta sağlayabilir.

Not: Sunucu imzalı bir belirteç ( durum bilgisi olmayan kimlik doğrulaması gerçekleştirmenizi sağlayan JWT gibi) yayınladıysa 3. adım gerekli değildir .

JAX-RS 2.0 (Jersey, RESTEasy ve Apache CXF) ile yapabilecekleriniz

Bu çözüm yalnızca JAX-RS 2.0 API'sini kullanır ve satıcıya özel çözümlerden kaçınır . Bu nedenle Jersey , RESTEasy ve Apache CXF gibi JAX-RS 2.0 uygulamaları ile çalışmalıdır .

Belirteç tabanlı kimlik doğrulaması kullanıyorsanız, sunucu uygulaması kapsayıcısı tarafından sunulan ve uygulamanın web.xmltanımlayıcısı aracılığıyla yapılandırılabilen standart Java EE web uygulaması güvenlik mekanizmalarına güvenmediğinizi belirtmek gerekir . Bu özel bir kimlik doğrulamasıdır.

Bir kullanıcının kullanıcı adı ve şifresi ile kimliğini doğrulama ve belirteç verme

Kimlik bilgilerini (kullanıcı adı ve şifre) alan ve doğrulayan bir JAX-RS kaynak yöntemi oluşturun ve kullanıcı için bir belirteç yayınlayın:

@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
    }
}

Kimlik bilgileri doğrulanırken herhangi bir istisna atılırsa, durumuyla 403(Yasak) bir yanıt döndürülür.

Kimlik bilgileri başarıyla doğrulanırsa, durumu 200(Tamam) olan bir yanıt döndürülür ve verilen belirteç yanıt yükünde istemciye gönderilir. İstemci, her istekte belirteci sunucuya göndermelidir.

Tüketim yaparken application/x-www-form-urlencoded, istemci kimlik bilgilerini istek yükünde aşağıdaki biçimde göndermelidir:

username=admin&password=123456

Form parametreleri yerine, kullanıcı adını ve parolayı bir sınıfa sarmak mümkündür:

public class Credentials implements Serializable {

    private String username;
    private String password;

    // Getters and setters omitted
}

Ve sonra JSON olarak tüketin:

@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
}

Bu yaklaşımı kullanarak, istemcinin kimlik bilgilerini isteğin yükünde aşağıdaki biçimde göndermesi gerekir:

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

Jetonun istekten çıkarılması ve onaylanması

İstemci, belirteci Authorizationisteğin standart HTTP üstbilgisinde göndermelidir . Örneğin:

Authorization: Bearer <token-goes-here>

Standart HTTP üstbilgisinin adı, yetkilendirme değil kimlik doğrulama bilgilerini taşıdığı için talihsizdir . Ancak, sunucuya kimlik bilgileri göndermek için standart HTTP üstbilgisidir.

JAX-RS, @NameBindingfiltreleri ve önleyicileri kaynak sınıflarına ve yöntemlerine bağlamak için başka ek açıklamalar oluşturmak için kullanılan bir meta ek açıklama sağlar. Bir @Securedek açıklamayı aşağıdaki gibi tanımlayın :

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

Yukarıda tanımlanan ad bağlama ek açıklaması, ContainerRequestFilterbir kaynak yöntemiyle işlenmeden önce isteği durdurmanıza olanak tanıyan bir filtre sınıfını süslemek için kullanılacaktır. ContainerRequestContextHTTP istek başlıklarını erişebilir ve daha sonra belirteç ayıklamak için kullanılabilir:

@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
    }
}

Belirteç doğrulaması sırasında herhangi bir sorun olursa, durumla ilgili bir yanıt 401(Yetkisiz) döndürülür. Aksi takdirde, istek bir kaynak yöntemine geçecektir.

REST uç noktalarınızı koruma

Kimlik doğrulama filtresini kaynak yöntemlerine veya kaynak sınıflarına bağlamak için, @Securedyukarıda oluşturulan ek açıklama ile açıklama ekleyin . Ek açıklamalı yöntemler ve / veya sınıflar için filtre yürütülür. Bu, bu tür uç noktalara yalnızca talebin geçerli bir belirteçle gerçekleştirilmesi durumunda ulaşılacağı anlamına gelir .

Bazı yöntemlerin veya sınıfların kimlik doğrulamasına ihtiyacı yoksa, bunlara açıklama eklemeyin:

@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
        ...
    }
}

Yukarıda gösterilen örnekte, filtre açıklandığı için yalnızcamySecuredMethod(Long) yöntem için yürütülür @Secured.

Mevcut kullanıcıyı belirleme

Büyük olasılıkla, REST API'nizle ilgili isteği gerçekleştiren kullanıcıyı tanımanız gerekecektir. Bunu başarmak için aşağıdaki yaklaşımlar kullanılabilir:

Geçerli isteğin güvenlik bağlamını geçersiz kılma

ContainerRequestFilter.filter(ContainerRequestContext)Yöntemin içinde SecurityContext, geçerli istek için yeni bir örnek ayarlanabilir. Ardından SecurityContext.getUserPrincipal(), bir Principalörneği döndürerek geçersiz kılın :

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'Adı olan kullanıcı tanımlayıcısını (kullanıcı adı) aramak için jetonu kullanın .

SecurityContextHerhangi bir JAX-RS kaynak sınıfına enjekte edin :

@Context
SecurityContext securityContext;

Aynı şey bir JAX-RS kaynak yönteminde de yapılabilir:

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

Ve sonra Principal:

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

CDI Kullanımı (Bağlam ve Bağımlılık Enjeksiyonu)

Herhangi bir nedenle, geçersiz kılmak istemiyorsanız SecurityContext, etkinlikler ve üreticiler gibi yararlı özellikler sağlayan CDI (Bağlam ve Bağımlılık Enjeksiyonu) kullanabilirsiniz.

Bir CDI niteleyicisi oluşturun:

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

AuthenticationFilterYukarıda oluşturduğunuzda, Eventaçıklamalı bir not ekleyin @AuthenticatedUser:

@Inject
@AuthenticatedUser
Event<String> userAuthenticatedEvent;

Kimlik doğrulama başarılı olursa, kullanıcı adını parametre olarak ileten etkinliği tetikleyin (unutmayın, kullanıcı için simge verilir ve simge kullanıcı tanımlayıcısını aramak için kullanılır):

userAuthenticatedEvent.fire(username);

Uygulamanızda bir kullanıcıyı temsil eden bir sınıf olması muhtemeldir. Bu sınıfa diyelim User.

Kimlik doğrulama olayını işlemek için bir CDI çekirdeği oluşturun User, karşılık gelen kullanıcı adına sahip bir örnek bulun ve authenticatedUserüretici alanına atayın :

@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
    }
}

authenticatedUserAlan bir üretir Userböyle JAX-RS hizmetler, CDI fasulye, servlet ve EJB'ler olarak konteyner yönetilen fasulye, enjekte edilebilir örneği. Bir Userörneği enjekte etmek için aşağıdaki kod parçasını kullanın (aslında, bu bir CDI proxy'sidir):

@Inject
@AuthenticatedUser
User authenticatedUser;

CDI @Producesek açıklamasının JAX-RS ek açıklamasından farklı olduğuna dikkat edin @Produces:

Çekirdeğinizde CDI @Producesnotunu kullandığınızdan emin olun AuthenticatedUserProducer.

Buradaki anahtar, açıklamalı fasulye olup @RequestScopedfiltreler ve çekirdekleriniz arasında veri paylaşmanıza izin verir. Olayları kullanmak istemiyorsanız, kimliği doğrulanmış kullanıcıyı istek kapsamındaki bir çekirdeğe kaydetmek için filtreyi değiştirebilir ve ardından JAX-RS kaynak sınıflarınızdan okuyabilirsiniz.

SecurityContextCDI yaklaşımı, geçersiz kılınan yaklaşıma kıyasla , kimliği doğrulanmış kullanıcıyı JAX-RS kaynakları ve sağlayıcıları dışındaki çekirdeklerden almanıza olanak tanır.

Rol tabanlı yetkilendirmeyi destekleme

Rol tabanlı yetkilendirmeyi nasıl destekleyeceğinize ilişkin ayrıntılar için lütfen diğer cevabıma bakın .

Jeton düzenleme

Bir belirteç şunlar olabilir:

  • Opak: Değerin kendisinden başka hiçbir ayrıntı göstermez (rastgele bir dize gibi)
  • Bağımsız: Simgenin kendisi hakkında ayrıntılar içerir (JWT gibi).

Aşağıdaki ayrıntılara bakın:

Jeton olarak rastgele dize

Bir belirteç rastgele bir dize oluşturularak ve kullanıcı tanımlayıcısı ve bir son kullanma tarihi ile birlikte bir veritabanına devam ettirilerek verilebilir. Java'da rastgele bir dizenin nasıl oluşturulacağına dair iyi bir örnek burada görülebilir . Ayrıca şunları kullanabilirsiniz:

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

JWT (JSON Web Simgesi)

JWT (JSON Web Jetonu), talepleri iki taraf arasında güvenli bir şekilde temsil etmek için standart bir yöntemdir ve RFC 7519 tarafından tanımlanır .

Bu, bağımsız bir jeton ve ayrıntıları taleplerde saklamanızı sağlar . Bu hak talepleri, Base64 olarak kodlanmış bir JSON olan belirteç bilgi yükünde saklanır . RFC 7519'da kayıtlı bazı iddialar ve anlamları (daha fazla bilgi için tam RFC'yi okuyun):

  • iss: Jetonu veren müdür.
  • sub: JWT'nin konusu olan müdür.
  • exp: Belirtecin son kullanma tarihi.
  • nbf: Simgenin işleme alınmaya başlanacağı zaman.
  • iat: Simgenin verildiği zaman.
  • jti: Belirteç için benzersiz tanımlayıcı.

Parola gibi hassas verileri jetonda saklamamanız gerektiğini unutmayın.

Yük müşteri tarafından okunabilir ve token bütünlüğü sunucudaki imzası doğrulanarak kolayca kontrol edilebilir. İmza, simgenin kurcalanmasını önleyen şeydir.

İzlemeniz gerekmiyorsa JWT jetonlarını saklamanız gerekmez. Bununla birlikte, jetonları devam ettirerek, bunların erişimini geçersiz kılma ve iptal etme olasılığınız olacaktır. JWT jetonlarının kaydını tutmak için, tüm jetonu sunucuda saklamak yerine, jeton tanımlayıcısına ( jtihak talebine), jetonu verdiğiniz kullanıcı, son kullanma tarihi vb. Gibi diğer bazı ayrıntılarla devam edebilirsiniz .

Belirteçlere devam ederken, veritabanınızın süresiz büyümesini önlemek için her zaman eskilerini kaldırmayı düşünün.

JWT kullanma

JWT belirteçlerini yayınlamak ve doğrulamak için birkaç Java kitaplığı vardır:

JWT ile çalışmak için başka harika kaynaklar bulmak için http://jwt.io adresine göz atın .

Jeton iptalini JWT ile işleme

Jetonları iptal etmek istiyorsanız, onları takip etmelisiniz. Tüm belirteci sunucu tarafında depolamanız, yalnızca belirteç tanımlayıcısını (benzersiz olması gerekir) ve gerekirse bazı meta verileri depolamanız gerekmez. Belirteç tanımlayıcısı için UUID kullanabilirsiniz .

jtiİstem belirteci token tanımlayıcı saklamak için kullanılır. Belirteci doğrularken, jtisunucu tarafında sahip olduğunuz belirteç tanımlayıcılarına karşı hak talebinin değerini kontrol ederek iptal edilmediğinden emin olun.

Güvenlik nedeniyle, bir kullanıcının şifresini değiştirdiğinde tüm simgeleri iptal edin.

Ek bilgi

  • Hangi tür kimlik doğrulamasını kullanmaya karar verdiğiniz önemli değildir. Ortadaki adam saldırısını önlemek için her zaman bir HTTPS bağlantısının üstünde yapın .
  • Jetonlar hakkında daha fazla bilgi için Bilgi Güvenliği'nden bu soruya göz atın .
  • Bu makalede , belirteç tabanlı kimlik doğrulaması hakkında bazı yararlı bilgiler bulacaksınız.

The server stores the previously generated token in some storage along with the user identifier and an expiration date. The server sends the generated token to the client. Bu nasıl RESTful?
scottysseus

3
@scottyseus Token tabanlı kimlik doğrulama, sunucunun verdiği jetonu nasıl hatırladığıyla çalışır. Durum bilgisi olmayan kimlik doğrulaması için JWT belirteçlerini kullanabilirsiniz.
cassiomolin

Düz yerine karma parola göndermeye ne dersiniz (sunucu tarafından oluşturulan nonce ile karma)? Güvenlik seviyesini artırıyor mu (örneğin https kullanmadığınızda)? Ortada adam olması durumunda - bir seansı ele geçirebilecek, ancak en azından şifreyi alamayacak
Denis Itskovich

15
Bunun resmi belgelerde olmadığına inanamıyorum.
Daniel

2
@grep REST'te sunucu tarafında oturum diye bir şey yoktur. Sonuç olarak, oturum durumu istemci tarafında yönetilir.
cassiomolin

99

Bu cevap tamamen yetkilendirme ile ilgilidir ve kimlik doğrulama hakkındaki önceki cevabımın tamamlayıcısıdır.

Neden başka bir cevap? JSR-250 ek açıklamalarını nasıl destekleyeceğinize dair ayrıntılar ekleyerek önceki yanıtımı genişletmeye çalıştım. Ancak orijinal cevap çok uzundu ve maksimum 30.000 karakter uzunluğunu aştı . Bu yüzden tüm yetkilendirme ayrıntılarını bu cevaba taşıdım, diğer yanıtı kimlik doğrulama ve belirteç vermeye odaklandı.


@SecuredEk açıklama ile role dayalı yetkilendirmeyi destekleme

Diğer yanıtta gösterilen kimlik doğrulama akışının yanı sıra , REST uç noktalarında role dayalı yetkilendirme desteklenebilir.

Bir numaralandırma oluşturun ve rolleri ihtiyaçlarınıza göre tanımlayın:

public enum Role {
    ROLE_1,
    ROLE_2,
    ROLE_3
}

@SecuredRolleri desteklemek için daha önce oluşturulan ad bağlama ek açıklamasını değiştirin :

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

Ardından @Secured, yetkilendirmeyi gerçekleştirmek için kaynak sınıflarına ve yöntemlerine açıklama ekleyin . Yöntem ek açıklamaları sınıf ek açıklamalarını geçersiz kılar:

@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
        ...
    }
}

Önceden tanımlanan öncelik filtresinden AUTHORIZATIONsonra yürütülen önceliğe sahip bir filtre oluşturun AUTHENTICATION.

Bu , isteği işleyecek ResourceInfokaynağı Methodve kaynağı almak Classve daha sonra @Securedbunlardan ek açıklamaları almak için kullanılabilir:

@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<Role> classRoles = extractRoles(resourceClass);

        // Get the resource method which matches with the requested URL
        // Extract the roles declared by it
        Method resourceMethod = resourceInfo.getResourceMethod();
        List<Role> 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<Role> extractRoles(AnnotatedElement annotatedElement) {
        if (annotatedElement == null) {
            return new ArrayList<Role>();
        } else {
            Secured secured = annotatedElement.getAnnotation(Secured.class);
            if (secured == null) {
                return new ArrayList<Role>();
            } else {
                Role[] allowedRoles = secured.value();
                return Arrays.asList(allowedRoles);
            }
        }
    }

    private void checkPermissions(List<Role> 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
    }
}

Kullanıcının işlemi yürütme izni yoksa, istek bir 403(Yasak) ile iptal edilir .

İsteği gerçekleştiren kullanıcıyı tanımak için önceki yanıtıma bakın . İstediğiniz yaklaşıma bağlı olarak SecurityContext(önceden ayarlanmış olması gerekir ContainerRequestContext) veya CDI kullanarak enjekte edebilirsiniz.

Bir @Securedek açıklamada herhangi bir rol belirtilmemişse, kimliği doğrulanmış tüm kullanıcıların bu son noktaya erişebileceğini ve kullanıcıların sahip olduğu rolleri göz ardı ettiğini varsayabilirsiniz.

JSR-250 ek açıklamalarıyla rol tabanlı yetkilendirmeyi destekleme

@SecuredEk açıklamadaki rolleri yukarıda gösterildiği gibi tanımlamaya alternatif olarak @RolesAllowed, @PermitAllve gibi JSR-250 ek açıklamalarını düşünebilirsiniz @DenyAll.

JAX-RS, bu tür açıklamaları kullanıma hazır olarak desteklemez, ancak bir filtreyle elde edilebilir. Hepsini desteklemek istiyorsanız aklınızda bulundurmanız gereken birkaç nokta:

JSR-250 ek açıklamalarını kontrol eden bir yetkilendirme filtresi şöyle olabilir:

@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.");
    }
}

Not: Yukarıdaki uygulama Forma dayanmaktadır RolesAllowedDynamicFeature. Jersey kullanıyorsanız, kendi filtrenizi yazmanıza gerek yoktur, sadece mevcut uygulamayı kullanın.


Mevcut bu zarif çözümle github deposu var mı?
Daniel Ferreira Castro

7
@DanielFerreiraCastro Elbette. Buraya bir bak .
cassiomolin

Bir isteğin yetkili bir kullanıcıdan geldiğini ve kullanıcının verileri "sahibi" olduğu için verileri değiştirebileceğini doğrulamanın iyi bir yolu var mı? Ben user_id== token.userId, ya da onun gibi bir şey olup olmadığını her uç noktada kontrol edebilirsiniz biliyorum , ama bu çok tekrarlayan.
mFeinstein

@mFeinstein Bunun cevabı, yorumlara yazabileceğimden daha fazla karakter gerektirecektir. Size bir yön vermek için sıra düzeyinde güvenlik arayabilirsiniz .
cassiomolin

Satır düzeyinde güvenlik aradığımda veritabanlarıyla ilgili birçok konu görebiliyorum, o zaman bunu yeni bir soru olarak
açacağım
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.