07客户端登录

07客户端登录

在开始实现客户端登录前,我将之前的代码做了以下重构。

  • IConfig 重新命名为 ServiceProxyConfig
  • ClientBase 重命名为 AbpServiceBase 文件名改为 api\AbpServiceBase.ts
  • ServiceProxyConfig独立到api\ServiceProxyConfig.ts文件中
  • 修改了使用nswag生成客户端的配置脚本,新脚本为:
    • package.json文件中的配置改为:
      "scripts": {
        "generate-client": "nswag run"
      }
      
    • 添加nswag.json文件,此文件为nswag的默认配置脚本,配置如下
      {
          "runtime": "Default",
          "defaultVariables": null,
          "documentGenerator": {
            "fromDocument": {
              "url": "https://localhost:7027/swagger/v1/swagger.json",
              "output": null,
              "newLineBehavior": "Auto"
            }
          },
          "codeGenerators": {
            "openApiToTypeScriptClient": {
              "className": "{controller}ServiceProxie", // 生成的代理类名模板
              "moduleName": "",
              "namespace": "",
              "typeScriptVersion": 2.7,
              "template": "Fetch",
              "promiseType": "Promise",
              "httpClass": "HttpClient",
              "withCredentials": false,
              "useSingletonProvider": false,
              "injectionTokenType": "InjectionToken",
              "rxJsVersion": 6.0,
              "dateTimeType": "Date",
              "nullValue": "Undefined",
              "generateClientClasses": true,
              "generateClientInterfaces": false,
              "generateOptionalParameters": true,
              "exportTypes": true,
              "wrapDtoExceptions": true,
              "exceptionClass": "ApiException",   // 异常类名称
              "clientBaseClass": "AbpServiceBase",  // 父类名称
              "wrapResponses": false,
              "wrapResponseMethods": [],
              "generateResponseClasses": true,
              "responseClass": "SwaggerResponse", // 响应类名称
              "protectedMethods": [],
              "configurationClass": "ServiceProxyConfig", // 配置类名称
              "useTransformOptionsMethod": true,  // 生成请求参数转换方法
              "useTransformResultMethod": true,   // 生成结果转换方法
              "generateDtoTypes": true,
              "operationGenerationMode": "MultipleClientsFromFirstTagAndOperationId", // 根据控制器和HTTP请求方法生成多客户端
              "markOptionalProperties": true,
              "generateCloneMethod": true,
              "typeStyle": "Class",
              "enumStyle": "Enum",
              "useLeafType": false,
              "classTypes": [],
              "extendedClasses": [],
              "extensionCode": "import { AbpServiceBase } from './AbpServiceBase' /n/r import { ServiceProxyConfig } from './ServiceProxyConfig'", // 生成是导入的代码
              "generateDefaultValues": true,
              "excludedTypeNames": [],
              "excludedParameterNames": [],
              "handleReferences": false,
              "generateTypeCheckFunctions": false,
              "generateConstructorInterface": false,
              "convertConstructorInterfaceData": false,
              "importRequiredTypes": true,
              "useGetBaseUrlMethod": true,
              "baseUrlTokenName": "API_BASE_URL",
              "queryNullValue": "",
              "useAbortSignal": false,
              "inlineNamedDictionaries": false,
              "inlineNamedAny": false,
              "includeHttpContext": false,
              "templateDirectory": "./Templates",
              "typeNameGeneratorType": null,
              "propertyNameGeneratorType": null,
              "enumNameGeneratorType": null,
              "serviceHost": null,
              "serviceSchemes": null,
              "output": "./src/api/abp-service-proxies.ts", // 生成的代码要存放的文件
              "newLineBehavior": "Auto"
            }
          }
        }
      

获取服务器OpenId配置

访问地址 http://服务器IP:服务器端口/.well-known/openid-configuration 可获得OpenId配置

