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