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()
}
}
})
定义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 () {
//
}
}
})
修改ServiceProxyConfig
的getAuthorization
方法,从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) {
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)
如此客户端就获得了授权令牌
授权令牌持久化
由于授权令牌是保存在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
获取授权令牌的功能。