当前位置: 首页 > 工具软件 > malagu > 使用案例 >

malagu认证与授权之@malagu/security

胡沈义
2023-12-01

导言

  • 本文已参与「开源摘星计划」,欢迎正在阅读的你加入。活动链接: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  

    }

结语

至此,@malagu/security的核心代码就已经完成。在module.ts文件引用,运行项目我们就可以进行的调试了。由于登录逻辑都交由组件处理了,malagu的认证授权还是比较简单的。

思考

  • @malagu/security的原理是怎样的?
  • UserService中返回体User各属性的含义?
  • 登录有效期怎么设置?
  • 可以使用@malagu/security实现单点登录吗?

*** 本文为学习分享文章,如有错误欢迎指正!思考内容欢迎各位大佬答疑解惑。***

 类似资料: