java - डिजिटल भुगतान क्या है




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

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

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

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

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


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

एक और जवाब क्यों? मैंने 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 करने के लिए @Secured साथ रिसोर्स क्लास और @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 प्राथमिकता फ़िल्टर के बाद निष्पादित किया जाता है।

@Secured उपयोग रिसोर्स Method और रिसोर्स Class को प्राप्त करने के लिए किया जा सकता है जो अनुरोध को हैंडल करेगा और फिर उनके साथ @Secured एनोटेशन @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 से प्राप्त कर सकते हैं (जो पहले से ही कंटेनररेंसटेक्स्टटेक् में सेट किया जाना चाहिए) या आपके द्वारा जाने वाले दृष्टिकोण के आधार पर, CDI का उपयोग करके इसे इंजेक्ट करें।

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

Authorization: Bearer <token-goes-here>

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

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

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

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

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

प्रमाणीकरण फ़िल्टर को संसाधन विधियों या संसाधन कक्षाओं में बाँधने के लिए, उन्हें ऊपर बनाए गए @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 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 का नाम होगा।

किसी भी JAX-RS संसाधन वर्ग में SecurityContext को इंजेक्ट करें:

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

@Inject
@AuthenticatedUser
User authenticatedUser;

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

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

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

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

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

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

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

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

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

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

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

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

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

JWT (JSON वेब टोकन)

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

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

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

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

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

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

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

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

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

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

जेडब्ल्यूटी के साथ टोकन रिफ्रेशमेंट को संभालना

जलपान के लिए केवल मान्य (और गैर-समाप्त) टोकन स्वीकार करें। यह एक्सपेक्टेशन डेट में एक्सपेक्टेशन डेट में बताए जाने से पहले टोकन को रिफ्रेश करने की क्लाइंट की जिम्मेदारी है।

आपको टोकन को अनिश्चित काल तक ताज़ा होने से रोकना चाहिए। नीचे कुछ दृष्टिकोण देखें जिन्हें आप विचार कर सकते हैं।

आप अपने टोकन में दो दावे जोड़कर टोकन रिफ्रेशमेंट का ट्रैक रख सकते हैं (दावे के नाम आप पर निर्भर हैं):

  • refreshLimit : बताता है कि टोकन को कितनी बार ताज़ा किया जा सकता है।
  • refreshCount : बताता है कि कितनी बार टोकन ताज़ा किया गया है।

यदि निम्न स्थितियाँ सत्य हैं, तो केवल टोकन को ताज़ा करें:

  • टोकन की समय सीमा समाप्त नहीं हुई है ( exp >= now )।
  • जितनी बार टोकन रीफ्रेश किया गया है, refreshCount < refreshLimit बार जितनी बार टोकन रिफ्रेश किया जा सकता है ( refreshCount < refreshLimit )।

और टोकन को ताज़ा करते समय:

  • समाप्ति तिथि अपडेट करें ( exp = now + some-amount-of-time )।
  • कई बार वृद्धि हुई है कि टोकन ताज़ा किया गया है (ताज़ा करें refreshCount++ )।

वैकल्पिक रूप से, जलपान की संख्या पर नज़र रखने के लिए, आपके पास एक दावा हो सकता है जो पूर्ण समाप्ति तिथि को इंगित करता है (जो ऊपर वर्णित refreshLimit दावे के समान ही काम करता है)। पूर्ण समाप्ति तिथि से पहले , किसी भी संख्या में जलपान स्वीकार्य है।

एक अन्य दृष्टिकोण में अलग-अलग लंबे समय तक ताज़ा रहने वाला टोकन जारी करना शामिल है, जिसका उपयोग अल्पकालिक JWT टोकन जारी करने के लिए किया जाता है।

सबसे अच्छा दृष्टिकोण आपकी आवश्यकताओं पर निर्भर करता है।

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

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

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

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

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

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





jersey-2.0