相关的概念解释
- 单点登录
当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,SSO包括统一的登录和统一的登出这两部分。基于OIDC实现的SSO主要是利用OIDC服务作为用户认证中心作为统一入口,使得所有的需要登录的地方都交给OIDC服务来做。更直白点说就是把需要进行用户认证的客户端中的用户认证这部分都剥离出来交给OIDC认证中心来做。
- token
就是在用户名密码登陆后,后台生成一个token,然后客户端保存token,考虑到token的通用性和广泛性,因此慢慢的就有了一些token定义的规范,一个目前比较通用的token规范就是jwt。
- jwt
是json web token的简称,标准的jwt格式token包含了三段,分别是token头、token主题和token签名,jwt只是处理token规范的问题
- OAuth2.0
授权码模式,OAuth2.0解决的是通过令牌获取某个系统的操作权限,因为有clientId的标识,一次登陆只能对该系统生效,第三方应用的操作用户不是鉴权系统的官方用户,授权权限鉴权中心可以做限制。
标准的oauth2的token同样是遵循jwt标准,不同的是,在普通jwt的token基础上加入了更多的内容。
oauth2标准的token有两个:"access_token和refresh_token"。
access_token用来访问资源时授权,包含基础授权信息,refresh_token的作用是在access_token失效后直接续签access_token。
举例子:微博就是客户端,QQ就是认证服务器,
OAuth2.0就是客户端和认证服务器之间由于相互不信任而产生的一个授权协议
- 第一步:在微博官网点击用qq登录
- 第二步:跳转到qq登录页面输入用户名密码,然后点授权并登录
- 第三步:跳回到微博页面,成功登录
首先接上一步,QQ服务器在判断登录成功后,使页面重定向到之前微博发来的callback并附上code授权码,即 callback=www.weibo.com/callback
页面接到重定向,发起 http://www.weibo.com/callback 请求
微博服务器收到请求后,再次与QQ沟通,即模拟浏览器发起了两次请求。一个是用拿到的Authorization Code获取Access Token,另一个就是用拿到的token换取用户信息。最后将用户信息储存起来,返回给浏览器其首页的视图。到此OAuth2.0授权结束。
- oidc
这个技术解决的就是外部第三方交互时的一个身份认证问题。
结合上边所说,认证和授权(authentication 和 authorization)分别属于不同的范畴,解决的也都是不同的问题,因此在这种涉及到第三方,如果再涉及到资源权限的系统中,就会引入一个新的规范,即oidc。
OIDC=(Identity, Authentication) + oauth2,就是openid+oauth2。
具体理解: OIDC在oauth2的基础上又加了一个token,叫做id_token。
id_token同样的遵循jwt规范,只不过里边的内容是能够体现用户身份的信息,OIDC里就会有三个token:access_token、refresh_token和id_token
Angular具体项目的使用
首先angular项目使用到oidc-client第三方库
前端的应用流程
-
访问前端地址, 如果没有登录用户, 那么跳转到Authorization Server进行登陆, 同意后, 返回到前端的网站.
-
如果前端网站有登录的用户, 那么在用户快过期的时候自动刷新token. 以免登陆过期.
-
前端应用访问api时, 自动拦截所有请求, 把登陆用户的access token添加到请求的authorization header, 然后再发送给 web api.
所以这将涉及到AuthService, Auth-token.interceptor,AuthGuard等文件的使用
// 创建AuthService
import { Injectable, NgZone } from '@angular/core';
import { User, UserManager, WebStorageStateStore } from 'oidc-client';
import { ENVIRONMENT } from '@env/environment';
import { SessionStorageService } from '@shared/services';
@Injectable()
export class AuthService {
public isLoggedIn: boolean = false;
public manager: UserManager; // UserManager就是oidc-client里面的东西. 我们主要是用它来操作.
public accessToken: string = '';
public currentUser: User;
constructor(private _ngZone: NgZone, private _session: SessionStorageService) {
const origin = `${window.location.protocol}//${window.location.hostname}${window.location.port ?
(':' + window.location.port) : ''}`;
const settings = {
authority: '',// 就是authorization server(上述的QQ)的地址.
client_id: '' // 发起认证请求的客户端的唯一标识,这个客户端事先已经在oidc-server.dev这个站点注册过了,
response_type: 'id_token token'// 区别于oauth2授权请求的一点,必须包含有id_token这一项,
scope: '' //区别于oauth2授权请求的一点,必须包含有openid这一项,
redirect_uri: '',// 是登陆成功后跳转回来的地址
post_logout_redirect_uri: origin, // OIDC / OAuth2注销后重定向UR
staleStateAge: ,// 一个数字(以秒为单位),指示用于授权被认为已放弃并可以清除的请求的状态的存储状态
automaticSilentRenew: boolean, //为true是启用自动安静刷新token.
};
this._ngZone.runOutsideAngular(() => {
this.manager = new UserManager(settings);
});
}
public login(): void {
// 方法里面的signInRedirect()会直接跳转到Authorization Server的登陆窗口
this.manager.signinRedirect();
}
public async getUser(): Promise<User> {
const user = await this.manager.getUser();
this.currentUser = user;
if (user) {
this.accessToken = user.access_token;
this.isLoggedIn = true;
}
return user;
}
public async signinRedirectCallback(): Promise<void> {
const user = await this.manager.signinRedirectCallback();
}
public getToken(): string {
return this.accessToken;
}
public logout(): void {
this.isLoggedIn = false;
this._session.clearAll();
localStorage.clear();
// 里的signoutRedirect()就会跳转到AuthorizationServer并执行登出.
this.manager.signoutRedirect({id_token_hint: this.currentUser.id_token});
}
}
// 创建access-token.interceptor
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs';
@Injectable()
export class AuthTokenInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) { }
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authToken = `Bearer ${this.auth.getToken()}`;
let req;
req = request.clone({
setHeaders: {
Authorization: authToken,
},
});
return next.handle(req);
}
}
// 创建auth-guard文件
import { CanActivate, CanLoad, Route, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './auth.service';
import { Injectable } from '@angular/core';
@Injectable()
export class AuthGuard implements CanLoad, CanActivate {
constructor(private auth: AuthService) { }
// 如果返回true就可以访问这个路由, 否则就不可以访问
// 取当前用户, 如果有用户那么就可以继续访问路由, 否走执行登陆动作.
public async canLoad(route: Route): Promise<boolean> {
const user = await this.auth.getUser();
const isLoggedIn = !!user;
if (!isLoggedIn) {
this.auth.login();
return false;
}
return true;
}
public async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
const user = await this.auth.getUser();
const isLoggedIn = !!user;
if (!isLoggedIn) {
this.auth.login();
return false;
}
return true;
}
}
自动刷新Token:
oidc-client的自动刷新token是只要配置好了, 你就不用再做什么操作了.
不过还是需要建立一个页面, 用于刷新:
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>test</title>
</head>
<body>
<p>登录成功,正在跳转页面……</p>
<script src="/assets/oidc/oidc-client.js"></script>
<script>
var mgr = new Oidc.UserManager();
mgr.signinRedirectCallback().then(...);
</script>
</body>
</html>
网友评论