angular 接入 IdentityServer4
angular 接入 IdentityServer4
Intro
最近把活动室预约的项目做了一个升级,预约活动室需要登录才能预约,并用 IdentityServer4 做了一个统一的登录注册中心,这样以后就可以把其他的需要用户操作的应用统一到 IdentityServer 这里,这样就不需要在每个应用里都做一套用户的机制,接入 IdentityServer 就可以了。
目前活动室预约的服务器端和基于 angular 的客户端已经完成了 IdentityServer 的接入,并增加了用户的相关的一些功能,比如用户可以查看自己的预约记录并且可以取消自己未开始的预约,
还有一个小程序版的客户端暂时还未完成接入,所以小程序版目前暂时是不能够预约的
为什么要写这篇文章
目前在网上看到很多都是基于 implicit 模式接入 IdentityServer,这样实现起来很简单,但是现在 OAuth 已经不推荐这样做了,OAuth 推荐使用 code 模式来代替 implicit
implicit 模式会有一些安全风险,implicit 模式会将 accessToken 直接返回到客户端,而 code 模式只是会返回一个 code,accessToken 和 code 的分离的两步,implicit 模式很有可能会将 token 泄露出去
详细可以参考 StackOverflow 上的这个问答
除此之外,还有一个小原因,大多是直接基于 oidc-client 的 一个 npm 包来实现的,我是用了一个针对 angular 封装的一个库 angular-oauth2-oidc,如果你在用 angular ,建议你可以尝试一下,针对 angular 做了一些封装和优化,对 angular 更友好一些
准备接入吧
API 配置
预约系统的 API 和网站管理系统是在一起的,针对需要登录才能访问的 API 单独设置了的 policy 访问
services.AddAuthentication()
.AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Authority = Configuration["Authorization:Authority"];
options.RequireHttpsMetadata = false;
options.NameClaimType = "name";
options.RoleClaimType = "role";
})
;
services.AddAuthorization(options =>
{
options.AddPolicy("ReservationApi", builder => builder
.AddAuthenticationSchemes(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireScope("ReservationApi")
);
});
需要授权才能访问的接口设置 Authorize 并指定 Policy 为 ReservationApi
[Authorize(Policy = "ReservationApi")]
[HttpPost]
public async Task<IActionResult> MakeReservation([FromBody] ReservationViewModel model)
IdentityServer Client 配置
首先我们需要在 IdentityServer 这边添加一个客户端,因为我们要使用 code 模式,所以授权类型需要配置 authorization-code 模式,不使用 implicit 模式
允许的作用域(scope) 是客户端允许访问的 api 资源和用户的信息资源,openid 必选,profile 是默认的用户基本信息的集合,根据自己客户端的需要进行配置,ReservationApi 是访问 API 需要的 scope,其他的 scope 根据客户端需要进行配置


angular 客户端配置
安装 angular-oauth2-oidc npm 包,我现在使用的是 9.2.0 版本
添加 oidc 配置:
export const authCodeFlowConfig: AuthConfig = {
issuer: 'https://id.weihanli.xyz',
// URL of the SPA to redirect the user to after login
redirectUri: window.location.origin + '/account/callback',
clientId: 'reservation-angular-client',
dummyClientSecret: 'f6f1f917-0899-ef36-63c8-84728f411e7c',
responseType: 'code',
scope: 'openid profile ReservationApi offline_access',
useSilentRefresh: false,
showDebugInformation: true,
sessionChecksEnabled: true,
timeoutFactor: 0.01,
// disablePKCI: true,
clearHashAfterLogin: false
};
在 app.module 引入 oauth 配置
imports: [
BrowserModule,
AppRoutingModule,
AppMaterialModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
BrowserAnimationsModule,
OAuthModule.forRoot({
resourceServer: {
allowedUrls: ['https://reservation.weihanli.xyz/api'],
sendAccessToken: true
}
})
]
OAuthModule 里 resourceServer 中的 allowedUrls 是配置的资源的地址,访问的资源符合这个地址时就会自动发送 accessToken,这样就不需要自己实现一个 interceptor 来实现自动在请求头中设置 accessToken 了
在 AppComponment 的构造器中初始化 oauth 配置,并加载 ids 的发现文档
export class AppComponent {
constructor(
private oauth: OAuthService
) {
this.oauth.configure(authConfig.authCodeFlowConfig);
this.oauth.loadDiscoveryDocument();
}
// ...
}
添加一个 AuthGuard,路由守卫,需要登录才能访问的页面自动跳转到 /account/login 自动登录
AuthGuard:
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { OAuthService } from 'angular-oauth2-oidc';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router, private oauthService: OAuthService) {}
canActivate() {
if (this.oauthService.hasValidAccessToken()) {
return true;
} else {
this.router.navigate(['/account/login']);
return false;
}
}
}
路由配置:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ReservationListComponent } from './reservation/reservation-list/reservation-list.component';
import { NoticeListComponent } from './notice/notice-list/notice-list.component';
import { NoticeDetailComponent } from './notice/notice-detail/notice-detail.component';
import { AboutComponent } from './about/about.component';
import { NewReservationComponent } from './reservation/new-reservation/new-reservation.component';
import { LoginComponent } from './account/login/login.component';
import { AuthGuard } from './shared/auth.guard';
import { AuthCallbackComponent } from './account/auth-callback/auth-callback.component';
import { MyReservationComponent } from './account/my-reservation/my-reservation.component';
const routes: Routes = [
{ path: '', component: ReservationListComponent },
{ path: 'reservations/new', component:NewReservationComponent, canActivate: [AuthGuard] },
{ path: 'reservations', component: ReservationListComponent },
{ path: 'notice', component: NoticeListComponent },
{ path: 'notice/:noticePath', component: NoticeDetailComponent },
{ path: 'about', component: AboutComponent },
{ path: 'account/login', component: LoginComponent },
{ path: 'account/callback', component: AuthCallbackComponent },
{ path: 'account/reservations', component: MyReservationComponent, canActivate: [AuthGuard] },
{ path: '**', redirectTo: '/'}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
AccountLogin 会将用户引导到 ids 进行登录,登录之后会跳转到配置的重定向 url,我配置的是 account/callback
import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.less']
})
export class LoginComponent implements OnInit {
constructor(private oauthService: OAuthService) {
}
ngOnInit(): void {
// 登录
this.oauthService.initLoginFlow();
}
}
Auth-Callback
import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { Router } from '@angular/router';
@Component({
selector: 'app-auth-callback',
templateUrl: './auth-callback.component.html',
styleUrls: ['./auth-callback.component.less']
})
export class AuthCallbackComponent implements OnInit {
constructor(private oauthService: OAuthService, private router:Router) {
}
ngOnInit(): void {
this.oauthService.loadDiscoveryDocumentAndTryLogin()
.then(_=> {
this.oauthService.loadUserProfile().then(x=>{
this.router.navigate(['/reservations/new']);
});
});
}
}
More
当前实现还不太完善,重定向现在始终是跳转到的新预约的页面,应当在跳转登录之前记录一下当前的地址保存在 storage 中,在 auth-callback 里登录成功之后跳转到 storage 中之前的地址
Reference
- https://sunnycoding.cn/2020/03/14/angular-spa-auth-with-ocelot-and-ids4-part3/#i-2
- https://github.com/OpenReservation/angular-client
- https://github.com/manfredsteyer/angular-oauth2-oidc/
- https://github.com/OpenReservation/ReservationServer
angular 接入 IdentityServer4的更多相关文章
- ASP.NET Core Swagger接入使用IdentityServer4 的 WebApi
写在前面 是这样的,我们现在接口使用了Ocelot做网关,Ocelot里面集成了基于IdentityServer4开发的授权中心用于对Api资源的保护.问题来了,我们的Api用了SwaggerUI做接 ...
- Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(一)
好吧,这个题目我也想了很久,不知道如何用最简单的几个字来概括这篇文章,原本打算取名<Angular单页面应用基于Ocelot API网关与IdentityServer4+ASP.NET Iden ...
- 每周.NET前沿技术文章摘要(2017-06-21)
汇总国外.NET社区相关文章,覆盖.NET ,ASP.NET等内容: .NET .NET Core Magic: Develop on one OS, run on another 链接:https: ...
- 每周.NET前沿技术文章摘要(2017-05-10)
汇总国内外.NET社区相关文章,覆盖.NET ,ASP.NET和Docker容器三个方面的内容: .NET Debugging .NET core with SOS everywhere 链接:htt ...
- Ocelot(四)- 认证与授权
Ocelot(四)- 认证与授权 作者:markjiang7m2 原文地址:https://www.cnblogs.com/markjiang7m2/p/10932805.html 源码地址:http ...
- .NETCore微服务探寻(二) - 认证与授权
前言 一直以来对于.NETCore微服务相关的技术栈都处于一个浅尝辄止的了解阶段,在现实工作中也对于微服务也一直没有使用的业务环境,所以一直也没有整合过一个完整的基于.NETCore技术栈的微服务项目 ...
- IdentityServer4密码模式接入现有用户数据表
具体接入identityserver请看文档,这里只简单列举部分步骤 1.创建一个web项目,引入Identityserver4的nuget包 2.新建一个类,实现IResourceOwnerPass ...
- Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(四)
在上一讲中,我们已经完成了一个完整的案例,在这个案例中,我们可以通过Angular单页面应用(SPA)进行登录,然后通过后端的Ocelot API网关整合IdentityServer4完成身份认证.在 ...
- Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(二)
上文已经介绍了Identity Service的实现过程.今天我们继续,实现一个简单的Weather API和一个基于Ocelot的API网关. 回顾 <Angular SPA基于Ocelot ...
随机推荐
- (板子)缩点 + DAG上的DP(深搜)luogu P3387
板子传送门 根据题目意思,我们只需要找出一条点权最大的路径就行了,不限制点的个数.那么考虑对于一个环上的点被选择了,一整条环是不是应该都被选择,这一定很优,能选干嘛不选.很关键的是题目还允许我们重复经 ...
- 面试官问我会不会Elasticsearch,我语塞了...
少点代码,多点头发 本文已经收录至我的GitHub,欢迎大家踊跃star 和 issues. https://github.com/midou-tech/articles 从今天开始准备给大家带来全新 ...
- .NetCore3.1中的WebApi如何配置跨域
写法 一: 1. 打开Startup.cs,定义静态变量Any,用以配置跨域. private readonly string Any = "Any"; 2. 在Configure ...
- Java实现 蓝桥杯VIP 算法训练 数的划分
[题目描述] 将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序). 例如:n=7,k=3,下面三种分法被认为是相同的. 1,1,5: 1,5,1: 5,1,1: 问有多少种不同的分法. ...
- Java实现 LeetCode 689 三个无重叠子数组的最大和(换方向筛选)
689. 三个无重叠子数组的最大和 给定数组 nums 由正整数组成,找到三个互不重叠的子数组的最大和. 每个子数组的长度为k,我们要使这3*k个项的和最大化. 返回每个区间起始索引的列表(索引从 0 ...
- Java实现 蓝桥杯 算法提高 周期字串
算法提高 周期字串 时间限制:1.0s 内存限制:256.0MB 问题描述 右右喜欢听故事,但是右右的妈妈总是讲一些"从前有座山,山里有座庙,庙里有个老和尚给小和尚讲故事,讲的什么呢?从前有 ...
- Java实现 蓝桥杯VIP 基础练习 芯片测试
问题描述 有n(2≤n≤20)块芯片,有好有坏,已知好芯片比坏芯片多. 每个芯片都能用来测试其他芯片.用好芯片测试其他芯片时,能正确给出被测试芯片是好还是坏.而用坏芯片测试其他芯片时,会随机给出好或是 ...
- Java实现 LeetCode 440 字典序的第K小数字
440. 字典序的第K小数字 给定整数 n 和 k,找到 1 到 n 中字典序第 k 小的数字. 注意:1 ≤ k ≤ n ≤ 109. 示例 : 输入: n: 13 k: 2 输出: 10 解释: ...
- Android中如何使用自定义对话框
自定义创建一个XML布局 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns ...
- Java实现第十届蓝桥杯等差数列
试题 I: 等差数列 时间限制: 1.0s 内存限制: 512.0MB 本题总分:25 分 [问题描述] 数学老师给小明出了一道等差数列求和的题目.但是粗心的小明忘记了一 部分的数列,只记得其中 N ...