{
    // openid 服务地址
    "issuer": "https://localhost:7027/",
    // 授权端口
    "authorization_endpoint": "https://localhost:7027/connect/authorize",
    // 令牌端口
    "token_endpoint": "https://localhost:7027/connect/token",
    // 
    "introspection_endpoint": "https://localhost:7027/connect/introspect",
    // 退出端口
    "end_session_endpoint": "https://localhost:7027/connect/logout",
    "revocation_endpoint": "https://localhost:7027/connect/revocat",
    // 用户信息端口
    "userinfo_endpoint": "https://localhost:7027/connect/userinfo",
    "device_authorization_endpoint": "https://localhost:7027/device",
    "jwks_uri": "https://localhost:7027/.well-known/jwks",
    "grant_types_supported": [
        "authorization_code",
        "implicit",
        "password",
        "client_credentials",
        "refresh_token",
        "urn:ietf:params:oauth:grant-type:device_code"
    ],
    "response_types_supported": [
        "code",
        "code id_token",
        "code id_token token",
        "code token",
        "id_token",
        "id_token token",
        "token",
        "none"
    ],
    "response_modes_supported": [
        "form_post",
        "fragment",
        "query"
    ],
    "scopes_supported": [
        "openid",
        "offline_access",
        "email",
        "profile",
        "phone",
        "roles",
        "address"
    ],
    "claims_supported": [
        "aud",
        "exp",
        "iat",
        "iss",
        "sub"
    ],
    "id_token_signing_alg_values_supported": [
        "RS256"
    ],
    "code_challenge_methods_supported": [
        "S256"
    ],
    "subject_types_supported": [
        "public"
    ],
    "token_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post"
    ],
    "introspection_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post"
    ],
    "revocation_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post"
    ],
    "claims_parameter_supported": false,
    "request_parameter_supported": false,
    "request_uri_parameter_supported": false
}

定义OpenId配置

export class OpenidConfiguration {
    issuer?: string;
    authorizationEndpoint?: string;
    tokenEndpoint?: string;
    introspectionEndpoint?: string;
    endSessionEndpoint?: string;
    revocationEndpoint?: string;
    userinfoEndpoint?: string;
    deviceAuthorizationEndpoint?: string;
    jwksUri?: string;
    grantTypesSupported?: Array<string>;
    responseTypesSupported?: Array<string>;
    responseModesSupported?: Array<string>;
    scopesSupported?: Array<string>;
    claimsSupported?: Array<string>;
    idTokenSigningAlgValuesSupported?: Array<string>;
    codeChallengeMethodsSupported?: Array<string>;
    subjectTypesSupported?: Array<string>;
    tokenEndpointAuthMethodsSupported?: Array<string>;
    introspectionEndpointAuthMethodsSupported?: Array<string>;
    revocationEndpointAuthMethodsSupported?: Array<string>;
    claimsParameterSupported?: boolean;
    requestParameterSupported?: boolean;
    requesturiParameterSupported?: boolean;

    init (_data?: any) {
      if (_data) {
        this.issuer = _data.issuer
        this.authorizationEndpoint = _data.authorization_endpoint
        this.tokenEndpoint = _data.token_endpoint
        this.introspectionEndpoint = _data.introspection_endpoint
        this.endSessionEndpoint = _data.end_session_endpoint
        this.revocationEndpoint = _data.revocation_endpoint
        this.userinfoEndpoint = _data.userinfo_endpoint
        this.deviceAuthorizationEndpoint = _data.device_authorization_endpoint
        this.jwksUri = _data.jwks_uri
        this.grantTypesSupported = _data.grant_types_supported
        this.responseTypesSupported = _data.response_types_supported
        this.responseModesSupported = _data.response_modes_supported
        this.scopesSupported = _data.scopes_supported
        this.claimsSupported = _data.claims_supported
        this.idTokenSigningAlgValuesSupported = _data.id_token_signing_alg_values_supported
        this.codeChallengeMethodsSupported = _data.code_challenge_methods_supported
        this.subjectTypesSupported = _data.subject_types_supported
        this.tokenEndpointAuthMethodsSupported = _data.token_endpoint_auth_methods_supported
        this.introspectionEndpointAuthMethodsSupported = _data.introspection_endpoint_auth_methods_supported
        this.revocationEndpointAuthMethodsSupported = _data.revocation_endpoint_auth_methods_supported
        this.claimsParameterSupported = _data.claims_parameter_supported
        this.requestParameterSupported = _data.request_parameter_supported
        this.requesturiParameterSupported = _data.request_uri_parameter_supported
      }
    }

