天天看点

Keycloak 入门使用第一篇

Keycloak入门使用第一篇

    • 简介
    • 安装 & 启动服务器
    • 专有名词 & 基本使用
    • 集成Keycloak
    • 理解运行流程
    • Reference

简介

Keycloak 为现代应用和分布式服务提供了一套完整的认证授权管理解决方案,它是开源的,是一个独立的认证授权服务器。它主要是基于OAuth2协议的实现,同时提供了多种语言库,让我们可以很快速地根据我们的需求将Keycloak集成到我们的项目中去使用。

这里主要介绍Keycloak的一些基本使用实践,在学习Keycloak之前,最好要先了解OAuth2的协议流程,否则比较难以理解keycloak的认证过程。在熟悉了OAuth2协议以后去学习Keycloak,其实就很简单了。

若想了解OAuth2协议,可以看这篇 理解OAuth2协议

下面列出的是我所知道的Keycloak相关的内容,下面会详细介绍其中的某一些:

  • 是一个独立的认证授权服务器,提供完整的认证授权解决方案
  • 主要基于OpenID-Connect & SAML协议
  • 基本的登录注册,以及登录注册页面主题自定义
  • 很人性化的用户界面管理,比如用户、角色、session、Clients等等的管理
  • 具有独立的数据库,用于存储用户等认证授权数据
  • 支持联合数据存储,比如集成Ldap服务器;提供SPI扩展,比如user Storage SPI,可以让用户的一部分数据存储在你自己的数据库,一部分存储在keycloak自己的数据库
  • 提供多种语言库集成keycloak
  • 提供管理API,用于管理keycloak中所有的认证授权对象
  • Docker-compose一键安装,同时windows解压版解压后即可使用

安装 & 启动服务器

  • 解压版安装

    – 下载地址: https://downloads.jboss.org/keycloak/11.0.3/keycloak-11.0.3.zip

    https://www.keycloak.org/downloads

    – 默认是使用H2数据库存储数据,如果你想要换成其他数据库可以参考:

    https://www.cnblogs.com/ifme/p/12588910.html

    添加链接描述

    Keycloak 入门使用第一篇

    解压后可以看到上面的文件目录。

    Keycloak提供多种模式启动,standalone单机启动,domain集群启动。我们这里使用单机启动,进去到bin目录,找到standalone.bat, 双击启动。启动后访问 http://localhost:8080,正常访问说明启动成功,如下:

    Keycloak 入门使用第一篇
    点击Administration Console, 第一次需要给admin用户创建密码。
  • docker-compoe安装

    – Docker安装: https://github.com/keycloak/keycloak-containers/tree/master/docker-compose-examples

    安装使用很简单,这里就累述了。

专有名词 & 基本使用

当你启动的keyclaok服务器,然后使用admin账号登录以后,进去到keycloak管理页面:

Keycloak 入门使用第一篇

这是Master Realm的settings页面,Master Realm是keycloak默认有的realm,顾名思义就是用于管理的realm,例如admin这个用户就是属于这个realm:

Keycloak 入门使用第一篇

那Realm是什么意思呢?Realm字面意思是领域,指的是在某一个软件业务领域中所涉及的用户认证授权管理相关的对象,在这个realm下有用户、角色、会话(session)等等用于认证授权管理的对象。

