simple-jwt使用
python
# 使用simple-jwt 快速体验 pip install djangorestframework-simplejwt
# python manage.py createsuperuser
'''创建超级用户是为了方便管理 Django 的后台管理界面,而 simple-jwt 的使用则是为了在 Django REST Framework 中实现 JWT 认证和用户身份验证。这两个步骤是相互独立的,但通常都需要在你的项目中进行。'''
# 用户名: admin
# 电子邮件地址: 333@qq.com
# 密码: 123123
- 登陆签发:默认使用auth的user表--》创建个用户--》能登录了
路由: 登陆接口了
path('login/', token_obtain_pair),
# 127.0.0.1:8080/app01/login-->post--》用户名密码就能登陆
- 认证
class UserTokenView(GenericViewSet, mixins.CreateModelMixin):
# 必须登陆后才能新增
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
创建一个超级管理员
from rest_framework_simplejwt.views import token_obtain_pair
python
# 登陆接口
from rest_framework_simplejwt.views import token_obtain_pair
from three.views import BookViewSet, UserTokenView, TeyView
# 自动生成路由
from rest_framework.routers import SimpleRouter, DefaultRouter
from django.urls import path, include
# 2 实例化得到对象
# router = SimpleRouter()
router = DefaultRouter()
# 3 执行对象的方法
router.register('User', UserTokenView, 'User')
urlpatterns = [
path('login/', token_obtain_pair),
path('try/', TeyView.as_view()),
]
# 5 把自动生成的路由,放到 urlpatterns中
urlpatterns += router.urls
python
# 序列化类
from rest_framework import serializers
from .models import Book,User,UserToken
#
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
#----------------------------------
from rest_framework import mixins
# 认证
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
class UserTokenView(GenericViewSet, mixins.CreateModelMixin):
# 获取表中信息
queryset = Book
# 序列化类
serializer_class = BookSerializer
# 必须登陆之后才能新增
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
使用步骤
-
先登陆接口复制
python
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzEzNDUwMjY1LCJpYXQiOjE3MTM0NDk5NjUsImp0aSI6ImIyNWFjNmI3NDQ3ZjQ3NzFhYjViYjM4ZDYzMjhiNTkzIiwidXNlcl9pZCI6MX0.xcqKK-YY2fpMkYKre6VpUOfmWdr2V_i-XL4R5pkOlCE"
- 对
http://127.0.0.1:8300/three/User/
这个接口header key:Authorization , vlaue:access
的值
【 1 】simple-jwt配置文件
python
# 用户模型 # 应用程序.表名
AUTH_USER_MODEL = "one.UserInfo"
# settings.py配置文件
import datetime
SIMPLE_JWT = {
# token有效时长
'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30),
# token刷新后的有效时间
'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1),
}
accessToken
:用户获取数据权限refreshToken
:用来获取新的accessToken
python
# 用户模型 # 应用程序.表名
AUTH_USER_MODEL = "one.UserInfo"
# JWT配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Access Token的有效期
'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期
# 对于大部分情况,设置以上两项就可以了,以下为默认配置项目,可根据需要进行调整
# 是否自动刷新Refresh Token
'ROTATE_REFRESH_TOKENS': False,
# 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为False,则旧的刷新令牌仍然可以用于获取新的访问令牌。需要将'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
'BLACKLIST_AFTER_ROTATION': False,
'ALGORITHM': 'HS256', # 加密算法
'SIGNING_KEY': settings.SECRET_KEY, # 签名密匙,这里使用Django的SECRET_KEY
# 如为True,则在每次使用访问令牌进行身份验证时,更新用户最后登录时间
"UPDATE_LAST_LOGIN": False,
# 用于验证JWT签名的密钥返回的内容。可以是字符串形式的密钥,也可以是一个字典。
"VERIFYING_KEY": "",
"AUDIENCE": None,# JWT中的"Audience"声明,用于指定该JWT的预期接收者。
"ISSUER": None, # JWT中的"Issuer"声明,用于指定该JWT的发行者。
"JSON_ENCODER": None, # 用于序列化JWT负载的JSON编码器。默认为Django的JSON编码器。
"JWK_URL": None, # 包含公钥的URL,用于验证JWT签名。
"LEEWAY": 0, # 允许的时钟偏差量,以秒为单位。用于在验证JWT的过期时间和生效时间时考虑时钟偏差。
# 用于指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer"
"AUTH_HEADER_TYPES": ("Bearer",),
# 包含JWT的HTTP请求头的名称。默认为"HTTP_AUTHORIZATION"
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
# 用户模型中用作用户ID的字段。默认为"id"。
"USER_ID_FIELD": "id",
# JWT负载中包含用户ID的声明。默认为"user_id"。
"USER_ID_CLAIM": "user_id",
# 用于指定用户身份验证规则的函数或方法。默认使用Django的默认身份验证方法进行身份验证。
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
# 用于指定可以使用的令牌类。默认为"rest_framework_simplejwt.tokens.AccessToken"。
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
# JWT负载中包含令牌类型的声明。默认为"token_type"。
"TOKEN_TYPE_CLAIM": "token_type",
# 用于指定可以使用的用户模型类。默认为"rest_framework_simplejwt.models.TokenUser"。
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
# JWT负载中包含JWT ID的声明。默认为"jti"。
"JTI_CLAIM": "jti",
# 在使用滑动令牌时,JWT负载中包含刷新令牌过期时间的声明。默认为"refresh_exp"。
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
# 滑动令牌的生命周期。默认为5分钟。
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
# 滑动令牌可以用于刷新的时间段。默认为1天。
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
# 用于生成access和刷refresh的序列化器。
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
# 用于刷新访问令牌的序列化器。默认
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
# 用于验证令牌的序列化器。
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
# 用于列出或撤销已失效JWT的序列化器。
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
# 用于生成滑动令牌的序列化器。
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
# 用于刷新滑动令牌的序列化器。
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
【 2 】jwt定制登陆的返回格式
这些的前提条件就是要先写一个登陆的接口
path('login/', token_obtain_pair)``token_obtain_pair
是Django REST Framework SimpleJWT提供的系统自带的用于获取访问令牌(access token)和刷新令牌(refresh token)的登录认证接口。
python
# 登陆接口
from rest_framework_simplejwt.views import token_obtain_pair
from three.views import UserTokenView
# 自动生成路由
from rest_framework.routers import SimpleRouter, DefaultRouter
from django.urls import path, include
# 2 实例化得到对象
# router = SimpleRouter()
router = DefaultRouter()
# 3 执行对象的方法
router.register('User', UserTokenView, 'User')
urlpatterns = [
path('login/', token_obtain_pair),
]
# 5 把自动生成的路由,放到 urlpatterns中
urlpatterns += router.urls
python
# views.py
from rest_framework import mixins
# 认证
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
class UserTokenView(GenericViewSet, mixins.CreateModelMixin):
# 获取表中信息
print(11111)
queryset = Book
# 序列化类
serializer_class = CommonTokenserializer
print(serializer_class)
# <class 'three.serial.BookSerializer'>
# 必须登陆之后才能新增
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
python
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
# 写一个序列化类。继承TokenObtainPairSerializer ------ >> 重写validate方法。
class CommonTokenserializer(TokenObtainPairSerializer):
def validate(self, attrs):
"""
自定义返回的格式
"""
# 获取父类的原始数据
old_data = super().validate(attrs)
data = {'code': 100,
'msg': '登录成功成功',
'username': self.user.username,
# 刷新令牌
'refresh': old_data.get('refresh'),
# 访问令牌
'access': old_data.get('access')
}
return data
配置文件
python
SIMPLE_JWT = {
# token有效时长 访问
# 'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30),
# token刷新后的有效时间 刷新
# 'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1),
"TOKEN_OBTAIN_SERIALIZER": "three.serial.CommonTokenserializer"
}
【 3 】 定制payload格式
这个跟登陆返回格式大致一样
序列化类
CommonTokenObtainSerializer
类继承自TokenObtainPairSerializer
,这意味着它继承了TokenObtainPairSerializer
类的所有属性和方法,并且可以在此基础上进行定制化。get_token(cls, user)
方法是一个类方法,用于获取令牌。它首先调用父类的get_token
方法来获取令牌对象,然后将用户的用户名添加到令牌中,并返回更新后的令牌对象。validate(self, attrs)
方法是一个局部钩子,用于验证输入数据。在这个方法中,首先调用父类的validate
方法来验证输入的属性,并获取到验证后的结果。然后,从验证结果中获取令牌的refresh
和access
属性,并将其与用户的用户名一起包装成一个字典返回给客户端。这个方法的主要作用是在用户登录成功后返回额外的信息,比如用户名和自定义的消息。
python
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CommonTokenserializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
# super() --> 代指父类的对象
# 对象调用类的绑定方法,会自动把对象的类传入。
token = super().get_token(user)
token['name'] = user.username
return token
# 局部钩子
def validate(self, attrs):
dict_res = super().validate(attrs)
data = {
'code': 1000,
'message': '登陆成功',
# 获取用户信息
'username': self.user.username,
'refresh': dict_res.get('refresh'),
'access': dict_res.get('access')
}
return data
python
SIMPLE_JWT = {
"TOKEN_OBTAIN_SERIALIZER": "three.serial.CommonTokenserializer"
}
【 4 】多方式登陆
-
扩写auth的user表---》加入mobile字段
-注意:
-之前迁移过--》auth的user表已经生成了,就不能扩展写
-方案一: 创建新项目,从头做
-方案二:删库,删除迁移记录(咱们自己app和auth和admin)
-以后如果要扩写auth的user表,必须在迁移之前就定好,写好
-
编写登陆接口
这里建议直接删除省事还可以直接就是要我们原本定义的User表中的数据。
python
# mdoels.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(models.Model):
username = models.CharField(max_length=64)
password = models.CharField(max_length=64)
phone = models.CharField(max_length=11)
email = models.EmailField(unique=True) # 添加 email 字段
user_type = models.IntegerField(choices=((1, '注册用户'), (2, '普通管理员'), (3, '超级管理员')), default=1)
要么就是使用在数据迁移之后出现的UserInfo表
UserInfo
表它继承自Django提供的 AbstractUser
模型。AbstractUser
是 Django 内置的用户模型,它包含了常见的用户属性,如用户名、密码和电子邮件地址等 。 我们只需要在这个基础上在models.py文件当添加一个phone字段就可以了。
python
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
phone = models.CharField(max_length=11)
# 这里可以添加额外的字段,不需要再次定义 username、password 和 email 字段
urls.py
http://127.0.0.1:8110/one/user/login/
python
from django.urls import path, include
# 登陆接口
from rest_framework_simplejwt.views import token_obtain_pair
# 自动生成路由
from rest_framework.routers import SimpleRouter, DefaultRouter
from one.views import UserJWTokenView,UserJWTOneView,PublishViews
# 2 实例化得到对象
# router = SimpleRouter()
router = DefaultRouter()
# 多方式登陆
router.register('user', UserJWTokenView, 'user')
# 自定义用户表,手动签发和认证
router.register('user1', UserJWTOneView, 'user1')
# 先登陆在访问
router.register('publish',PublishViews , 'publish')
urlpatterns = [
# 系统自带的登陆接口
path('login/', token_obtain_pair),
# path('api/v1',include(router.urls))/
]
# 5 把自动生成的路由,放到 urlpatterns中
urlpatterns += router.urls
views.py
python
from rest_framework.viewsets import GenericViewSet
# 限制响应方法
from rest_framework.decorators import action
from .serializer import LoginJWTSerial, LoginJWTOneSerial
# 响应
from rest_framework.response import Response
# 认证类
from .authent import JWTOurSerial
class UserJWTokenView(GenericViewSet):
serializer_class = LoginJWTSerial
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
# 正常逻辑:取出手机号 / 用户名 / 邮箱 + 密码 - -》去数据校验 - -》校验通过 -->签发token - -》返回给前端
# 现在直接在序列化定义
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(): # 执行 三层认证
# 校验通过:会把user,access和refresh都放到序列化类对象中--》返回给前端、
# 现在在视图类中----》有个序列化类--》把视图类中变量给序列化类---》序列化类的变量给视图类--》借助于context给[字典]
refresh = serializer.context.get('refresh')
access = serializer.context.get('access')
return Response({'code': 100, 'msg': '登录成功', 'refresh': refresh, 'access': access})
else:
return Response({'code': 101, 'msg': serializer.errors})
序列化类
python
# 序列化模块
from rest_framework import serializers
import re
from rest_framework_simplejwt.tokens import RefreshToken
from one.models import User
# 异常捕获
from rest_framework.exceptions import ValidationError
# 多方式登陆
class LoginJWTSerial(serializers.Serializer):
# 判断是用户名、手机号、邮箱等等
# 对用户进行序列化
username = serializers.CharField()
password = serializers.CharField()
def _get_user(self,attrs):
# 【1】校验用户
username = attrs.get('username')
password = attrs.get('password')
# 我们要去数据库 查询用户 ---> username 可能是 用户名、手机号、邮箱
# 所以我们要在登陆的时候进行检验(使用正则)
if re.match(r'^1[3-9][0-9]{9}$', username):
# 手机号
user = User.objects.filter(phone=username).filter()
elif re.match(r'^.+@.+$',username):
user = User.objects.filter(email=username).filter()
else:
user = User.objects.filter(username=username).filter()
# 校验user是否在User表中是否存在
if user and user.first().password == password:
print(user)
return user.first()
else:
raise ValidationError('用户名或者密码错误!!!')
def validate(self, attrs):
# 取出 手机号/用户名/邮箱+密码--》数据库校验--》校验通过签发 access和refresh,放到context中
user = self._get_user(attrs)
# 验证token
token = RefreshToken.for_user(user)
self.context['access'] = str(token.access_token)
self.context['refresh'] = str(token)
return attrs # 不返回不行:因为源码中校验了是否为空--》
python
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzEzNzkyMzc2LCJpYXQiOjE3MTM3OTIwNzYsImp0aSI6IjllZmQxM2E5MDk2ZTQ0OGFhZjU5ODQ1OGNiOWFkMTRlIiwidXNlcl9pZCI6Mn0.uR1mUGDW703oQkbJhDedm3k8o1AiDGOsWCB4UVEXhvQ
是一个 JSON Web Token(JWT)。JWT 通常由三部分组成,它们用点号 .
分隔开来:
- Header(头部):包含了该 JWT 使用的算法等信息。
- Payload(荷载):包含了实际的数据,比如用户的身份信息以及其他数据。
- Signature(签名):用于验证 JWT 的真实性,确保它没有被篡改过。
在你提供的 JWT 中,第一个逗号之前的部分就是头部,第二个逗号之前的部分是荷载,第三个逗号之后的部分是签名。
总结:
python
# 1 校验数据,放到序列化类的 validate中,而不放在视图类的方法中乐
# 2 视图类和序列化类直接交互变量
serializer.context
# 3 user.check_password 必须是auth的user表,校验密码使用它
# 4 attrs必须返回值,返回空报错
# 5 视图类的方法校验失败的else中:也要return Response
# 6 如何签发token
token = RefreshToken.for_user(user)
self.context['access'] = str(token.access_token)
self.context['refresh'] = str(token)
【 5 】自定义用户表,手动签发和认证
urls.py
python
from django.urls import path, include
# 登陆接口
from rest_framework_simplejwt.views import token_obtain_pair
# 自动生成路由
from rest_framework.routers import SimpleRouter, DefaultRouter
from one.views import UserJWTOneView,PublishViews
# 2 实例化得到对象
# router = SimpleRouter()
router = DefaultRouter()
# 自定义用户表,手动签发和认证
router.register('user1', UserJWTOneView, 'user1')
# 先登陆在访问
router.register('publish',PublishViews , 'publish')
urlpatterns = [
# 系统自带的登陆接口
path('login/', token_obtain_pair),
# path('api/v1',include(router.urls))/
]
# 5 把自动生成的路由,放到 urlpatterns中
urlpatterns += router.urls
views.py
python
class UserJWTOneView(GenericViewSet):
serializer_class = LoginJWTOneSerial
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(): # 执行 三层认证
# 校验通过:会把user,access和refresh都放到序列化类对象中--》返回给前端、
# 现在在视图类中----》有个序列化类--》把视图类中变量给序列化类---》序列化类的变量给视图类--》借助于context给[字典]
refresh = serializer.context.get('refresh')
access = serializer.context.get('access')
return Response({'code': 200, 'msg': '登录成功', 'refresh': refresh, 'access': access})
else:
return Response({'code': 101, 'msg': serializer.errors})
# 登陆之后的测试 ----------------------------------------------------------
class PublishViews(GenericViewSet):
authentication_classes = [JWTOurSerial]
def list(self, request):
return Response('查看成功!!!')
序列化类
python
class LoginJWTOneSerial(serializers.Serializer):
# 用户名
username = serializers.CharField()
password = serializers.CharField()
def _get_user(self, attrs):
# 1 校验用户
username = attrs.get('username')
password = attrs.get('password')
user = User.objects.filter(username=username, password=password).first()
# 校验用户是否
if user:
return user
else:
raise ValidationError('用户名或密码错误')
def validate(self, attrs):
user = self._get_user(attrs)
token = RefreshToken.for_user(user)
self.context['access'] = str(token.access_token)
self.context['refresh'] = str(token)
return attrs
自定义认证类
python
from rest_framework_simplejwt.authentication import JWTAuthentication
from .models import User
class JWTOurAuth(JWTAuthentication):
def authenticate(self, request):
# 取出用户携带的access---》放请求头中:Authorization
token = request.META.get('HTTP_AUTHORIZATION')
if token:
# 校验token--》validated_token 返回的就是可以信任的payload
validated_token = self.get_validated_token(token)
user_id = validated_token['user_id']
user = User.objects.filter(pk=user_id).first()
return user, token
else:
raise AuthenticationFailed('请携带登录信息')
http://127.0.0.1:8110/one/user1/login/
POSThttp://127.0.0.1:8110/one/publish/
GET
- 序列化类
LoginJWTOneSerial
:- 定义了两个字段
username
和password
,用于接收用户提交的用户名和密码。 _get_user
方法用于根据提交的用户名和密码查询用户,如果找到用户,则返回用户对象,否则抛出ValidationError
异常。validate
方法用于对提交的数据进行验证,调用_get_user
方法验证用户,并使用RefreshToken.for_user
方法为用户生成 JWT,将生成的 access token 和 refresh token 放入 context 中返回。
- 定义了两个字段
- 用户模型
User
:- 定义了用户的基本信息,包括用户名、密码、手机号、邮箱和用户类型等字段。
- 使用方式:
- 用户可以通过
http://127.0.0.1:8110/one/user1/login/
地址发送 POST 请求来登录,提交用户名和密码。 - 登录成功后会返回包含 access token 和 refresh token 的响应。
- 用户可以通过
http://127.0.0.1:8110/one/publish/
地址发送 GET 请求来获取发布内容列表,但需要在请求头中带上有效的 JWT 进行身份验证。
- 用户可以通过
双 token 验证流程
双 token 验证机制,其中 accessToken 过期时间较短,refreshToken 过期时间较长。当 accessToken 过期后,使用 refreshToken 去请求新的 token。
- 用户登录向服务端发送账号密码,登录失败返回客户端重新登录。登录成功服务端生成 accessToken 和 refreshToken,返回生成的 token 给客户端。
- 在请求拦截器中,请求头中携带 accessToken 请求数据,服务端验证 accessToken 是否过期。token 有效继续请求数据,token 失效返回失效信息到客户端。
- 客户端收到服务端发送的请求信息,在二次封装的 axios 的响应拦截器中判断是否有 accessToken 失效的信息,没有返回响应的数据。有失效的信息,就携带 refreshToken 请求新的 accessToken。
- 服务端验证 refreshToken 是否有效。有效,重新生成 token, 返回新的 token 和提示信息到客户端,无效,返回无效信息给客户端。
- 客户端响应拦截器判断响应信息是否有 refreshToken 有效无效。无效,退出当前登录。有效,重新存储新的 token,继续请求上一次请求的数据。
注意事项
- 短token失效,服务端拒绝请求,返回token失效信息,前端请求到新的短token如何再次请求数据,达到无感刷新的效果。
- 服务端白名单,成功登录前是还没有请求到token的,那么如果服务端拦截请求,就无法登录。定制白名单,让登录无需进行token验证。
关于双token认证的问题
大佬搭建的接口平台
python
# 1 使用auth的user表---》只能传用户名 ,密码校验
# 2 项目中:手机号/用户名/邮箱 + 密码--》也可以登录成功--》simple-jwt就不行了
# 3 自己定制登陆接口--》使用auth的user表
-签发自己签发
-认证继续用 simple-jwt的认证即可
# 4 编写一个多方式登陆接口
- 扩写auth的user表---》加入mobile字段
-坑:
-之前迁移过--》auth的user表已经生成了,就不能扩展写
-方案一: 创建新项目,从头做
-方案二:删库,删除迁移记录(咱们自己app和auth和admin)
-以后如果要扩写auth的user表,必须在迁移之前就定好,写好
- 编写登陆接口
python
# 用户模型 # 应用程序.表名
AUTH_USER_MODEL = "one.UserInfo"
Tracking file by folder pattern: migrations
Username: admin
Email address: 363@qq.com
Warning: Password input may be echoed.
Password: xxxxxx
Warning: Password input may be echoed.
Password (again): xxxxxx