    static fromJS (data: any): OpenidConfiguration {
      data = typeof data === 'object' ? data : {}
      const result = new OpenidConfiguration()
      result.init(data)
      return result
    }

    toJSON (data?: any) {
      data = typeof data === 'object' ? data : {}
      data.issuer = this.issuer
      data.authorization_endpoint = this.authorizationEndpoint
      data.token_endpoint = this.tokenEndpoint
      data.introspection_endpoint = this.introspectionEndpoint
      data.revocation_endpoint = this.revocationEndpoint
      data.userinfo_endpoint = this.userinfoEndpoint
      data.device_authorization_endpoint = this.deviceAuthorizationEndpoint
      data.jwks_uri = this.jwksUri
      data.grant_types_supported = this.grantTypesSupported
      data.response_types_supported = this.responseTypesSupported
      data.response_modes_supported = this.responseModesSupported
      data.scopes_supported = this.scopesSupported
      data.claims_supported = this.claimsSupported
      data.id_token_signing_alg_values_supported = this.idTokenSigningAlgValuesSupported
      data.code_challenge_methods_supported = this.codeChallengeMethodsSupported
      data.subject_types_supported = this.subjectTypesSupported
      data.token_endpoint_auth_methods_supported = this.tokenEndpointAuthMethodsSupported
      data.introspection_endpoint_auth_methods_supported = this.introspectionEndpointAuthMethodsSupported
      data.revocation_endpoint_auth_methods_supported = this.revocationEndpointAuthMethodsSupported
      data.claims_parameter_supported = this.claimsParameterSupported
      data.request_parameter_supported = this.requestParameterSupported
      data.request_uri_parameter_supported = this.requesturiParameterSupported
      return data
    }

    clone (): OpenidConfiguration {
      const json = this.toJSON()
      const result = new OpenidConfiguration()
      result.init(json)
      return result
    }
}

定义OpenId配置客户端

获取服务器OpenId配置

import { ApiException, RemoteServiceErrorResponse } from './abp-service-proxies'
import { AbpServiceBase, ServiceProxyConfig } from './AbpServiceBase'

export class OpenidConfigurationProxie extends AbpServiceBase {
  private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
  private baseUrl: string;
  protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;

  constructor (configuration: ServiceProxyConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
    super(configuration)
    this.http = http || window as any
    this.baseUrl = this.getBaseUrl('', baseUrl)
  }

  getOpenidConfiguration (): Promise<OpenidConfiguration> {
    let url_ = this.baseUrl + '/.well-known/openid-configuration'
    url_ = url_.replace(/[?&]$/, '')

    const options_: RequestInit = {
      method: 'GET'
    }

    return this.transformOptions(options_).then(transformedOptions_ => {
      return this.http.fetch(url_, transformedOptions_)
    }).then((_response: Response) => {
      return this.transformResult(url_, _response, (_response: Response) => this.processOpenidConfiguration(_response))
    })
  }

