div class=“articleAbstract“>In diesem Teil der Artikelserie wird für den WCF Service eine Authentifizierung durch Windows Live implementiert.
Überblick zur Artikelserie
Teil 1 gibt eine Übersicht über die eingesetzten Technologien und Portale
Teil 2 beschäftigt sich mit der Erzeugung und Konfiguration der Windows 8 Store App mit Live Anmeldung
Teil 3 erzeugt den WCF Service und sichert diesen über SSL ab
Teil 4 autorisiert den angemeldeten Live-Benutzer an den WCF Services
Teil 5 hosted den Service in Windows Azure
Teil 4 – Autorisierung des WCF Services mit LiveID
Ausgangsituation ist die Solution aus Teil 3 der Artikelserie. Diese besteht aus einer Windows Store App Anwendung, die sich bei Windows Live authentifizieren kann. Diese Clientanwendung ruft einen mit SSL transportgesicherten WCF Service auf.
In Teil 4 wird der WCF Service um eine Autorisierung erweitert. Diese prüft, ob der Benutzer sich mit einer gültigen LiveID angemeldet hat. Damit wird sichergestellt, dass die durch Live gelieferte UserID nicht manipuliert ist und der Benutzer somit eindeutig und sicher identifiziert werden kann.
Durchgeführt wird die Autorisierung mit dem AuthenticationToken der Live-Anmeldung aus Teil 2. Dieses Token ist ein Json Web Token (JWT), das unter anderem UserID und Claims des Benutzers enthält. Das JWT ist bei der Live-Anmeldung mit dem „Geheimen Clientschlüssel“ signiert worden. Den geheimen Clientschlüssel hat Live bei der Konfiguration der Anwendung angelegt (siehe Teil 2 – „Live ID Login“) und kann über das Managementportal https://manage.dev.live.com durch den Entwickler abgefragt werden.
Die Servicekommunikation steht nun vor der Aufgabe das JWT an den Service zu übertragen. Ein probates Mittel besteht darin, die Service-Security von Transport-Security auf TransportWithMessageCredential-Security umzustellen. Dies bedeutet, dass auf dem SSL verschlüsselten Kanal in der Nachricht eine User-Passwort Kombination übergeben wird.
Als Service-User wird die UserID des JWT genutzt. WCF nutzt wiederum den Namen und setzt diesen in der .NET-Umgebung als Name der PrimaryIdentity ein.
Autorisierung des WCF Services
Wie zuvor beschrieben wird das Binding auf TransportWithMessageCredential geändert:
Änderung des Bindings in der web.config
1: <binding name="SayHelloServiceBinding">
2: <security mode="TransportWithMessageCredential">
3: <transport/>
4: <message clientCredentialType="UserName"/>
5: </security>
6: </binding>
Durch die Konfiguration wird ausgesagt, dass im Nachrichteninhalt zusätzlich eine UserName und Passwort Kombination übertragen wird. Diese Informationen werden bei Empfang der Nachricht auf der Service-Seite überprüft, bevor der eigentliche Service aufgerufen wird. Die Service-Schnittstelle ändert sich somit nicht.
User und Passwort müssen auf Server-Seite überprüft werden. Netterweise findet sich in den LiveSDKSamples https://github.com/liveservices/LiveSDK ein Parser für das Format. Als Input nutzt der Parser das WebToken sowie den „geheimen Schlüssel“ zum Validieren der Signatur.
Der Service wird um die Klasse JsonWebToken erweitert:
Neue Klasse JsonWebToken.cs
1: namespace WcfService
2: {
3: // Reference: http://tools.ietf.org/search/draft-jones-json-web-token-00
4: //
5: // JWT is made up of 3 parts: Envelope, Claims, Signature.
6: // - Envelope - specifies the token type and signature algorithm used to produce
7: // signature segment. This is in JSON format
8: // - Claims - specifies claims made by the token. This is in JSON format
9: // - Signature - Cryptographic signature use to maintain data integrity.
10: //
11: // To produce a JWT token:
12: // 1. Create Envelope segment in JSON format
13: // 2. Create Claims segment in JSON format
14: // 3. Create signature
15: // 4. Base64url encode each part and append together separated by "."
16: using System;
17: using System.Collections.Generic;
18: using System.IO;
19: using System.Runtime.Serialization;
20: using System.Runtime.Serialization.Json;
21: using System.Security.Cryptography;
22: using System.Text;
23:
24: public class JsonWebToken
25: {
26: #region Helper Classes
27: [DataContract]
28: public class JsonWebTokenClaims
29: {
30: [DataMember(Name = "exp")]
31: private int expUnixTime
32: {
33: get;
34: set;
35: }
36: private DateTime? expiration = null;
37: public DateTime Expiration
38: {
39: get
40: {
41: if (this.expiration == null)
42: {
43: this.expiration = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(this.expUnixTime);
44: }
45: return (DateTime)this.expiration;
46: }
47: }
48:
49: [DataMember(Name = "iss")]
50: public string Issuer
51: {
52: get;
53: private set;
54: }
55:
56: [DataMember(Name = "aud")]
57: public string Audience
58: {
59: get;
60: private set;
61: }
62:
63: [DataMember(Name = "uid")]
64: public string UserId
65: {
66: get;
67: private set;
68: }
69:
70: [DataMember(Name = "ver")]
71: public int Version
72: {
73: get;
74: private set;
75: }
76:
77: [DataMember(Name = "urn:microsoft:appuri")]
78: public string ClientIdentifier
79: {
80: get;
81: private set;
82: }
83:
84: [DataMember(Name = "urn:microsoft:appid")]
85: public string AppId
86: {
87: get;
88: private set;
89: }
90: }
91:
92: [DataContract]
93: public class JsonWebTokenEnvelope
94: {
95: [DataMember(Name = "typ")]
96: public string Type
97: {
98: get;
99: private set;
100: }
101:
102: [DataMember(Name = "alg")]
103: public string Algorithm
104: {
105: get;
106: private set;
107: }
108:
109: [DataMember(Name = "kid")]
110: public int KeyId
111: {
112: get;
113: private set;
114: }
115: }
116: #endregion
117:
118: #region Properties
119: private static readonly DataContractJsonSerializer ClaimsJsonSerializer = new DataContractJsonSerializer(typeof(JsonWebTokenClaims));
120: private static readonly DataContractJsonSerializer EnvelopeJsonSerializer = new DataContractJsonSerializer(typeof(JsonWebTokenEnvelope));
121: private static readonly UTF8Encoding UTF8Encoder = new UTF8Encoding(true, true);
122: private static readonly SHA256Managed SHA256Provider = new SHA256Managed();
123: private string claimsTokenSegment;
124:
125: public JsonWebTokenClaims Claims
126: {
127: get;
128: private set;
129: }
130:
131: private string envelopeTokenSegment;
132:
133: public JsonWebTokenEnvelope Envelope
134: {
135: get;
136: private set;
137: }
138:
139: public string Signature
140: {
141: get;
142: private set;
143: }
144:
145: public bool IsExpired
146: {
147: get
148: {
149: return this.Claims.Expiration < DateTime.Now;
150: }
151: }
152: #endregion
153:
154: #region Constructors
155: public JsonWebToken(string token, Dictionary<int, string> keyIdsKeys)
156: {
157: // Get the token segments & perform validation
158: string[] tokenSegments = this.SplitToken(token);
159:
160: // Decode and deserialize the claims
161: this.claimsTokenSegment = tokenSegments[1];
162: this.Claims = this.GetClaimsFromTokenSegment(this.claimsTokenSegment);
163:
164: // Decode and deserialize the envelope
165: this.envelopeTokenSegment = tokenSegments[0];
166: this.Envelope = this.GetEnvelopeFromTokenSegment(this.envelopeTokenSegment);
167:
168: // Get the signature
169: this.Signature = tokenSegments[2];
170:
171: // Ensure that the tokens KeyId exists in the secret keys list
172: if (!keyIdsKeys.ContainsKey(this.Envelope.KeyId))
173: {
174: throw new Exception(string.Format("Could not find key with id {0}", this.Envelope.KeyId));
175: }
176:
177: // Validation
178: this.ValidateEnvelope(this.Envelope);
179: this.ValidateSignature(keyIdsKeys[this.Envelope.KeyId]);
180: }
181:
182: private JsonWebToken()
183: {
184: }
185:
186: #endregion
187:
188: #region Parsing Methods
189: private JsonWebTokenClaims GetClaimsFromTokenSegment(string claimsTokenSegment)
190: {
191: byte[] claimsData = this.Base64UrlDecode(claimsTokenSegment);
192: using (MemoryStream memoryStream = new MemoryStream(claimsData))
193: {
194: return ClaimsJsonSerializer.ReadObject(memoryStream) as JsonWebTokenClaims;
195: }
196: }
197:
198: private JsonWebTokenEnvelope GetEnvelopeFromTokenSegment(string envelopeTokenSegment)
199: {
200: byte[] envelopeData = this.Base64UrlDecode(envelopeTokenSegment);
201: using (MemoryStream memoryStream = new MemoryStream(envelopeData))
202: {
203: return EnvelopeJsonSerializer.ReadObject(memoryStream) as JsonWebTokenEnvelope;
204: }
205: }
206:
207: private string[] SplitToken(string token)
208: {
209: // Expected token format: Envelope.Claims.Signature
210: if (string.IsNullOrEmpty(token))
211: {
212: throw new Exception("Token is empty or null.");
213: }
214:
215: string[] segments = token.Split('.');
216: if (segments.Length != 3)
217: {
218: throw new Exception("Invalid token format. Expected Envelope.Claims.Signature");
219: }
220: if (string.IsNullOrEmpty(segments[0]))
221: {
222: throw new Exception("Invalid token format. Envelope must not be empty");
223: }
224: if (string.IsNullOrEmpty(segments[1]))
225: {
226: throw new Exception("Invalid token format. Claims must not be empty");
227: }
228: if (string.IsNullOrEmpty(segments[2]))
229: {
230: throw new Exception("Invalid token format. Signature must not be empty");
231: }
232: return segments;
233: }
234: #endregion
235:
236: #region Validation Methods
237: private void ValidateEnvelope(JsonWebTokenEnvelope envelope)
238: {
239: if (envelope.Type != "JWT")
240: {
241: throw new Exception("Unsupported token type");
242: }
243: if (envelope.Algorithm != "HS256")
244: {
245: throw new Exception("Unsupported crypto algorithm");
246: }
247: }
248:
249: private void ValidateSignature(string key)
250: {
251: // Derive signing key, Signing key = SHA256(secret + "JWTSig")
252: byte[] bytes = UTF8Encoder.GetBytes(key + "JWTSig");
253: byte[] signingKey = SHA256Provider.ComputeHash(bytes);
254:
255: // To Validate:
256: //
257: // 1. Take the bytes of the UTF-8 representation of the JWT Claim
258: // Segment and calculate an HMAC SHA-256 MAC on them using the
259: // shared key.
260: //
261: // 2. Base64url encode the previously generated HMAC as defined in this
262: // document.
263: //
264: // 3. If the JWT Crypto Segment and the previously calculated value
265: // exactly match in a character by character, case sensitive
266: // comparison, then one has confirmation that the key was used to
267: // generate the HMAC on the JWT and that the contents of the JWT
268: // Claim Segment have not be tampered with.
269: //
270: // 4. If the validation fails, the token MUST be rejected.
271:
272: // UFT-8 representation of the JWT envelope.claim segment
273: byte[] input = UTF8Encoder.GetBytes(this.envelopeTokenSegment + "." + this.claimsTokenSegment);
274:
275: // calculate an HMAC SHA-256 MAC
276: using (HMACSHA256 hashProvider = new HMACSHA256(signingKey))
277: {
278: byte[] myHashValue = hashProvider.ComputeHash(input);
279:
280: // Base64 url encode the hash
281: string base64urlEncodedHash = this.Base64UrlEncode(myHashValue);
282:
283: // Now compare the two has values
284: if (base64urlEncodedHash != this.Signature)
285: {
286: throw new Exception("Signature does not match.");
287: }
288: }
289: }
290: #endregion
291:
292: #region Base64 Encode / Decode Functions
293: // Reference: http://tools.ietf.org/search/draft-jones-json-web-token-00
294: public byte[] Base64UrlDecode(string encodedSegment)
295: {
296: string s = encodedSegment;
297: s = s.Replace('-', '+'); // 62nd char of encoding
298: s = s.Replace('_', '/'); // 63rd char of encoding
299: switch (s.Length % 4) // Pad with trailing '='s
300: {
301: case 0: break; // No pad chars in this case
302: case 2: s += "=="; break; // Two pad chars
303: case 3: s += "="; break; // One pad char
304: default: throw new System.Exception("Illegal base64url string");
305: }
306: return Convert.FromBase64String(s); // Standard base64 decoder
307: }
308:
309: public string Base64UrlEncode(byte[] arg)
310: {
311: string s = Convert.ToBase64String(arg); // Standard base64 encoder
312: s = s.Split('=')[0]; // Remove any trailing '='s
313: s = s.Replace('+', '-'); // 62nd char of encoding
314: s = s.Replace('/', '_'); // 63rd char of encoding
315: return s;
316: }
317: #endregion
318: }
319: }
Jetzt fehlt nur noch die Überprüfung des Tokens.
Zur Erinnerung: Das Token enthält Claims und wurde von Live unterschrieben. Diese Unterschrift kann mit dem “geheimen Client Schlüssel” bzw. “Client Secret” der Anwendung geprüft werden. Wie im Teil 2 beschrieben, wird im Management Portal von Live der Schlüssel angezeigt bzw. neu erzeugt.
Die Prüfung des Tokens wird in einem UserNamePasswordValidator realisiert, der in die WCF Konfiguration eingehängt wird. Somit wird das transportierte Token überprüft ohne die Methodensignatur zu ändern. Der Code nutzt Funktionen aus System.IdentityModel bzw. System.IdentityModel.Selectors, die als Referenz hinzugefügt werden.
Neue Klasse CustomUserNameValidator.cs
1: using System;
2: using System.Collections.Generic;
3: using System.IdentityModel.Selectors;
4: using System.Linq;
5: using System.ServiceModel;
6: using System.Web;
7:
8: namespace WcfService
9: {
10: public class CustomUserNameValidator : UserNamePasswordValidator
11: {
12: const string LiveSecretClientKey = "ClientSecret_AusDemLivePortal";
13:
14: public override void Validate(string userName, string password)
15: {
16: try
17: {
18: if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(password))
19: {
20: throw new Exception("User or Password not set");
21: }
22: Dictionary<int, string> d = new Dictionary<int, string>();
23: d.Add(0, LiveSecretClientKey);
24:
25: // Parse Token und validiere Signatur
26: JsonWebToken jwt = new JsonWebToken(password, d);
27:
28: // Prüfe ob User mit der UserId im Token übereinstimmt
29: var jwtUserName = jwt.Claims.UserId;
30: if (jwtUserName != userName)
31: {
32: throw new Exception("Manipulated Username");
33: }
34: }
35: catch (Exception e)
36: {
37: FaultException fe = new FaultException("Unknown Username or Incorrect Password " + e.ToString());
38: throw fe;
39: }
40: }
41: }
42: }
Der geheime Client Schlüssel muss im CustomUserNameValidator eingetragen werden (siehe Zeile 12) damit dieser die Unterschrift prüfen kann.
Das Einhängen des Validators erfolgt in einer Behavior-Konfiguration des Services.
Konfiguration des CustomUserNameValidators in der web.config
1: <behavior>
2: <serviceBehaviors>
3: [...]
4: <serviceCredentials>
5: <userNameAuthentication
6: userNamePasswordValidationMode="Custom"
7: customUserNamePasswordValidatorType="WcfService.CustomUserNameValidator, WcfService" />
8: </serviceCredentials>
9: </behavior>
10: </serviceBehaviors>
Zum Debuggen des Validators muss der Debugger an den IISExpress Attached werden.
Setzen der Service-Credentials im Client
Nun muss der Client beim Serviceaufruf User und Passwort der Service-Authentifizierung setzen. Ansonsten erhält man nachfolgende Fehlermeldung:
InvalidOperationException „The username is not provided. Specify username in ClientCredentials“
Im ersten Schritte wird die UserID aus dem JWT Token extrahiert werden. Die Extraktion erfolgt dabei mit dem gleichen Code wie im CustomUserNameValiditor, mit Ausnahme der Validierung. Zum einen muss auf der Client-Seit das Token nicht validiert werden, zum anderen stehen auch einige kryptografische Funktionen in WinRT nicht zur Verfügung, so dass der Code nur leicht modifiziert funktioniert.
Neu Klasse JsonWebTokenMin.cs
1: namespace ClientStoraApp
2: {
3: // Reference: http://tools.ietf.org/search/draft-jones-json-web-token-00
4: //
5: // JWT is made up of 3 parts: Envelope, Claims, Signature.
6: // - Envelope - specifies the token type and signature algorithm used to produce
7: // signature segment. This is in JSON format
8: // - Claims - specifies claims made by the token. This is in JSON format
9: // - Signature - Cryptographic signature use to maintain data integrity.
10: //
11: // To produce a JWT token:
12: // 1. Create Envelope segment in JSON format
13: // 2. Create Claims segment in JSON format
14: // 3. Create signature
15: // 4. Base64url encode each part and append together separated by "."
16:
17: using System;
18: using System.Collections.Generic;
19: using System.IO;
20: using System.Runtime.Serialization;
21: using System.Runtime.Serialization.Json;
22: using System.Text;
23:
24: public class JsonWebTokenMin
25: {
26: #region Helper Classes
27: [DataContract]
28: public class JsonWebTokenClaims
29: {
30: [DataMember(Name = "exp")]
31: private int expUnixTime
32: {
33: get;
34: set;
35: }
36: private DateTime? expiration = null;
37: public DateTime Expiration
38: {
39: get
40: {
41: if (this.expiration == null)
42: {
43: this.expiration = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(this.expUnixTime);
44: }
45: return (DateTime)this.expiration;
46: }
47: }
48:
49: [DataMember(Name = "iss")]
50: public string Issuer
51: {
52: get;
53: private set;
54: }
55:
56: [DataMember(Name = "aud")]
57: public string Audience
58: {
59: get;
60: private set;
61: }
62:
63: [DataMember(Name = "uid")]
64: public string UserId
65: {
66: get;
67: private set;
68: }
69:
70: [DataMember(Name = "ver")]
71: public int Version
72: {
73: get;
74: private set;
75: }
76:
77: [DataMember(Name = "urn:microsoft:appuri")]
78: public string ClientIdentifier
79: {
80: get;
81: private set;
82: }
83:
84: [DataMember(Name = "urn:microsoft:appid")]
85: public string AppId
86: {
87: get;
88: private set;
89: }
90: }
91:
92: [DataContract]
93: public class JsonWebTokenEnvelope
94: {
95: [DataMember(Name = "typ")]
96: public string Type
97: {
98: get;
99: private set;
100: }
101:
102: [DataMember(Name = "alg")]
103: public string Algorithm
104: {
105: get;
106: private set;
107: }
108:
109: [DataMember(Name = "kid")]
110: public int KeyId
111: {
112: get;
113: private set;
114: }
115: }
116: #endregion
117:
118: #region Properties
119: private static readonly DataContractJsonSerializer ClaimsJsonSerializer = new DataContractJsonSerializer(typeof(JsonWebTokenClaims));
120: private static readonly DataContractJsonSerializer EnvelopeJsonSerializer = new DataContractJsonSerializer(typeof(JsonWebTokenEnvelope));
121: private static readonly UTF8Encoding UTF8Encoder = new UTF8Encoding(true, true);
122: private string claimsTokenSegment;
123:
124: public JsonWebTokenClaims Claims
125: {
126: get;
127: private set;
128: }
129:
130: private string envelopeTokenSegment;
131:
132: public JsonWebTokenEnvelope Envelope
133: {
134: get;
135: private set;
136: }
137:
138: public string Signature
139: {
140: get;
141: private set;
142: }
143:
144: public bool IsExpired
145: {
146: get
147: {
148: return this.Claims.Expiration < DateTime.Now;
149: }
150: }
151: #endregion
152:
153: #region Constructors
154: public JsonWebTokenMin(string token)
155: {
156: // Get the token segments & perform validation
157: string[] tokenSegments = this.SplitToken(token);
158:
159: // Decode and deserialize the claims
160: this.claimsTokenSegment = tokenSegments[1];
161: this.Claims = this.GetClaimsFromTokenSegment(this.claimsTokenSegment);
162:
163: // Decode and deserialize the envelope
164: this.envelopeTokenSegment = tokenSegments[0];
165: this.Envelope = this.GetEnvelopeFromTokenSegment(this.envelopeTokenSegment);
166:
167: // Get the signature
168: this.Signature = tokenSegments[2];
169:
170: // Validation
171: this.ValidateEnvelope(this.Envelope);
172: }
173:
174: private JsonWebTokenMin()
175: {
176: }
177: #endregion
178:
179: #region Parsing Methods
180:
181: private JsonWebTokenClaims GetClaimsFromTokenSegment(string claimsTokenSegment)
182: {
183: byte[] claimsData = this.Base64UrlDecode(claimsTokenSegment);
184: using (MemoryStream memoryStream = new MemoryStream(claimsData))
185: {
186: return ClaimsJsonSerializer.ReadObject(memoryStream) as JsonWebTokenClaims;
187: }
188: }
189:
190: private JsonWebTokenEnvelope GetEnvelopeFromTokenSegment(string envelopeTokenSegment)
191: {
192: byte[] envelopeData = this.Base64UrlDecode(envelopeTokenSegment);
193: using (MemoryStream memoryStream = new MemoryStream(envelopeData))
194: {
195: return EnvelopeJsonSerializer.ReadObject(memoryStream) as JsonWebTokenEnvelope;
196: }
197: }
198:
199: private string[] SplitToken(string token)
200: {
201: // Expected token format: Envelope.Claims.Signature
202: if (string.IsNullOrEmpty(token))
203: {
204: throw new Exception("Token is empty or null.");
205: }
206: string[] segments = token.Split('.');
207: if (segments.Length != 3)
208: {
209: throw new Exception("Invalid token format. Expected Envelope.Claims.Signature");
210: }
211: if (string.IsNullOrEmpty(segments[0]))
212: {
213: throw new Exception("Invalid token format. Envelope must not be empty");
214: }
215: if (string.IsNullOrEmpty(segments[1]))
216: {
217: throw new Exception("Invalid token format. Claims must not be empty");
218: }
219: if (string.IsNullOrEmpty(segments[2]))
220: {
221: throw new Exception("Invalid token format. Signature must not be empty");
222: }
223: return segments;
224: }
225: #endregion
226:
227: #region Validation Methods
228: private void ValidateEnvelope(JsonWebTokenEnvelope envelope)
229: {
230: if (envelope.Type != "JWT")
231: {
232: throw new Exception("Unsupported token type");
233: }
234: if (envelope.Algorithm != "HS256")
235: {
236: throw new Exception("Unsupported crypto algorithm");
237: }
238: }
239: #endregion
240:
241: #region Base64 Encode / Decode Functions
242: // Reference: http://tools.ietf.org/search/draft-jones-json-web-token-00
243: public byte[] Base64UrlDecode(string encodedSegment)
244: {
245: string s = encodedSegment;
246: s = s.Replace('-', '+'); // 62nd char of encoding
247: s = s.Replace('_', '/'); // 63rd char of encoding
248: switch (s.Length % 4) // Pad with trailing '='s
249: {
250: case 0: break; // No pad chars in this case
251: case 2: s += "=="; break; // Two pad chars
252: case 3: s += "="; break; // One pad char
253: default: throw new System.Exception("Illegal base64url string");
254: }
255: return Convert.FromBase64String(s); // Standard base64 decoder
256: }
257:
258: public string Base64UrlEncode(byte[] arg)
259: {
260: string s = Convert.ToBase64String(arg); // Standard base64 encoder
261: s = s.Split('=')[0]; // Remove any trailing '='s
262: s = s.Replace('+', '-'); // 62nd char of encoding
263: s = s.Replace('/', '_'); // 63rd char of encoding
264: return s;
265: }
266: #endregion
267: }
268: }
Als Service-Passwort wird das JWT Token direkt genutzt. Service-User und Service-Passwort können nun zum Service Aufruf (siehe Zeile 14-15) hinzugefügt werden.
Änderung des WCF Aufrufs MainPage.xaml.cs
1: private async void CallWCFButton_Click(object sender, RoutedEventArgs e)
2: {
3: SayHelloServiceClient helloClient = new SayHelloServiceClient(
4: SayHelloServiceClient.EndpointConfiguration.SayHelloService);
5: // Endpunkt für IIS Express (Portnummer bitte anpassen)
6: // EndpointAddress ea = new EndpointAddress("https://localhost:44303/SayHelloService.svc");
7: // Alternativer Endpunkt für Azure (Rechnername und Portnummer bitte anpassen)
8: //EndpointAddress ea = new EndpointAddress("https://win8securewcf.cloudapp.net:443/SayHelloService.svc");
9: helloClient.Endpoint.Address = ea;
10:
11: if (! String.IsNullOrEmpty(AuthToken))
12: {
13: JsonWebTokenMin jwt = new JsonWebTokenMin(AuthToken);
14: helloClient.ClientCredentials.UserName.UserName = jwt.Claims.UserId;
15: helloClient.ClientCredentials.UserName.Password = AuthToken;
16: }
17: WcfOutput = await helloClient.SayHelloAsync("lieber Benutzer");
18: }
Wird nun der Client gestartet antwortet der Service korrekt. Mit einer kleinen Erweiterungen gibt der Service auch die UserId mit aus.
Änderung an SayHello.cs
1: public string SayHello(string input)
2: {
3: ServiceSecurityContext ssc = OperationContext.Current.ServiceSecurityContext;
4: string userName = ssc.PrimaryIdentity.Name;
5: return String.Format("Hello {0}. Your JWT-UserID is '{1}' ", input, userName);
6: }
Finale
Knackpunkt in diesem Teil war die Auswertung und Validierung des Json Web Tokens. Dieses wird durch den Client bereitgestellt und im ServiceHost durch den Validator geprüft.
Sie sehen gerade einen Platzhalterinhalt von Facebook. Um auf den eigentlichen Inhalt zuzugreifen, klicken Sie auf die Schaltfläche unten. Bitte beachten Sie, dass dabei Daten an Drittanbieter weitergegeben werden.
Mehr InformationenSie sehen gerade einen Platzhalterinhalt von Instagram. Um auf den eigentlichen Inhalt zuzugreifen, klicken Sie auf die Schaltfläche unten. Bitte beachten Sie, dass dabei Daten an Drittanbieter weitergegeben werden.
Mehr InformationenSie sehen gerade einen Platzhalterinhalt von X. Um auf den eigentlichen Inhalt zuzugreifen, klicken Sie auf die Schaltfläche unten. Bitte beachten Sie, dass dabei Daten an Drittanbieter weitergegeben werden.
Mehr Informationen