您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

106 行
3.8 KiB

  1. # Copyright 2023 Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from typing import TYPE_CHECKING
  15. from authlib.jose import JsonWebToken, JWTClaims
  16. from authlib.jose.errors import BadSignatureError, InvalidClaimError, JoseError
  17. from synapse.api.errors import Codes, LoginError
  18. from synapse.types import JsonDict, UserID
  19. if TYPE_CHECKING:
  20. from synapse.server import HomeServer
  21. class JwtHandler:
  22. def __init__(self, hs: "HomeServer"):
  23. self.hs = hs
  24. self.jwt_secret = hs.config.jwt.jwt_secret
  25. self.jwt_subject_claim = hs.config.jwt.jwt_subject_claim
  26. self.jwt_algorithm = hs.config.jwt.jwt_algorithm
  27. self.jwt_issuer = hs.config.jwt.jwt_issuer
  28. self.jwt_audiences = hs.config.jwt.jwt_audiences
  29. def validate_login(self, login_submission: JsonDict) -> str:
  30. """
  31. Authenticates the user for the /login API
  32. Args:
  33. login_submission: the whole of the login submission
  34. (including 'type' and other relevant fields)
  35. Returns:
  36. The user ID that is logging in.
  37. Raises:
  38. LoginError if there was an authentication problem.
  39. """
  40. token = login_submission.get("token", None)
  41. if token is None:
  42. raise LoginError(
  43. 403, "Token field for JWT is missing", errcode=Codes.FORBIDDEN
  44. )
  45. jwt = JsonWebToken([self.jwt_algorithm])
  46. claim_options = {}
  47. if self.jwt_issuer is not None:
  48. claim_options["iss"] = {"value": self.jwt_issuer, "essential": True}
  49. if self.jwt_audiences is not None:
  50. claim_options["aud"] = {"values": self.jwt_audiences, "essential": True}
  51. try:
  52. claims = jwt.decode(
  53. token,
  54. key=self.jwt_secret,
  55. claims_cls=JWTClaims,
  56. claims_options=claim_options,
  57. )
  58. except BadSignatureError:
  59. # We handle this case separately to provide a better error message
  60. raise LoginError(
  61. 403,
  62. "JWT validation failed: Signature verification failed",
  63. errcode=Codes.FORBIDDEN,
  64. )
  65. except JoseError as e:
  66. # A JWT error occurred, return some info back to the client.
  67. raise LoginError(
  68. 403,
  69. "JWT validation failed: %s" % (str(e),),
  70. errcode=Codes.FORBIDDEN,
  71. )
  72. try:
  73. claims.validate(leeway=120) # allows 2 min of clock skew
  74. # Enforce the old behavior which is rolled out in productive
  75. # servers: if the JWT contains an 'aud' claim but none is
  76. # configured, the login attempt will fail
  77. if claims.get("aud") is not None:
  78. if self.jwt_audiences is None or len(self.jwt_audiences) == 0:
  79. raise InvalidClaimError("aud")
  80. except JoseError as e:
  81. raise LoginError(
  82. 403,
  83. "JWT validation failed: %s" % (str(e),),
  84. errcode=Codes.FORBIDDEN,
  85. )
  86. user = claims.get(self.jwt_subject_claim, None)
  87. if user is None:
  88. raise LoginError(403, "Invalid JWT", errcode=Codes.FORBIDDEN)
  89. return UserID(user, self.hs.hostname).to_string()