    /**
     * 处理响应
     */
  protected processOpenidConfiguration (response: Response): Promise<OpenidConfiguration> {
    const status = response.status
    const _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => { _headers[k] = v }) }
    if (status === 200) {
      return response.text().then((_responseText) => {
        let result200: any = null
        const resultData200 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver)
        result200 = OpenidConfiguration.fromJS(resultData200)
        return result200
      })
    } else if (status === 403) {
      return response.text().then((_responseText) => {
        let result403: any = null
        const resultData403 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver)
        result403 = RemoteServiceErrorResponse.fromJS(resultData403)
        return throwException('Forbidden', status, _responseText, _headers, result403)
      })
    } else if (status === 401) {
      return response.text().then((_responseText) => {
        let result401: any = null
        const resultData401 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver)
        result401 = RemoteServiceErrorResponse.fromJS(resultData401)
        return throwException('Unauthorized', status, _responseText, _headers, result401)
      })
    } else if (status === 400) {
      return response.text().then((_responseText) => {
        let result400: any = null
        const resultData400 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver)
        result400 = RemoteServiceErrorResponse.fromJS(resultData400)
        return throwException('Bad Request', status, _responseText, _headers, result400)
      })
    } else if (status === 404) {
      return response.text().then((_responseText) => {
        let result404: any = null
        const resultData404 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver)
        result404 = RemoteServiceErrorResponse.fromJS(resultData404)
        return throwException('Not Found', status, _responseText, _headers, result404)
      })
    } else if (status === 501) {
      return response.text().then((_responseText) => {
        let result501: any = null
        const resultData501 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver)
        result501 = RemoteServiceErrorResponse.fromJS(resultData501)
        return throwException('Server Error', status, _responseText, _headers, result501)
      })
    } else if (status === 500) {
      return response.text().then((_responseText) => {
        let result500: any = null
        const resultData500 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver)
        result500 = RemoteServiceErrorResponse.fromJS(resultData500)
        return throwException('Server Error', status, _responseText, _headers, result500)
      })
    } else if (status !== 200 && status !== 204) {
      return response.text().then((_responseText) => {
        return throwException('An unexpected server error occurred.', status, _responseText, _headers)
      })
    }
    return Promise.resolve<OpenidConfiguration>(null as any)
  }
}

function throwException (message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
  throw new ApiException(message, status, response, headers, result)
}

定义OpenId配置Store

用 pinia 管理OpenId配置,方便其他地方调用

import { OpenidConfiguration, OpenidConfigurationProxie } from '@/api/openid-configuration-proxies'
import { defineStore } from 'pinia'
import { ServiceProxyConfig } from '../api/AbpServiceBase'

export const useOpenidConfigurationStore = defineStore('openidConfigurationStore', {
  state: () => {
    return {
      openidConfiguration: {} as OpenidConfiguration,
      changeCallBack: (_data: OpenidConfiguration) => { /** */ }
    }
  },
  actions: {
    init () {
      const client = new OpenidConfigurationProxie(new ServiceProxyConfig())
      client.getOpenidConfiguration()
        .then(result => {
          this.openidConfiguration = result
          this.changeCallBack(result)
        })
    },
    refresh () {
      this.init()
    }
  }
})

获取到服务器配置后changeCallBack方法

定义openidPlugin

确保在使用OpenidConfigurationStore前先初始化,添加文件openidPlugin.ts,添加以下代码

import { useOpenidConfigurationStore } from '@/stores/OpenidConfigurationStore'

const openidPlugin = {
  install () {
    useOpenidConfigurationStore().init()
  }
}

export default openidPlugin

修改main.ts

  app.use(openidPlugin)

定义令牌获取客户端

// 令牌参数
const tokenParameter = {
  scope: 'profile offline_access StudyAbp openid', // 只有设置了 openid,在通过AuthorizationCode 获取token时才能获取到id_token
  reponse_type: 'code id_token token',
  client_id: 'StudyAbp_Vue_App', // 与服务端定义 身份认证应用程序Id 保存一直  OpenIddictDataSeedContributor.cs 数据种子的 CreateApplicationsAsync 方法定义的
  redirect_uri: 'http://localhost:8080', // 获取到授权code或用户退出后浏览器重定向的URI
  state: ''

}

export default tokenParameter
import { useOpenidConfigurationStore } from '@/stores/OpenidConfigurationStore'
import { Hellper, packageConfigerHellper } from '@/Utilsts/common'
import { ApiException, RemoteServiceErrorResponse, UserLoginInfo } from './abp-service-proxies'
import { AbpServiceBase, ServiceProxyConfig } from './AbpServiceBase'
import tokenParameter from './token-parameter'

export class TokenServiceProxie extends AbpServiceBase {
  private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
  private baseUrl: string;
  protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;

  constructor (configuration: ServiceProxyConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
    super(configuration)
    this.http = http || window as any
    this.baseUrl = this.getBaseUrl('', baseUrl)
  }

  /**
   * 获取授权Code
   */
  getAuthorizationCode () {
    // 访问授权端口
    let url_ = useOpenidConfigurationStore().openidConfiguration.authorizationEndpoint
    if (url_ === undefined) {
      throw new Error('请求地址不存在')
    }
    url_ = url_.replace(/[?&]$/, '')

    const parameter_ = Hellper.ToParameter({
      client_id: packageConfigerHellper.getClientId(),// 传入客户单端ID
      response_type: 'code',                        // 定义获取授权code
      redirect_uri: tokenParameter.redirect_uri,    // 定义获取到授权Code后浏览器重定向的网站地址
      scope: tokenParameter.scope,                  // 
      culture: this.config.getAcceptLanguage(),     //定义语言
      'ui-culture': this.config.getAcceptLanguage() // 定义Ui语言
    })

    url_ += '?' + parameter_
    window.open(url_, '_self')  // 访问获取授权code服务器,授权成功后浏览器重定向到 redirect_uri 地址
  }

  /**
   * 根据用户密码获取令牌
   */
  getTokenByPassword (body?: UserLoginInfo | undefined): Promise<TokenDataResult> {
    // 访问令牌端口
    let url_ = useOpenidConfigurationStore().openidConfiguration.tokenEndpoint
    if (url_ === undefined) {
      throw new Error('请求地址不存在')
    }
    url_ = url_.replace(/[?&]$/, '')

    // 定义传入参数
    const content_ = Hellper.ToParameter({
      ...body,
      username: body?.userNameOrEmailAddress,
      grant_type: 'password',                   // 表示根据用户名、密码获取令牌
      response_type: tokenParameter.reponse_type,
      client_id: tokenParameter.client_id,
      scope: tokenParameter.scope
    })
    const options_: RequestInit = {
      body: content_,
      method: 'POST',
      headers: { 'content-type': 'application/x-www-form-urlencoded' } // 必须定义此请求头,否则授权服务器无法识别传入参数
    }

    return this.transformOptions(options_).then(transformedOptions_ => {
      return this.http.fetch(url_ as string, transformedOptions_)
    }).then((_response: Response) => {
      return this.transformResult(url_ as string, _response, (_response: Response) => this.processToken(_response))
    })
  }

  /**
   * 根据授权code获取令牌
   */
  getTokenByAuthorizationCode (code: string) {
    // 访问令牌端口
    let url_ = useOpenidConfigurationStore().openidConfiguration.tokenEndpoint
    if (url_ === undefined) {
      throw new Error('请求地址不存在')
    }
    url_ = url_.replace(/[?&]$/, '')

    const content_ = Hellper.ToParameter({
      client_id: tokenParameter.client_id,
      grant_type: 'authorization_code',     // 表示根据授权code获取令牌
      response_type: 'code token id_token',
      redirect_uri: tokenParameter.redirect_uri,    // 重定向uri
      code: code                            // 传入授权code
    })

    const options_: RequestInit = {
      body: content_,
      method: 'POST',
      headers: { 'content-type': 'application/x-www-form-urlencoded' } // 必须定义此请求头,否则授权服务器无法识别传入参数
    }

    return this.transformOptions(options_).then(transformedOptions_ => {
      return this.http.fetch(url_ as string, transformedOptions_)
    }).then((_response: Response) => {
      return this.transformResult(url_ as string, _response, (_response: Response) => this.processToken(_response))
    })
  }
  /**
   * 根据id令牌退出登录
   */
  logoutByIdToken (idToken?: string) {
    // 访问 退出端口
    let url_ = useOpenidConfigurationStore().openidConfiguration.endSessionEndpoint
    if (url_ === undefined) {
      throw new Error('请求地址不存在')
    }
    url_ = url_.replace(/[?&]$/, '')

    const content_ = Hellper.ToParameter({
      id_token_hint: idToken,
      post_logout_redirect_uri: encodeURIComponent(this.getClientUrl('')),
      culture: this.config.getAcceptLanguage(),
      'ui-culture': this.config.getAcceptLanguage()
    })

    url_ += '?' + content_
    window.open(url_, 'self')
  }

