JAX-RS और जर्सी के साथ REST टोकन-आधारित प्रमाणीकरण के लिए सर्वोत्तम अभ्यास


459

मैं जर्सी में टोकन-आधारित प्रमाणीकरण को सक्षम करने का एक तरीका ढूंढ रहा हूं। मैं किसी विशेष ढांचे का उपयोग नहीं करने की कोशिश कर रहा हूं। क्या यह संभव है?

मेरी योजना यह है: एक उपयोगकर्ता मेरी वेब सेवा के लिए साइन अप करता है, मेरी वेब सेवा एक टोकन उत्पन्न करती है, इसे क्लाइंट को भेजती है, और क्लाइंट इसे बनाए रखेगा। फिर क्लाइंट, प्रत्येक अनुरोध के लिए, उपयोगकर्ता नाम और पासवर्ड के बजाय टोकन भेजेगा।

मैं प्रत्येक अनुरोध के लिए एक कस्टम फ़िल्टर का उपयोग करने के बारे में सोच रहा था, @PreAuthorize("hasRole('ROLE')") लेकिन मैंने अभी सोचा कि यह डेटाबेस के लिए बहुत सारे अनुरोधों की जाँच करता है कि टोकन वैध है या नहीं।

या फ़िल्टर नहीं बनाते हैं और प्रत्येक अनुरोध में एक परम टोकन डालते हैं? ताकि प्रत्येक एपीआई पहले टोकन की जांच करे और संसाधन को पुनः प्राप्त करने के लिए कुछ निष्पादित करे।

जवाबों:


1387

टोकन आधारित प्रमाणीकरण कैसे काम करता है

टोकन-आधारित प्रमाणीकरण में, क्लाइंट टोकन नामक डेटा के एक टुकड़े के लिए हार्ड क्रेडेंशियल्स (जैसे उपयोगकर्ता नाम और पासवर्ड) का आदान- प्रदान करता है । प्रत्येक अनुरोध के लिए, ग्राहक हार्ड क्रेडेंशियल भेजने के बजाय, प्रमाणीकरण और फिर प्राधिकरण करने के लिए सर्वर को टोकन भेजेगा।

कुछ शब्दों में, टोकन पर आधारित प्रमाणीकरण योजना इन चरणों का पालन करती है:

  1. क्लाइंट अपना क्रेडेंशियल (उपयोगकर्ता नाम और पासवर्ड) सर्वर को भेजता है।
  2. सर्वर क्रेडेंशियल्स को प्रमाणित करता है और, यदि वे मान्य हैं, तो उपयोगकर्ता के लिए एक टोकन उत्पन्न करते हैं।
  3. सर्वर उपयोगकर्ता के पहचानकर्ता और एक समाप्ति तिथि के साथ कुछ स्टोरेज में पहले से उत्पन्न टोकन को संग्रहीत करता है।
  4. सर्वर क्लाइंट को उत्पन्न टोकन भेजता है।
  5. क्लाइंट प्रत्येक अनुरोध में सर्वर को टोकन भेजता है।
  6. सर्वर, प्रत्येक अनुरोध में, आने वाले अनुरोध से टोकन निकालता है। टोकन के साथ, सर्वर प्रमाणीकरण करने के लिए उपयोगकर्ता के विवरण को देखता है।
    • यदि टोकन मान्य है, तो सर्वर अनुरोध को स्वीकार करता है।
    • यदि टोकन अमान्य है, तो सर्वर अनुरोध को अस्वीकार कर देता है।
  7. प्रमाणीकरण हो जाने के बाद, सर्वर प्राधिकरण करता है।
  8. टोकन को ताज़ा करने के लिए सर्वर एक समापन बिंदु प्रदान कर सकता है।

नोट: चरण 3 की आवश्यकता नहीं है यदि सर्वर ने एक हस्ताक्षरित टोकन जारी किया है (जैसे कि जेडब्ल्यूटी, जो आपको सांख्यिकीय प्रमाणीकरण करने की अनुमति देता है)।

