IdentityServer4之Implicit和纯前端好像很配哦

理解和运用 ClassLoader 该篇文章就够了

前言

上一篇Resource Owner Password Credentials模式虽然有用户参与,但对于非信任的第三方的来说,使用这种模式是有风险的,所以相对用的不多;这里接着说说implicit隐式模式,这种模式比较适合于纯前端客户端,比如Vue、Angular、React项目等,相对来说整个流程比较安全,只需在认证服务器进行认证即可,无需在客户端进行相关隐私信息录入,但前提是要客户端保证安全,不然容易被别人钓鱼(安全很重要),那IdentityServer4不背这个锅。

正文

既然有用户参与,交互肯定少不了,认证那更不能缺少,所以这里使用OIDC(OpenID Connect)的协议进行用户身份验证;别跑,不管是客户端还是认证服务器端,已经都封装好了,拿过来直接用即可;

从流程先了解一下,直接上图:

流程简要说明:

  1. 用户通过浏览器(User-Agent)访问第三方客户端(Client);
  2. 如果客户端需要进行认证授权,则将其重定向到认证服务器(Authorization server);
  3. 用户(ResorceOwner)在认证服务器上进行认证;
  4. 认证服务器(Authorization server)验证成功之后,返回AccessToken和Id Token客户端;
  5. 后续用户再操作客户端的时候,若需访问保护资源,直接在请求中带上AccessToken即可;
  6. 最终资源服务器(Resource server)返回对应信息;

术语解释:

  • User-Agent:这里指就是浏览器;如果客户端是Web,就需要用户通过浏览器访问客户端,所以这里的浏览器就是User-Agent;
  • Id Token:标识用户身份的Token,这是OpenID Connect带上的。

知道大概的流程,接下来就通过代码实践的方式演示一把。

1.拷贝上一节Reource Owner Password的授权服务器和资源服务器代码并完善;

对于资源服务器来说,这里先暂时不用修改其他;

认证服务器增加界面支持

而认证服务器需要有自己的页面,因为在访问客户端时会重定向到认证服务器进行认证信息录入和授权相关操作,那页面需要自己写吗,不需要的,IdentityServer4模板中已经准备好了,如下操作:

通过以上两种方式都可以获取到以下文件,将其拷贝到认证服务器项目中,如下:

由于之前的认证服务器只是单纯API项目,不支持MVC,因为增加的界面是MVC页面,所以需要在Startup.cs中增加MVC的支持,页面中需要样式文件的加载,则同时需要增加静态文件处理,如下:

运行起来看效果,如下:

备案客户端

上面界面没问题了,这里打算做一个纯前端(Vue)的客户端进行测试,所以需要在认证服务器中提前将客户端进行备案,如下:

2. 增加纯前端客户端(Vue)

这里不打算使用脚手架工具去生成项目,为了不喧宾夺主,这里就很单纯的写html和Js,只是在其中引用Vue,用Vue的思路进行编码演示;

既然是个纯前端项目,得要有个服务器作为宿主,然后通过URL地址访问页面,这里不想用其他比较重的服务器,live-server简单好用,就用它了,简单搭建一下环境:

环境准备

我是用npm安装,所以需要安装nodejs,这里就不详细扩展了,具体安装步骤详见:https://www.runoob.com/nodejs/nodejs-install-setup.html;

有了nodejs环境,直接npm安装live-server,如下命令:

npm install -g live-server ###全局安装live-server,后续都可以用

安装完就可以直接使用了,直接在指定目录下执行live-server即可,默认端口是8080;

创建纯前端客户端

项目目录结构如下:

简要说明:

  • js目录:存放依赖的js;

    live-server-https目录是提供live-server启动时提供live-server-https支持,其实项目本身并不需要;后续如果需要将前端页面指定https访问,可以指定此库运行;

    oidc-client.js/oidc-client.min.js是用于前端客户端实现OpenID Connect,引入直接使用即可,也可以自行在网上下载(https://github.com/IdentityModel/oidc-client-js);

    vue.js提供vue的支持,虽然没用脚手架,但用vue还是没问题;

    作为一款内存数据库,为什么断电后Redis数据不会丢失

  • callback.html登录成功之后回调的页面,即当在认证服务器登录成功之后重定向客户端的页面;

  • index.html主页面,这里也用于登出时重定向的页面;

  • noauth.html无权限时跳转的页面;

代码主要集中在index.html,下面直接上代码吧,具体说明直接看注释吧;

index.html代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <div id="app">
        <button id="login" @click="login">Login</button>
        <button id="api" @click="callApi">Call API</button>
        <button id="logout" @click="logout">Logout</button>
        <!-- 显示日志,包含登入、登出和请求API结果 -->
        <pre id="results">{{results}}</pre>
    </div>
    <script src="./js/oidc-client.js"></script>
    <script src="js/vue.js"></script>
    <!-- <script src="./js/app.js"></script> -->
    <script>
        var vm = new Vue({
            el: "#app",
            created() {
                // 根据配置信息创建用户管理对象,后续直接使用
                this.mgr = new Oidc.UserManager(this.config);
            },
            mounted() {
                // 这里要注意this的使用
                var that = this;
                this.mgr.getUser().then(function(user) {
                    if (user) { // 判断是否登录成功
                        that.log("User logged in", user.profile);
                    } else { // 没登录
                        that.log("User not logged in");
                    }
                });
            },
            data: {
                // 客户端配置
                config: {
                    authority: "http://localhost:6100",
                    client_id: "JsClient",
                    redirect_uri: "http://localhost:8080/callback.html",
                    response_type: "id_token token",
                    scope: "openid profile orderApi", //客户端需要的权限范围 
                    post_logout_redirect_uri: "http://localhost:8080/index.html",
                },
                mgr: null, //登录对象
                results: "" //登录通过数据
            },
            methods: {
                // 登录
                login: function() {
                    console.log("login");
                    this.mgr.signinRedirect();
                },
                callApi: function() {
                    console.log("CallAPI");
                    var that = this;
                    // 先判断是否登录,登录之后在去调用API
                    this.mgr.getUser().then(function(user) {
                        var url = "http://localhost:5000/api/Order";
                        // 这里使用原生异步请求,就是引入第三方ajax库啦
                        var xhr = new XMLHttpRequest();

                        xhr.open("GET", url);
                        xhr.onload = function() {
                            // 显示请求API获取到的数据
                            that.log(xhr.status, JSON.parse(xhr.responseText));
                        };
                        xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
                        // 发送请求
                        xhr.send();
                    });
                },
                logout: function() {
                    console.log("logout");
                    // 登出
                    this.mgr.signoutRedirect();
                },
                // 将信息展示
                log: function() {
                    var that = this;
                    Array.prototype.forEach.call(arguments, function(msg) {
                        if (msg instanceof Error) {
                            msg = "Error: " + msg.message;
                        } else if (typeof msg !== 'string') {
                            msg = JSON.stringify(msg, null, 2);
                        }
                        that.results = msg + '\r\n';
                    });
                }
            }
        });
    </script>
</body>
</html>

callback.html代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <script src="./js/oidc-client.js"></script>
    <script>
        new Oidc.UserManager().signinRedirectCallback().then(function() {
            console.log("test");
            // 如果登录成功就回到主页面
            window.location = "index.html";
        }).catch(function(e) {
            console.log(e);
            //没有权限就跳转到无权限页面
            window.location="noauth.html";
        });
    </script>
</body>
</html>

noauth.html代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <div>
        没权限
    </div>
</body>
</html>

到这客户端的代码已经撸完了,直接进入JsClient目录,执行live-server即可:

如果使用http请求,执行live-server即可;

3.联合调试看效果

上一步已经将客户端启起来了,接下来把认证服务器和API资源服务器启动,看看效果:

  1. 点击Login进行登录,就会跳转到认证服务器进行验证,如下:

    到这一步,估计用其他浏览器没什么问题,会继续进行跳转;但如果用到谷歌最新的浏览器,这里就不会跳转,因为谷歌对Cookie进行整改,尽管输入正确的用户名和密码,也只是刷新一下,还是在登录页面,需要做一下处理,如下:

    新增一个处理类:SameSiteCookiesServiceCollectionExtensions.cs,代码内容就不贴啦,这是公开代码,大佬写好的解决方案;

    写好处理类之后,直接使用即可,如下:

    完成以上步骤,谷歌不能跳转的问题就解决啦; 继续往下走;

  2. 输入用户名和密码 Zoe/123456,点击登录,直接进入授权选择页面,如下:

    这个界面是不是比较熟,通常我们用微信、QQ或者是其他登录时,都会弹出一个授权页面选择授权内容。这个授权页面是可以控制不显示的,只需要在认证服务器备案客户端时设置属性即可,如下:

  3. 点击授权完成之后,就会重定向配置的callback.html页面,在callback.html代码中可以看到,如果判断用户已经登录,就直接转到index.html主页面,否则就转到noauth.html无权限页面,这里已经登录授权成功,肯定最终就转到index.html页面,如下:

  4. 点击CallAPI去调用受保护资源,内部是根据上一步得到AccessToken进行保护资源的访问;

    注意,这里资源服务器一定要进跨域配置,允许客户访问,在API资源服务器中进行如下配置:

    然后运行效果如下:

  5. 点击Logout登出,这里不截图了,小伙伴试试;

好吧,今天状态不佳,先到这,后续项目实战再好好说说。

源码地址:https://github.com/zyq025/IDS4Demo/tree/main/ImlicitDemo;

总结

Implicit模式使用思路差不多就是这样,但这种模式特别要注意项目本身安全,如数据传输过程被拦截,或网站本身容易被攻击,黑客通过钓鱼页面就容易获取隐私信息等,所以项目通常都强烈推荐https,降低数据传输过程被抓包的风险;而对于钓鱼等这种攻击,可以通过判断校验源IP等方式;安全的点很多,这里只是简单举例;下次说说Authorization Code(授权码)模式;

一个被程序搞丑的帅小伙,关注”Code综艺圈”,跟我一起学~

Java RPC 框架 Solon 1.3.1 发布,推出Cloud接口与配置规范

人已赞赏
经验教程

Fody,告别烦人的INotifyPropertyChanged,最简方式实现通知!

2021-1-30 9:50:00

经验教程

理解和运用 ClassLoader 该篇文章就够了

2021-1-30 10:02:00

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索