  protected processToken (response: Response): Promise<TokenDataResult> {
    ...
  }
}

/**
 * 授权数据结果
 */
export class TokenDataResult {
  accessToken?: string;
  tokenType?: string | undefined;
  expiresIn?: number | undefined;
  refreshToken?: string | undefined;
  'id_token'?: string|undefined

  init (_data?: any) {
    if (_data) {
      this.accessToken = _data.access_token
      this.tokenType = _data.token_type
      this.expiresIn = _data.expires_in
      this.refreshToken = _data.refresh_token
      this.id_token = _data.id_token
    }
  }

  static fromJS (data: any): TokenDataResult {
    data = typeof data === 'object' ? data : {}
    const result = new TokenDataResult()
    result.init(data)
    return result
  }

  toJSON (data?: any) {
    data = typeof data === 'object' ? data : {}
    data.access_token = this.accessToken
    data.token_type = this.tokenType
    data.expires_in = this.expiresIn
    data.refresh_token = this.refreshToken
    data.id_token = this.id_token
    return data
  }

  clone (): TokenDataResult {
    const json = this.toJSON()
    const result = new TokenDataResult()
    result.init(json)
    return result
  }
}

function throwException (message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
  throw new ApiException(message, status, response, headers, result)
}
export class Hellper {
  static ToParameter (data: any) : string {
    let value = ''
    Object.keys(data).forEach(p => {
      if (value && value.length > 0) {
        value += '&'
      }

      value += (p + '=' + data[p])
    })
    return value
  }

  static getUrlParamValue (url :string, name: string) {
    return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(url) || ['', ''])[1].replace(/\+/g, '%20')) || undefined
  }
}

定义令牌Store

用 pinia 管理令牌,方便其他地方调用

import { UserLoginInfo } from '@/api/abp-service-proxies'
import { TokenDataResult, TokenServiceProxie, UserInfo } from '@/api/token-service-proxies'
import { defineStore } from 'pinia'
import { useApplicationConfigurationStore } from './ApplicationConfiguration'
import { ServiceProxyConfig } from '@/api/ServiceProxyConfig'

export const useTokenStore = defineStore('tokenStore', {
  state: () => {
    return {
      token: {} as TokenDataResult,
      UserInfo: {} as UserInfo
    }
  },
  getters: {
    /**
     * 授权令牌
     */
    accessToken: (state) => state.token.tokenType + ' ' + state.token.accessToken
  },
  actions: {
    loginByPassword (body?: UserLoginInfo): Promise<void> {
      const client = new TokenServiceProxie(new ServiceProxyConfig())
      return client.getTokenByPassword(body)
        .then(result => {
          this.token = result

          useApplicationConfigurationStore().resetApplicationConfiguration()
        })
    },
    loginByAuthorizationCode (code: string): Promise<void> {
      const client = new TokenServiceProxie(new ServiceProxyConfig())
      return client.getTokenByAuthorizationCode(code)
        .then(result => {
          this.token = result
          if (this.token) {
            useApplicationConfigurationStore().resetApplicationConfiguration()
          }
        })
    },
    getUserInfo (): Promise<void> {
      const client = new TokenServiceProxie(new ServiceProxyConfig())
      return client.getUserInfo()
        .then(result => {
          this.UserInfo = result
        })
    },
    logoutByIdToken () : Promise<any> {
      const client = new TokenServiceProxie(new ServiceProxyConfig())
      client.logoutByIdToken(this.token.id_token)
      return Promise.resolve({})
    },
    logout () {
      this.token = new TokenDataResult()
    },
    refreshToken () {
      //
    }
  }
})

