导言
- 本文已参与「开源摘星计划」,欢迎正在阅读的你加入。活动链接:https://github.com/weopenprojects/WeOpen-Star
- malagu的认证与授权参考spring-security思想,详细介绍请移步官方文档。malagu除了基本的security外提供了ODIC 的认证和 OAuth2.0 的授权能力,本文主要介绍@malagu/security组件的基本应用实践。
认证与授权组件 @malagu/security的简单应用
- 1.添加组件
在项目中引用@malagu/security组件
yarn add @malagu/security # 或者 npm i @malagu/security
- 2.重写loginUrl和logoutUrl
定义登录和注销接口和请求方式
malagu:
security:
loginUrl: /api/login
loginMethod: POST
logoutUrl: /api/logout
- 3.重写UserService实现自定义登录
注册用户时密码需要使用PasswordEncoder生成;
我们只需将用户和密码在load()中赋值给security组件User即可,校验比对逻辑交由@malagu/security组件完成。
import { Component, Autowired } from '@malagu/core';
import { UserService, UsernameNotFoundError, AccountStatusError, PasswordEncoder } from '@malagu/security/lib/node';
import { User, ElPolicy, PolicyType, AuthorizeType } from '@malagu/security';
import { OrmContext, Transactional } from "@malagu/typeorm/lib/node";
import { UserEntity } from '@microservice/datasource';
/**
* 重写UserService实现自定义登录
* @param username 登录名可以是用户名称(user_name)或者电话(mobile), 优先级:user_name > mobile
*/
@Component({ id: UserService, rebind: true })
export class UserServiceImpl implements UserService<string, User> {
@Autowired(PasswordEncoder)
protected readonly passwordEncoder: PasswordEncoder;
@Transactional({ readOnly: true })
async load(username:string): Promise<User>{
const repo = OrmContext.getRepository(UserEntity);
let user = await repo.findOne({ userName: username })
if (!user) {
user = await repo.findOne({ mobile: username })
}
if (!user) {
throw new UsernameNotFoundError();
}
if(user.state == false){
throw new AccountStatusError();
}
return {
type: "",
username: user.userName,
password: user.password,
policies: [ <ElPolicy>{
type: PolicyType.el,
authorizeType: AuthorizeType.Pre,
el: 'true'
} ],
accountNonExpired: true,
accountNonLocked: true,
credentialsNonExpired: true,
enabled: true
}
}
}
- 4.重写认证失败处理器AuthenticationErrorHandler
import { Component, Autowired } from '@malagu/core';
import { HttpStatus } from '@malagu/web';
import { ErrorHandler, Context, RedirectStrategy } from '@malagu/web/lib/node';
import { AuthenticationErrorHandler,
AUTHENTICATION_ERROR_HANDLER_PRIORITY,
AuthenticationError } from '@malagu/security/lib/node'
@Component({ id: AuthenticationErrorHandler, rebind: true })
export class AuthenticationErrorHandlerImpl implements ErrorHandler{
readonly priority: number = AUTHENTICATION_ERROR_HANDLER_PRIORITY;
@Autowired(RedirectStrategy)
protected readonly redirectStrategy: RedirectStrategy;
canHandle(ctx: Context, err: Error): Promise<boolean> {
return Promise.resolve(err instanceof AuthenticationError);
}
async handle(ctx: Context, err: AuthenticationError): Promise<void> {
let message = "";
switch (err.name) {
case "UsernameNotFoundError":
ctx.response.statusCode = HttpStatus.FORBIDDEN;
message = "用户不存在";
break;
case "BadCredentialsError":
ctx.response.statusCode = HttpStatus.FORBIDDEN;
message = "用户密码错误";
break;
case "AccountStatusError":
ctx.response.statusCode = HttpStatus.FORBIDDEN;
message = "用户被冻结";
break;
case "AuthenticationError":
ctx.response.statusCode = HttpStatus.UNAUTHORIZED;
message = "用户没有访问权限,需要进行身份认证";
break;
default:
ctx.response.statusCode = HttpStatus.UNAUTHORIZED;
message = err.message;
break;
}
ctx.response.end(message);
}
}
- 5.重写认证成功处理器AuthenticationSuccessHandler
非必需,不重写将跳转到首页
import { Component } from '@malagu/core';
import { HttpStatus } from '@malagu/web';
import { AuthenticationSuccessHandler, Authentication } from '@malagu/security/lib/node'
import { Context } from '@malagu/web/lib/node'
@Component({ id: AuthenticationSuccessHandler, rebind: true })
export class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
async onAuthenticationSuccess(authentication: Authentication): Promise<void> {
Context.getResponse().statusCode = HttpStatus.OK;
Context.getResponse().body = JSON.stringify({ username: authentication.name });
}
}
- 6.重新登出处理器LogoutSuccessHandler
非必需,不重写将跳转到登录页
import { LogoutSuccessHandler, LOGOUT_SUCCESS_HANDLER_PRIORITY } from '@malagu/security/lib/node';
import { Component } from '@malagu/core';
import { HttpStatus } from '@malagu/web';
import { Context } from '@malagu/web/lib/node';
@Component({id: LogoutSuccessHandler, rebind: true })
export class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
readonly priority = LOGOUT_SUCCESS_HANDLER_PRIORITY;
async onLogoutSuccess(): Promise<void> {
Context.getResponse().statusCode = HttpStatus.OK;
Context.getResponse().body = "登出成功";
}
}
- 7.@Authenticated的使用
可以在controller类上使用,这样该类下的所有开放接口都需要鉴权
@Controller("user")
@Authenticated()
export class UserController {
@Autowired(UserInfoService)
protected userInfoService: UserInfoService;
... ...
}
也可以在指定的接口上使用
@Get("/:userId")
@Json()
@Authenticated()
async getUserInfo(@Param("userId") userId: number){
const result = await this.userInfoService.getUserInfo(userId);
return result
}
- 8.除了@Authenticated,malagu还提供了用于权限控制的装饰器@PreAuthorize以及匿名@Anonymous
结语
至此,@malagu/security的核心代码就已经完成。在module.ts文件引用,运行项目我们就可以进行的调试了。由于登录逻辑都交由组件处理了,malagu的认证授权还是比较简单的。
思考
- @malagu/security的原理是怎样的?
- UserService中返回体User各属性的含义?
- 登录有效期怎么设置?
- 可以使用@malagu/security实现单点登录吗?
*** 本文为学习分享文章,如有错误欢迎指正!思考内容欢迎各位大佬答疑解惑。***