आप JAX-RS 2.0 (जर्सी, RESTEasy और Apache CXF) के साथ क्या कर सकते हैं

यह समाधान केवल JAX-RS 2.0 एपीआई का उपयोग करता है, किसी भी विक्रेता विशिष्ट समाधान से परहेज करता है । तो, यह JAX-RS 2.0 कार्यान्वयन के साथ काम करना चाहिए, जैसे जर्सी , रेस्टैसी और अपाचे सीएक्सएफ

यह उल्लेख करना सार्थक है कि यदि आप टोकन-आधारित प्रमाणीकरण का उपयोग कर रहे हैं, तो आप सर्वलेट कंटेनर द्वारा प्रस्तुत मानक जावा ईई वेब एप्लिकेशन सुरक्षा तंत्र पर भरोसा नहीं कर रहे हैं और एप्लिकेशन के 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(ओके) के साथ एक प्रतिक्रिया वापस कर दी जाएगी और जारी किए गए टोकन को ग्राहक को प्रतिक्रिया पेलोड में भेजा जाएगा। क्लाइंट को प्रत्येक अनुरोध में सर्वर पर एक टोकन भेजना होगा।

उपभोग करते समय application/x-www-form-urlencoded, ग्राहक को अनुरोध पेलोड में निम्नलिखित प्रारूप में क्रेडेंशियल्स भेजना होगा:

username=admin&password=123456

फॉर्म पैरा के बजाय, उपयोगकर्ता नाम और पासवर्ड को एक वर्ग में लपेटना संभव है:

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

अनुरोध से टोकन निकालना और इसे मान्य करना

क्लाइंट को Authorizationअनुरोध के मानक HTTP हेडर में टोकन भेजना चाहिए । उदाहरण के लिए:

Authorization: Bearer <token-goes-here>

मानक HTTP हेडर का नाम दुर्भाग्यपूर्ण है क्योंकि यह प्रमाणीकरण सूचना देता है, प्राधिकरण नहीं । हालाँकि, यह सर्वर पर क्रेडेंशियल भेजने के लिए मानक HTTP हेडर है।

JAX-RS प्रदान करता है @NameBinding, एक मेटा-एनोटेशन है जिसका उपयोग फिल्टर और इंटरसेप्टर को संसाधन वर्गों और विधियों से बाँधने के लिए अन्य एनोटेशन बनाने के लिए किया जाता है। एक @Securedएनोटेशन को निम्नानुसार परिभाषित करें :

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

उपरोक्त परिभाषित नाम-बाइंडिंग एनोटेशन का उपयोग फ़िल्टर वर्ग को सजाने के लिए किया जाएगा, जो लागू करता है ContainerRequestFilter, आपको संसाधन विधि द्वारा नियंत्रित होने से पहले अनुरोध को रोकना है। ContainerRequestContextHTTP अनुरोध हेडर का उपयोग करने के लिए और फिर टोकन निकालने के लिए इस्तेमाल किया जा सकता है:

@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(अनधिकृत) के साथ एक प्रतिक्रिया वापस कर दी जाएगी। अन्यथा अनुरोध संसाधन विधि के लिए आगे बढ़ेगा।

अपने बाकी के समापन बिंदुओं को सुरक्षित करना

प्रमाणीकरण फ़िल्टर को संसाधन विधियों या संसाधन कक्षाओं में बाँधने के लिए, उन्हें @Securedऊपर दिए गए एनोटेशन के साथ एनोटेट करें । जिन विधियों और / या वर्गों को एनोटेट किया गया है, उनके लिए फ़िल्टर निष्पादित किया जाएगा। इसका अर्थ है कि इस तरह के समापन बिंदु केवल तभी पहुंचेंगे जब अनुरोध एक वैध टोकन के साथ किया जाता है।

यदि कुछ तरीकों या कक्षाओं को प्रमाणीकरण की आवश्यकता नहीं है, तो बस उन्हें एनोटेट न करें:

@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 एपीआई। इसे प्राप्त करने के लिए निम्नलिखित तरीकों का इस्तेमाल किया जा सकता है:

वर्तमान अनुरोध के सुरक्षा संदर्भ को ओवरराइड करना

आपकी 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's नाम होगा।

SecurityContextकिसी भी 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 and Dependency Injection) का उपयोग कर सकते हैं, जो घटनाओं और उत्पादकों जैसी उपयोगी सुविधाएँ प्रदान करता है।

एक CDI क्वालिफायर बनाएं:

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

अपने AuthenticationFilterउपर्युक्त में, के Eventसाथ एनोटेट इंजेक्ट करें @AuthenticatedUser:

@Inject
@AuthenticatedUser
Event<String> userAuthenticatedEvent;

यदि प्रमाणीकरण सफल होता है, तो उपयोगकर्ता नाम को पारित करने वाले ईवेंट को पैरामीटर के रूप में आगें (याद रखें, उपयोगकर्ता के लिए टोकन जारी किया गया है और उपयोगकर्ता पहचानकर्ता को देखने के लिए टोकन का उपयोग किया जाएगा):

userAuthenticatedEvent.fire(username);

यह बहुत संभावना है कि एक वर्ग है जो आपके आवेदन में एक उपयोगकर्ता का प्रतिनिधित्व करता है। चलो इस वर्ग को बुलाते हैं 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 सेम, सर्वलेट्स और EJBs जैसे कंटेनर प्रबंधित बीन्स में इंजेक्ट किया जा सकता है। Userउदाहरण को इंजेक्ट करने के लिए कोड के निम्नलिखित टुकड़े का उपयोग करें (वास्तव में, यह एक सीडीआई प्रॉक्सी है):

@Inject
@AuthenticatedUser
User authenticatedUser;

ध्यान दें कि CDI @Producesएनोटेशन JAX-RS एनोटेशन से अलग है @Produces:

सुनिश्चित करें कि आप @Producesअपने AuthenticatedUserProducerबीन में सीडीआई एनोटेशन का उपयोग करते हैं ।

यहाँ कुंजी बीन एनोटेट की गई है @RequestScoped, जिससे आप फ़िल्टर और अपने बीन्स के बीच डेटा साझा कर सकते हैं। यदि आप ईवेंट का उपयोग नहीं करना चाहते हैं, तो आप प्रमाणित उपयोगकर्ता को अनुरोधित स्कोप बीन में संग्रहीत करने के लिए फ़िल्टर को संशोधित कर सकते हैं और फिर इसे अपने JAX-RS संसाधन कक्षाओं से पढ़ सकते हैं।

ओवरराइड करने वाले दृष्टिकोण की तुलना में SecurityContext, CDI दृष्टिकोण आपको JAX-RS संसाधनों और प्रदाताओं के अलावा सेम से प्रमाणित उपयोगकर्ता प्राप्त करने की अनुमति देता है।

सहायक भूमिका आधारित प्राधिकरण

भूमिका-आधारित प्राधिकरण का समर्थन करने के तरीके के विवरण के लिए कृपया मेरे अन्य उत्तर को देखें।

टोकन जारी करना

एक टोकन हो सकता है:

  • अपारदर्शी: स्वयं मूल्य के अलावा अन्य कोई विवरण नहीं बताता है (जैसे एक यादृच्छिक स्ट्रिंग)
  • स्व-निहित: इसमें टोकन के बारे में विवरण (जैसे जेडब्ल्यूटी) शामिल हैं।

विवरण नीचे देखें:

टोकन के रूप में यादृच्छिक स्ट्रिंग

एक टोकन एक यादृच्छिक स्ट्रिंग उत्पन्न करके और इसे एक डेटाबेस के साथ उपयोगकर्ता पहचानकर्ता और समाप्ति तिथि के साथ जारी करके जारी किया जा सकता है। जावा में एक यादृच्छिक स्ट्रिंग उत्पन्न करने का एक अच्छा उदाहरण यहां देखा जा सकता है । आप भी इस्तेमाल कर सकते हैं:

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