修改ServiceProxyConfiggetAuthorization方法,从TokenStore中获取用户的授权令牌

  /**
   * 返回一个用于请求头 Authorization 的有效值
   * 用于动态注入当前认证头
   */
  public getAuthorization (): string {
    return useTokenStore()?.accessToken
  }

登录

使用authorizationCode登录

代码为:

new TokenServiceProxie(new ServiceProxyConfig()).getAuthorizationCode()

此时页面跳转到服务器提供的授权界面。

授权成功后页面跳转到我们提供的重定向界面,重定向界面的Url包含授权code

使用授权code获取授权令牌,修改app.vue,添加以下代码:

  mounted () {
    const code = Hellper.getUrlParamValue(location.href, 'code') || ''
    if (code) {
      this.$cookies.set(ConstantKey.AuthorizationCodeKey, code)
      location.href = packageConfigerHellper.getClientUrl()
    }
  },

如果请求地址code参数,则将code保存进cookie中,并重新加载客户端

添加authorizationCodeLoginPlugin插件,在插件安装时从cookie中获取授权code,如果有则用该code获取授权令牌。由于该授权code已从服务器中获取过令牌,所以该code已无效,所有还要从cookie中删除该授权code

import { useOpenidConfigurationStore } from '@/stores/OpenidConfigurationStore'
import { useTokenStore } from '@/stores/TokenStore'
import { ConstantKey } from '@/Utilsts/common'
import Cookies from 'vue-cookies'

const authorizationCodeLoginPlugin = {
  install () {
    const cookies: any = Cookies
    const code = cookies.get(ConstantKey.AuthorizationCodeKey) || ''
    if (code) {
      // 设置 OpenidConfigurationStore的 changeCallBack 方法,在成功获取到服务器配置后执行获取授权令牌代码
      useOpenidConfigurationStore().changeCallBack = () => {
        const store = useTokenStore()
        if (!store.token || !store.token.accessToken) {
          useTokenStore()
            .loginByAuthorizationCode(code)
            .then(() => {
              cookies.set(ConstantKey.AuthorizationCodeKey, '')
            })
        }
      }
    }
  }
}

export default authorizationCodeLoginPlugin

main.ts中使用插件

app.use(authorizationCodeLoginPlugin)

app.use(openidPlugin)前调用

如此客户端就获得了授权令牌

授权令牌持久化

由于授权令牌是保存在TokenStore中,所以当页面刷新时,TokenStore中保存的授权令牌就丢失了。为了避免这种情况发生,需要将令牌进行持久化。

添加文件Plugins\tokenPiniaStorePlugin.ts,定义pinia插件:

import { TokenDataResult, UserInfo } from '@/api/token-service-proxies'
import { PiniaPluginContext, StateTree, _ActionsTree, _GettersTree } from 'pinia'

export function tokenPiniaStorePlugin (context: PiniaPluginContext<string, StateTree, _GettersTree<StateTree>, _ActionsTree>) {
  if (context.store.$id !== 'tokenStore') {
    return
  }

  // 从 sessionStorage 中获取 sotre json字符串初始化store
  context.store.$patch((state) => {
    const strTokenStore = sessionStorage.getItem('tokenStore')
    if (strTokenStore) {
      const data = JSON.parse(strTokenStore)
      if (data) {
        state.token = TokenDataResult.fromJS(data.token)
        state.UserInfo = UserInfo.fromJS(data.UserInfo)
      }
    }
  })

  // 当store中的值改变时执行
  context.store.$subscribe((mutation, state) => {
    console.log('context.store.$subscribe', mutation)
    sessionStorage.setItem('tokenStore', JSON.stringify(state))
  })
}

以上代码从sessionStorage 中获取 sotre json字符串初始化tokenStore,并在 tokenStore的值改变时将tokenStore保存进sessionStorage

main.ts中使用插件

pinia.use(tokenPiniaStorePlugin)

tokenPiniaStorePlugin插件为pinia插件,所以由pinia安装

以上完成用AuthorizationCode获取授权令牌的功能。

posted @ 2023-04-28 18:14  $("#阿飞")  阅读(27)  评论(0编辑  收藏  举报