python后端需要什么基础_一个六年经验的python后端是怎么学习用java写API的(6) 基本的Auth...

描述

实现了依赖注入之后就可以方便的实现各种API的业务逻辑了,下一部的问题就在于权限,我们知道大部分的系统API并不是开放的,需要基本的用户体系(注册、登录、购买、会员、不同的role等等),例如管理员能看到CMS,登录用户才能查看文章详情等等。

代码

parrot tag: auth-and-token

API 鉴权机制 JSON Web Token

简单的说就是用户登录后,客户端(web、iOS、Android)会拿到登录成功返回的一个token,请求其他接口时把token带在Header里面而不用sessionId的策略,比如django restframe work 的 TokenAuthentication,python oauth2 provider accesstoken 等实现。目前基本算业内的通用方案了,比如找下微信开发者文档、微博开发者文档会看到具体的例子。

具体实现

│   ├── auth

│   │   ├── CustomJWTAuthFilter.java

│   │   ├── NoAuth.java

│   │   ├── ParrotSecurityContext.java

│   │   ├── UserAuthenticationDynamicFeature.java

│   │   ├── UserAuthenticationFilter.java

│   │   ├── UserAuthenticator.java

│   │   ├── UserAuthorizer.java

│   │   └── hasher

│   │   ├── PBKDF2PasswordHasher.java

│   │   └── PasswordHasher.java

│   ├── bundles

│   │   ├── AuthBundle.java

│   │   ├── CorsBundle.java

│   │   ├── GuiceBundle.java

│   │   └── MysqlBundle.java

根据dropwizard的文档,主要需要实现下面的类和方法

Authenticator.authenticate,通过token返回用户,此方法即为验证用户是否拥有api权限判断accesstoken的方法

Authorizer.authorize,通过token判断此用户是否有某些permission的权限

AuthFilter.newInstance,是通过java的建造者木事把上面几个类串到一起去的东西

登录判断用户密码正确后另外还需要写一个将user和token关联的方法 tokenize

Authenticator.authenticate 通过token的subject拿到用户唯一标识username

public Optional authenticate(JsonWebToken token) {

final JsonWebTokenValidator expiryValidator = getValidator();

try {

expiryValidator.validate(token);

} catch (TokenExpiredException e) {

throw e;

}

User user = userMapper.selectByUsername(token.claim().subject());

return Optional.fromNullable(user);

}

JsonWebTokenServiceImpl.tokenize 给用户一个可用的token

@Override

public JsonWebToken tokenize(User user) {

return JsonWebToken.builder()

.header(JsonWebTokenHeader.HS512())

.claim(JsonWebTokenClaim.builder()

.subject(user.getUsername())

.issuedAt(DateTime.now())

.expiration(DateTime.now().plusHours(DEFAULT_SESSION_EXPIRATION_HOURS))

.build())

.build();

}

UserAuthorizer.authorize 可以自己定义某些permission字符串实现对应方法,这里我没用到这么细的权限,用户那直接根据用户是否超级管理员和是否有效做了判断

public boolean authorize(User user, String permission) {

return user.hasPermission(permission);

}

user.hasPermission

public boolean hasPermission(String permission) {

if (isSuperUser) {

return true;

}

if (isActive) {

return true;

}

return false;

}

Login API, 不需要登录的接口添加NoAuth,这个东西类似 django restframework 的 rest_framework.permissions 里面的 AllowAny​,默认是需要登录的,这样需要登录的接口在param里面加上 @Auth user 即可拿到登录后的用户

@POST

@Consumes(APPLICATION_JSON)

@NoAuth

@Path("/login")

public MetaMapperResponse login(LoginRequest loginRequest, @Context HttpServletRequest request) throws