假设一个公司A使用一个ERP系统,那么就可以给这个公司A设置一个Realm,用于该公司所有员工的授权管理。那么如果另一个公司B也使用这个ERP系统(假设这个ERP系统是第三方提供给所有公司使用的一个ERP服务,就需要给公司B也创建一个Realm,用于公司B员工的授权管理。

所以Realm之间的相互隔离的一个业务领域概念。

  • 创建一个Realm
    Keycloak 入门使用第一篇

    设置一个名字,然后你就可以给你自己的realm设置各种对象了。比如创建用户,创建一个角色,给用户授予角色等等。

    例如:我创建一个USER的角色,创建一个testuser01用户,然后给这个用户授予USER角色:

    Keycloak 入门使用第一篇
    界面使用非常简单,大家探索一下就行了。

集成Keycloak

Keycloak 入门使用第一篇

假设我们有两个web服务器,我们需要使用keycloak来对我们的资源进行保护,只有用户登录以后才能访问到这两个服务器的资源,否则就要跳转到登录页面。所以我们要在两个服务之前加一个gateway层,在这一层对用户请求进行拦截,验证用户是否已经登录(了解OAuth2的话,就知道这里就是验证accessToken),如果没有的话,就要引导用户去到keycloak登录页面,认证以后再跳转回到要访问的页面。

因此,我们已经启动了keycloak服务器,缺少的是怎么将拦截用户请求并验证accessToken这些逻辑加入到我们的应用中。别急,keycloak官方给我们提供了这些库,它把这个东西叫做 adaptor(connector),所以我们只要将这些库安装到我们的项目中就可以为我们的应用实现认证授权。

这里呢,我实践的是给nodejs server集成keycloak认证授权,所以以下介绍的是 Nodejs-keycloak-adaptor.

keycloak-nodejs-adaptor 我也是参考官方的例子去实现的。

另外keycloak官方有一些quickstart的最佳实践,参考:keycloakQuickstart

  • 注册一个client

    第一步首先要在你的realm下注册一个client:

    Keycloak 入门使用第一篇

    选择clients,点击create,输入clientId,点击save以后出现该client的settings页面,Access Type选择confidential,Valid Redirect URLs必须要填,这个填入你要集成keycloak的那个web服务器的主页,例如http://localhost:3000/*即可。在OAuth2协议中,redirectURI是必填的,而在keycloak这里,意思是回调的redirectURI必须要match上这里配置的Valid Redirect URLs才可以,否则会报错。

    例如上面填的是http://localhost:3000/*,http://localhost:3000/users就可以match,而http://localhost:3001/users就不会match,就会报错。

点击Save,然后切换到最后一栏installation,拿到client的连接信息:

Keycloak 入门使用第一篇
  • 初始化nodejs项目
  1. 使用npm init初始化一个nodejs项目,加入如下依赖:
"body-parser": "^1.13.3",
    "cors": "^2.8.1",
    "express": "^4.13.3",
    "express-session": "^1.14.2",
    "jsonwebtoken": "^8.5.1",
    "keycloak-admin": "^1.13.0",
    "keycloak-connect": "11.0.2",
           

其中keycloak-connect就是集成keycloak的连接库

  1. 新建一个keycloak-config.js,将你的的installation连接信息替换替换对应的属性,例如上面的信息就是:
module.exports = {
  realm: 'myRealm',
  authServerUrl: 'http://localhost:8080/auth/',
  clientId: 'testClient',
  credentials: {
    secret: '4434b959-835b-45c9-bed5-607e025e0100'
  },
  bearerOnly: false
}
           

authServerUrl记得最后加上/,这里是一个坑,不加可能报错。以上拿到的连接信息,其实resource就是clientId,对应OAuth2中的clientId和clientSecret(credentials.secret)。所以说,如果你熟悉OAuth2,来到这里很容易就知道看到这就是client必须要有的基本信息。

  1. 新建一个app-express.js (名字随你)

直接贴出全部代码:

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser')
const session = require('express-session')
const KeycloakConnect = require('keycloak-connect')
const keycloakConfig = require('./keycloak-config')
const jwt = require('jsonwebtoken')
const  router = express.Router();

// keycloak
const memoryStore = new session.MemoryStore();
const keycloak = new KeycloakConnect({store: memoryStore}, keycloakConfig)

const app = express();
app.use(bodyParser.json());

app.use(session({
    secret: 'some secret',
    resave: false,
    saveUninitialized: true,
    store: memoryStore
}));

app.use(keycloak.middleware({
    logout: '/logout',
    admin: '/'
}));

// api settings
router.get('/service/public', (req, res) => {
    res.json({message: 'public'});
});
router.get('/service/secured', keycloak.protect('realm:USER'), function (req, res) {
    res.json({message: 'secured'});
});

const keycloakProtect = keycloak.protect()
router.get('*', (req, resp, next) => {
    const originalUrl = req.originalUrl
    if (originalUrl.indexOf('abc') > -1 || originalUrl === '/' || originalUrl.indexOf('favicon.ico') > -1) {
        return next()
    }
    return keycloakProtect(req, resp, next)
}, (req, resp, next) => {
    const keycloakToken = req.session['keycloak-token']
    let userId = 'test'
    if (keycloakToken) {
        userId = jwt.decode(JSON.parse(keycloakToken).access_token).preferred_username
    }
    const cookiesOption = {
        maxAge: 1000 * 60 * 60 * 24,
        httpOnly: false
    }
    resp.cookie('user_id', userId, cookiesOption)
    return next()
})

app.use(router)
// static resources
app.use(express.static(path.join(__dirname, '/views')));


app.listen(3001, () => {
    console.log('Started at port 3001');
});
           

这里是使用express启动一个nodejs server

const keycloak = new KeycloakConnect({store: memoryStore}, keycloakConfig)

const app = express();
app.use(bodyParser.json());

app.use(session({
    secret: 'some secret',
    resave: false,
    saveUninitialized: true,
    store: memoryStore
}));

app.use(keycloak.middleware({
    logout: '/logout',
    admin: '/'
}));
           

新建KeycloakConnect对象,传进去sessionStore以及keycloakConfig。注意这里keycloak的store使用的是express-session的store,因为认证成功以后keycloak就会将所有的token的信息存储在session中。

最后就是use keycloak提供的中间件,查看这些中间源码你就会知道,这些中间件所做的事情其实就是首先会从session拿出accessToken,然后认证accessToken的有效性等等。

keycloak.protect() 在你的router api中加入这个中间件,就能够对你这个API进行认证保护,后面的代码相信大家一看就明白了,这里不多说。(Talk is cheap, show you the code)

完整代码查看:Github (有些代码没有放出来)

运行项目,访问http://localhost:3000/,可以正常访问;访问http://localhost:3000/work.html,会跳转到登录页面。

Keycloak 入门使用第一篇

理解运行流程

那么对应上面的这个例子呢,可能你确实能成功保护你的资源了,但是我们有必要去理解一下整一个认证过程,这个验证过程其实就是OAuth2的授权码模式:

Keycloak 入门使用第一篇

在第2步,web server将用户导向keycloak server的重定向连接:

http://localhost:8080/auth/realms/myRealm/protocol/openid-connect/auth?client_id=nodejs&state=10abdacb-5e85-4f48-8bf7-b8aedb378567&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fwork.html%3Fauth_callback%3D1&scope=openid&response_type=code

可以看到使用的就是OAuth2授权码模式:response_type=code

在第3步中keycloak-server将用户导向回web-server的重定向连接:

http://localhost:3001/work.html?auth_callback=1&state=10abdacb-5e85-4f48-8bf7-b8aedb378567&session_state=3f47f74a-ef0c-4245-b87e-22acae8b08e9&code=281cd9c6-187e-423f-84ee-9419aa4dab7d.3f47f74a-ef0c-4245-b87e-22acae8b08e9.aa9ee788-cbb7-4370-9ed2-6de8b2b510b7

  • 通过debug查看存储在session中的keycloak-token信息:
    Keycloak 入门使用第一篇

    返回了三个token:accessToken、refreshToken、idToken

    accessToken 用于每次访问资源服务器时验证用户权限,refreshToken用于在accessToken过期以后可以通过它重新想keycloak-server申请一个新的accessToken,而不需求重新登录。idToken用于识别用户身份,这个OpenID-Connect协议新加入的一个token。

  • 补充

    这个例子是基于express,如果你的node-server是使用Koa的话,官方是没有提供Koa相关connector的,如果有需要可以参考我的github,里面有Koa的keycloak例子。啊哈哈哈哈!!

    Github 代码

后面还有一篇哦 入门第二篇

Reference

https://github.com/keycloak

https://www.keycloak.org/

继续阅读