JWT (JSON वेब टोकन)

JWT (JSON वेब टोकन) दो दलों के बीच सुरक्षित रूप से दावों का प्रतिनिधित्व करने के लिए एक मानक तरीका है और इसे RFC 7519 द्वारा परिभाषित किया गया है ।

यह एक स्व-निहित टोकन है और यह आपको दावों में विवरण संग्रहीत करने में सक्षम बनाता है । इन दावों को टोकन पेलोड में संग्रहीत किया जाता है जो कि बेस 64 के रूप में एक JSON एन्कोडेड है । यहाँ कुछ दावे आरएफसी 7519 में दर्ज किए गए हैं और उनका क्या अर्थ है (आगे के विवरण के लिए पूर्ण आरएफसी पढ़ें):

  • iss: प्रधानाचार्य जिसने टोकन जारी किया।
  • sub: प्रिंसिपल जो कि JWT का विषय है।
  • exp: टोकन के लिए समाप्ति तिथि।
  • nbf: जिस समय प्रसंस्करण के लिए टोकन स्वीकार करना शुरू किया जाएगा।
  • iat: समय जिस पर टोकन जारी किया गया था।
  • jti: टोकन के लिए विशिष्ट पहचानकर्ता।

ध्यान रखें कि आपको संवेदनशील डेटा, जैसे कि पासवर्ड, टोकन में संग्रहीत नहीं करना चाहिए।

पेलोड को क्लाइंट द्वारा पढ़ा जा सकता है और टोकन की अखंडता को सर्वर पर इसके हस्ताक्षर की पुष्टि करके आसानी से जांचा जा सकता है। हस्ताक्षर वह है जो टोकन को छेड़छाड़ करने से रोकता है।

यदि आपको उन्हें ट्रैक करने की आवश्यकता नहीं है, तो आपको JWT टोकन को जारी रखने की आवश्यकता नहीं होगी। सोचा, टोकन जारी करके, आपको उनकी पहुंच को अमान्य करने और रद्द करने की संभावना होगी। JWT टोकन का ट्रैक रखने के लिए, सर्वर पर पूरे टोकन को जारी रखने के बजाय, आप टोकन पहचानकर्ता ( jtiदावा) को कुछ अन्य विवरणों जैसे कि आपके द्वारा जारी किए गए उपयोगकर्ता, समाप्ति तिथि आदि के साथ जारी रख सकते हैं ।

टोकन जारी रखते समय, अपने डेटाबेस को अनिश्चित काल तक बढ़ने से रोकने के लिए हमेशा पुराने को हटाने पर विचार करें।

JWT का उपयोग करना

JWT टोकन जारी करने और उन्हें मान्य करने के लिए कुछ जावा लाइब्रेरी हैं:

JWT के साथ काम करने के लिए कुछ अन्य महान संसाधनों को खोजने के लिए, http://jwt.io पर एक नज़र डालें ।

जेडब्ल्यूटी के साथ टोकन निरस्तीकरण से निपटने

यदि आप टोकन रद्द करना चाहते हैं, तो आपको उन पर नज़र रखनी होगी। आपको सर्वर की तरफ पूरे टोकन को स्टोर करने की आवश्यकता नहीं है, केवल टोकन आइडेंटिफ़ायर (जो यूनिक होना चाहिए) और कुछ मेटाडेटा को स्टोर करें यदि आपको ज़रूरत है। टोकन पहचानकर्ता के लिए आप UUID का उपयोग कर सकते हैं ।

jtiस्वीकार कर लेंगे पर टोकन पहचानकर्ता को संग्रह करने के लिए इस्तेमाल किया जाना चाहिए। टोकन को मान्य करते समय, यह सुनिश्चित करें कि jtiसर्वर साइड पर आपके द्वारा पहचाने गए टोकन पहचानकर्ताओं के खिलाफ दावे के मूल्य की जांच करके इसे रद्द नहीं किया गया है ।