InvalidKeySpecException, NoSuchAlgorithmException {

Optional optionalUser = userService.login(loginRequest.getUsername(), loginRequest.getPassword());

if (!optionalUser.isPresent()){

throw new NotAuthorizedException("Wrong username or password");

}

User user = optionalUser.get();

String token = jsonWebTokenService.tokenizeAndSign(user);

UserSerializer serializer = UserSerializer.build(user);

MetaMapperResponse response = new MetaMapperResponse();

response.putMeta("token", token);

response.setData(serializer);

return response;

}

有点意思的点

因为用了django的用户系统(是在是懒,直接用django admin的cms管理用户),django user的密码加密默认采用了pbkdf2_sha256这个加密算法。

django/contrib/auth/hashers.py PBKDF2PasswordHasher,需要把这个翻译成java

class PBKDF2PasswordHasher(BasePasswordHasher):

"""

Secure password hashing using the PBKDF2 algorithm (recommended)

Configured to use PBKDF2 + HMAC + SHA256.

The result is a 64 byte binary string. Iterations may be changed

safely but you must rename the algorithm if you change SHA256.

"""

algorithm = "pbkdf2_sha256"

iterations = 36000

digest = hashlib.sha256

def encode(self, password, salt, iterations=None):

assert password is not None

assert salt and '$' not in salt

if not iterations:

iterations = self.iterations

hash = pbkdf2(password, salt, iterations, digest=self.digest)

hash = base64.b64encode(hash).decode('ascii').strip()

return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)

def verify(self, password, encoded):

algorithm, iterations, salt, hash = encoded.split('$', 3)

assert algorithm == self.algorithm

encoded_2 = self.encode(password, salt, int(iterations))

return constant_time_compare(encoded, encoded_2)

自己翻译了一会儿实现了,结果发现之前有人写过,https://gist.github.com/spapa...,直接抄过来了,只不过需要注意我用的版本是django 1.11 默认他的iterations=36000,需要对应修改一下,这样就可以直接用django的用户系统做登录了。

UserServiceImpl 这样使用

public Optional login(String username, String password) throws InvalidKeySpecException, NoSuchAlgorithmException {

User user = userMapper.selectByUsername(username);

Boolean correct = passwordHasher.checkPassword(password, user.getPassword());

if (correct){

return Optional.ofNullable(user);

}

return Optional.empty();

}

需要权限的API

之前写过的查看文章详情接口,不加@NoAuth即为需要token,参数里面添加@Auth User user即可拿到登录的用户。

@Path("/{id}/secret")

@GET

@Timed

public MetaMapperResponse getSecretArticle(@Auth User user, @NotNull @PathParam("id") Integer articleId) {

MetaMapperResponse response = new MetaMapperResponse();

Boolean isActive = true;

Article article = articleService.get(articleId, isActive);

response.setData(article);

return response;

}

请求时 header中添加 key:Authorization, value: Bearer空格token

key: Authorization

value: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjE1ODUxMjg1MjcsImlhdCI6MTU4NDUyMzcyNywic3ViIjoieWFuZ3lhbmcifQ.F8HKeA2qEz3btJvsM6vvP0T3i0E-dk-FEB-RZzmNBy09xO3VEAXPXzRxIaq6\_18XzZOeKlXYnmndEkgiEgVBFA

即可返回

{

"meta": {},

"data": {

"id": 4,

"cover": "http://cdn.reworkplan.com/6bf32d3229a4.jpg",

"title": "【解局】湖北多地开始实施“战时管制”,为什么?",

"description": "不必恐慌,再坚持忍耐一下",

"is_active": true,

"created": 1581754687304

}

}

token不对则 401 Credentials are required to access this resource.

总结

这个系列大概率完结了,因为剩下的东西在于具体的业务权限了,对于项目架构的东西基本完了。可能还需要做的是:

怎么让response的serializer更加项目式

怎么处理request参数的封装问题,难道一个接口要封装一个类么?

mapper的通用父interface,例如封装常用的selectById,offset、limit等等是否有必要

上面的这些问题可能需要边写边重构,看是否有必要在写对应的文章好了。之后可能会去学一下react、react-router、react-redux做一下前端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值