सुरक्षा उद्देश्यों के लिए, अपना पासवर्ड बदलने पर उपयोगकर्ता के लिए सभी टोकन रद्द करें।

अतिरिक्त जानकारी

  • इससे कोई फर्क नहीं पड़ता कि आप किस प्रकार के प्रमाणीकरण का उपयोग करने का निर्णय लेते हैं। हमेशा यह एक HTTPS कनेक्शन के शीर्ष पर करते हैं ताकि मैन-इन-द-बीच हमले को रोका जा सके ।
  • टोकन के बारे में अधिक जानकारी के लिए सूचना सुरक्षा के इस प्रश्न पर एक नज़र डालें ।
  • इस लेख में आपको टोकन-आधारित प्रमाणीकरण के बारे में कुछ उपयोगी जानकारी मिलेगी।

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. यह कैसे होता है?
21

3
@scottyseus टोकन आधारित प्रमाणीकरण यह बताता है कि सर्वर ने जारी किए गए टोकन को कैसे याद किया। आप स्टेटलेस प्रमाणीकरण के लिए JWT टोकन का उपयोग कर सकते हैं।
कैसियोमोलिन 9:18

सादे के बजाय हैशेड पासवर्ड भेजने के बारे में क्या है (सर्वर-जनरेटेड नॉन के साथ हैशेड)? क्या यह सुरक्षा स्तर बढ़ाता है (उदाहरण के लिए जब https का उपयोग नहीं कर रहा है)? बीच में आदमी के मामले में - वह एक सत्र को हाईजैक करने में सक्षम हो जाएगा, लेकिन कम से कम उसे पासवर्ड नहीं मिलेगा
डेनिस इटकोविचich

15
मुझे विश्वास नहीं हो रहा है कि यह आधिकारिक दस्तावेज में नहीं है।
डैनियल एम।

2
@grep REST में, सर्वर साइड पर सत्र जैसी कोई चीज नहीं है। नतीजतन, सत्र राज्य क्लाइंट पक्ष पर प्रबंधित किया जाता है।
कैसियोमोलिन

98

यह उत्तर सभी प्राधिकरण के बारे में है और यह प्रमाणीकरण के बारे में मेरे पिछले उत्तर का पूरक है

दूसरा जवाब क्यों ? मैंने JSR-250 एनोटेशन का समर्थन करने के बारे में विवरण जोड़कर अपने पिछले उत्तर का विस्तार करने का प्रयास किया। हालाँकि मूल उत्तर बहुत लंबा हो गया और 30,000 वर्णों की अधिकतम लंबाई को पार कर गया । इसलिए मैंने प्रमाणीकरण को पूरा करने और टोकन जारी करने के लिए अन्य जवाब को ध्यान में रखते हुए पूरे प्राधिकरण विवरण को इस उत्तर में स्थानांतरित कर दिया।


@Securedएनोटेशन के साथ सहायक भूमिका आधारित प्राधिकरण

अन्य उत्तर में दिखाए गए प्रमाणीकरण प्रवाह के अलावा , भूमिका आधारित प्राधिकरण को REST समापन बिंदुओं में समर्थित किया जा सकता है।

अपनी आवश्यकताओं के अनुसार एक गणना बनाएं और भूमिकाओं को परिभाषित करें:

public enum Role {
    ROLE_1,
    ROLE_2,
    ROLE_3
}

@Securedभूमिकाओं का समर्थन करने से पहले बनाए गए नाम बाइंडिंग एनोटेशन को बदलें :

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

और फिर @Securedप्राधिकरण के प्रदर्शन के लिए संसाधन वर्गों और विधियों की व्याख्या करें । विधि एनोटेशन वर्ग एनोटेशन को ओवरराइड करेगा:

@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है कि अनुरोध को संभाल लेंगे और उसके बाद निकालने @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<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
    }
}

यदि उपयोगकर्ता को ऑपरेशन को निष्पादित करने की कोई अनुमति नहीं है, तो अनुरोध को एक 403(निषिद्ध) के साथ निरस्त कर दिया जाता है ।

अनुरोध करने वाले उपयोगकर्ता को जानने के लिए, मेरा पिछला उत्तर देखें । आप इसे SecurityContext(जो पहले से सेट होना चाहिए ContainerRequestContext) से प्राप्त कर सकते हैं या आपके द्वारा जाने वाले दृष्टिकोण के आधार पर सीडीआई का उपयोग करके इसे इंजेक्ट कर सकते हैं।

यदि किसी @Securedएनोटेशन में कोई भूमिका घोषित नहीं की गई है, तो आप मान सकते हैं कि सभी प्रमाणित उपयोगकर्ता उस एंडपॉइंट तक पहुंच सकते हैं, जो उपयोगकर्ताओं द्वारा की जाने वाली भूमिकाओं की उपेक्षा करते हैं।

JSR-250 एनोटेशन के साथ सहायक भूमिका आधारित प्राधिकरण

वैकल्पिक रूप से @Securedएनोटेशन में भूमिकाओं को परिभाषित करने के लिए जैसा कि ऊपर दिखाया गया है, आप जेएसआर -50 एनोटेशन जैसे @RolesAllowed, @PermitAllऔर पर विचार कर सकते हैं @DenyAll

JAX-RS ऐसे एनोटेशन का आउट-ऑफ-द-बॉक्स समर्थन नहीं करता है, लेकिन इसे एक फ़िल्टर के साथ प्राप्त किया जा सकता है। यदि आप उन सभी का समर्थन करना चाहते हैं, तो ध्यान में रखने के लिए यहां कुछ विचार दिए गए हैं:

  • @DenyAllविधि पर पर पूर्वता लेता है @RolesAllowedऔर @PermitAllवर्ग पर।
  • @RolesAllowedविधि @PermitAllपर वर्ग पर पूर्वता लेता है ।
  • @PermitAllविधि @RolesAllowedपर वर्ग पर पूर्वता लेता है ।
  • @DenyAll कक्षाओं में संलग्न नहीं किया जा सकता है।
  • @RolesAllowedकक्षा में कक्षा पर पूर्वता लेता है @PermitAll

तो एक प्राधिकरण फ़िल्टर जो 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। यदि आप जर्सी का उपयोग करते हैं, तो आपको अपना फ़िल्टर लिखने की आवश्यकता नहीं है, बस मौजूदा कार्यान्वयन का उपयोग करें।


क्या इस सुरुचिपूर्ण समाधान के साथ कोई गितब रिपॉजिटरी उपलब्ध है?
डैनियल फेरेइरा कास्त्रो

6
@DanielFerreiraCastro बेशक। यहाँ एक नज़र है
कैसियोमोलिन 12

क्या यह पुष्टि करने का कोई अच्छा तरीका है कि एक अनुरोध अधिकृत उपयोगकर्ता का है और वह उपयोगकर्ता डेटा को बदल सकता है क्योंकि वह डेटा का "मालिक" है (उदाहरण के लिए एक हैकर अपने टोकन का उपयोग दूसरे उपयोगकर्ता का नाम बदलने के लिए नहीं कर सकता है)? मुझे पता है कि मैं हर समापन बिंदु पर जांच कर सकता हूं अगर user_id== token.userId, या ऐसा कुछ हो, लेकिन यह बहुत दोहराव वाला है।
mFeinstein

@mFeinstein इसके लिए एक उत्तर के लिए निश्चित रूप से अधिक पात्रों की आवश्यकता होगी, जैसा कि मैं यहां टिप्पणियों में लिख सकता हूं। बस आपको कुछ दिशा देने के लिए, आप पंक्ति स्तर सुरक्षा की तलाश कर सकते हैं ।
कैसियोमोलिन

जब मैं पंक्ति स्तर सुरक्षा की खोज करता हूं, तो मैं डेटाबेस पर बहुत सारे विषय देख सकता हूं, फिर मैं इसे एक नए प्रश्न के रूप में
खोलूंगा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.