nginx配置sso登錄

nginx配置sso登錄

下面的例子是如何在nginx配置sso登錄服務(wù)挫剑。

背景

用到幾個(gè)主要元素:應(yīng)用服務(wù)器(myweb)樊破,ngingx服務(wù)器,和認(rèn)證服務(wù)器(mycas)奔滑。

為了驗(yàn)證的簡(jiǎn)化朋其,所有的服務(wù)器都搭建在一臺(tái)主機(jī)上(假設(shè)當(dāng)前機(jī)器名為host.example.com),主機(jī)名即域名氓辣,三個(gè)服務(wù)通過(guò)三個(gè)不同的端口提供服務(wù)钞啸。

  1. 應(yīng)用服務(wù)器喇潘,監(jiān)聽(tīng)在主機(jī)端口4000颖低。
  2. CAS服務(wù)器,監(jiān)聽(tīng)在主機(jī)端口3000源武。
  3. nginx服務(wù)器粱栖,監(jiān)聽(tīng)在主機(jī)端口80脏毯。

sso登錄驗(yàn)證的基本流程

    1. 用戶通過(guò)服務(wù)器端口80來(lái)請(qǐng)求訪問(wèn)資源食店。
      端口80實(shí)際上是nginx的端口,真正的應(yīng)用端口是4000价认;nginx會(huì)把請(qǐng)求最終路由到應(yīng)用服務(wù)器端口4000上用踩。
    1. nginx通過(guò)配置文件的auth_request模塊脐彩,把請(qǐng)求轉(zhuǎn)向CAS服務(wù)器進(jìn)行認(rèn)證(假設(shè)http://host.example.com:3000/auth)
    • 2.1. 如果用戶沒(méi)有在CAS服務(wù)器上經(jīng)過(guò)登錄認(rèn)證姊扔,那么認(rèn)證服務(wù)器返回401錯(cuò)誤給nginx恰梢;nginx繼續(xù)根據(jù)配置文件對(duì)401錯(cuò)誤的處理方式证九,向用戶瀏覽器返回302狀態(tài)碼愧怜,并附帶一個(gè)認(rèn)證服務(wù)器的跳轉(zhuǎn)地址(http://host.example.com:3000/login)拥坛,然后客戶瀏覽器重新向認(rèn)證服務(wù)器發(fā)起登錄請(qǐng)求尘分。用戶在認(rèn)證服務(wù)器上進(jìn)行簡(jiǎn)單用戶名密碼驗(yàn)證:
      • 2.1.1. 如果用戶為合法用戶:那么認(rèn)證服務(wù)器將生成session培愁,包含認(rèn)證過(guò)的用戶信息定续,以備將來(lái)的再次驗(yàn)證使用,并通過(guò)Set-cookie命令告知用戶瀏覽器摹察。用戶通過(guò)此Cookie即可獲得認(rèn)證服務(wù)器的認(rèn)可授權(quán)供嚎,此后用戶帶上此Cookie來(lái)訪問(wèn)認(rèn)證服務(wù)器(http://host.example.com:3000/auth)時(shí)峭状,認(rèn)證服務(wù)器會(huì)返回200的狀態(tài)碼优床。
      • 2.1.2. 如果用戶為非法用戶:那么認(rèn)證服務(wù)器將不會(huì)session羔巢,這樣用戶無(wú)法獲得認(rèn)可的Cookie罩阵,那么當(dāng)再次訪問(wèn)(http://host.example.com:3000/auth)時(shí)稿壁,會(huì)繼續(xù)得到401錯(cuò)誤的錯(cuò)誤碼傅是,請(qǐng)求再次認(rèn)證蕾羊。
    • 2.2. 假設(shè)用戶已經(jīng)授權(quán)成功龟再,那么當(dāng)用戶訪問(wèn)服務(wù)器中的資源時(shí)利凑,由于認(rèn)證服務(wù)器(http://host.example.com:3000/auth)返回返回200狀態(tài)碼嫌术,服務(wù)器允許用戶繼續(xù)訪問(wèn)度气。

我們使用node/express來(lái)模擬應(yīng)用服務(wù)器和CAS認(rèn)證服務(wù)器磷籍。

步驟

  1. 安裝nginx
$ cat /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/rhel/7/$basearch/
gpgcheck=0
enabled=1

$ sudo yum install nginx
  1. 安裝node

參考官方文檔院领,不細(xì)說(shuō)。
https://nodejs.org/en/

  1. 安裝express-generator

我們用它來(lái)生產(chǎn)node項(xiàng)目框架汪诉。

$ npm install express-generator -g
  1. 創(chuàng)建兩個(gè)node項(xiàng)目

    4.1. 創(chuàng)建應(yīng)用服務(wù)器

$ express -e --git myweb
$ cd myweb
$ npm install

修改認(rèn)證服務(wù)器端口為4000

$ cat bin/www
var port = normalizePort(process.env.PORT || '4000');

4.2. 創(chuàng)建認(rèn)證服務(wù)器

$ express -e --git mycase
$ cd note
$ npm install

保持認(rèn)證服務(wù)器端口為3000

$ cat bin/www
var port = normalizePort(process.env.PORT || '3000');
  1. 修改nginx配置文件
$ cat /etc/nginx/nginx.conf
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;

    server {
        listen   80 ;
        server_name host.example.com;

        location / {
            auth_request /auth;
            error_page 401 = @error401;
  
            auth_request_set $user $upstream_http_x_forwarded_user;
            proxy_set_header X-Forwarded-User $user;

            proxy_pass http://host.example.com:4000;
        }

        location /logout {
            proxy_pass http://host.example.com:4000/logout;
        }

        location /auth {
            internal;
            proxy_pass_request_body off;
            proxy_set_header Content-Length "";
            proxy_set_header X-Original-URI $request_uri;

            proxy_pass http://host.example.com:3000;
        }
  
        location @error401 {
            add_header Set-Cookie "redirect=$scheme://$http_host$request_uri;Domain=.example.com;Path=/;Max-Age=3000";
            return 302 http://host.example.com:3000/login;
        }
    }
}
  1. 修改應(yīng)用服務(wù)器app.js
$ cat myweb/app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var bodyparser = require('body-parser');
var expresssession = require('express-session');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(bodyparser.json());
app.use(bodyparser.urlencoded({ extended: false }));
app.use(expresssession({
    secret: 'mysecret',
    resave: true,
    saveUninitialized: false,
    cookie: {
        maxAge: 1000 * 60 * 3
    }
}));

app.get('/', function (req, res) {
    console.log("/ session.id   =", req.session.id);
    console.log("/ session.user =", req.session.user);
    console.log("/ headers.user =", req.headers["user"]);
    console.log("/ cookies.user =", req.cookies["user"]);

    user = req.cookies["user"]
    if (user) {
        req.session.user= user;
        res.end('Welcome Page!');
    } else {
        console.error("401 Unauthorized");
        res.end('401 Unauthorized');
    }
});

app.get('/logout', function (req, res) {
    console.log("/ session.id   =", req.session.id);
    console.log("/ session.user =", req.session.user);
    console.log("/ headers.user =", req.headers["user"]);

    req.session.destroy();
    res.redirect('http://host.example.com:3000/logout');
});

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;
  1. 修改認(rèn)證服務(wù)器app.js
$ cat mycas/app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var bodyparser = require('body-parser');
var expresssession = require('express-session');
var cookie = require('cookie-parser');
var crypto = require('crypto');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var userTokens = {};
var userDatabase = { 'admin': 'admin' };

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(cookie());
app.use(bodyparser.json());
app.use(bodyparser.urlencoded({ extended: false }));
app.use(expresssession({
    secret: 'mysecret',
    resave: true,
    saveUninitialized: false,
    cookie: {
        maxAge: 1000 * 60 * 3
    }
}));

app.get('/login', function (req, res) {
    console.log("/login req.session.id = ", req.session.id);
    console.log("/login req.headers    = ", req.headers);
    res.sendFile(path.join(__dirname, './public/templates', 'login.html'));
});

app.post('/login', function (req, res) {
    var user = req.body.username;
    var pass = req.body.password;
    console.log("/login POST req.session.id=", req.session.id);
    if (userDatabase.hasOwnProperty(user) && userDatabase[user] == pass) {
        // when username/password is valid
        var token = crypto.createHash('sha256').update(req.session.id).digest("hex")
        //req.session.user= user;
        //req.session.token= token;

        res.cookie('user', user);
        res.cookie('token', token);

        userTokens[token] = user;
        console.log("/login POST generate token[", token, "] for user [", user, "]");

        res.redirect('http://host.example.com')
    } else {
        res.redirect('/login');
    }
});

app.get('/auth', function (req, res) {
    console.log("/auth session.id    : " + req.session.id);
    console.log("/auth session.user  : " + req.session.user);
    console.log("/auth session.token : " + req.session.token);
    console.log("/auth cookie.user   : " + req.cookies.user);
    console.log("/auth cookie.token  : " + req.cookies.token);

    token = req.cookies.token;
    user  = req.cookies.user;

    if (userTokens[token] && userTokens[token] == user) {
        console.log("/auth return success");
        res.setHeader("X-Forwarded-User", user);
        res.setHeader("user", user);
        res.end()
    } else {
        console.log("/auth return failure");
        res.status(401);
        res.end()
    }
});

app.get('/logout', function (req, res) {
    console.log("/logout req.session.id : " + req.session.id);
    req.session.destroy();
    res.clearCookie("user");
    res.clearCookie("token");
    res.redirect('/login');
});

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

為認(rèn)證服務(wù)器添加一個(gè)登錄頁(yè)面:

$ cat mycas/public/templates/login.html 
<!DOCTYPE html>
 
<html>
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
<style type="text/css">
 body{
  margin: 100px 0px;
  padding:0px;
  text-align:center;
  align:center;
  }

 input[type=text], input[type=password]{
    width:20%;
    padding:7px 10px;
    margin: 8px 0;
    display:inline-block;
    border: 1px solid #ccc;
    box-sizing: border-box;
  }
  
 button{
     background-color:#4CAF50;
     width: 10%;
     padding: 9px 5px;
     margin:5px 0;
     cursor:pointer;
     border:none;
     color:#ffffff;
  }

 button:hover{
   opacity:0.8;
 }

#un,#ps{
 font-family:'Lato', sans-serif;
 color: gray;
}
</style>
</head>
 
<body>
  <div id="container">
    <form action="/login" method="post">
      <h2>Login Form</h2>
   
      <label for="username" id="un">Username:</label> 
      <input type="text" name="username" id="username"><br/><br/>
  
      <label for="password" id="ps">Password:</label>  
      <input type="password" name="password" id="password"><br/><br/>
   
      <button type="submit" value="Login"  id="submit">Login</button>
   </form>
  </div>
</body>
  1. 啟動(dòng)應(yīng)用

啟動(dòng)認(rèn)證服務(wù)器

$ DEBUG=myweb:* npm start

啟動(dòng)應(yīng)用服務(wù)器

$ DEBUG=mycas:* npm start

啟動(dòng)nginx

$ sudo service nginx restart

nginx的錯(cuò)誤日志在:/var/log/nginx/error.log

另外如果碰到如下錯(cuò)誤:
(13: Permission denied) while connecting to upstream:[nginx]

請(qǐng)參考下面鏈接:
https://stackoverflow.com/questions/23948527/13-permission-denied-while-connecting-to-upstreamnginx

  1. 登錄訪問(wèn)

登錄
http://host.example.com
瀏覽器會(huì)跳轉(zhuǎn)到登錄頁(yè)面,http://host.example.com:3000/login于樟,輸入用戶名密碼(admin/admin)后迂曲,跳轉(zhuǎn)到應(yīng)用服務(wù)器頁(yè)面寥袭,顯示"Welcome Page!"

退出
http://host.example.com/logout
退出后,頁(yè)面重新跳轉(zhuǎn)到登錄頁(yè)面队寇。

  1. 關(guān)于應(yīng)用服務(wù)器如何獲取認(rèn)證服務(wù)器的數(shù)據(jù)

11.1 設(shè)置request.Headers
在認(rèn)證服務(wù)器章姓,認(rèn)證成功時(shí):

app.get('/auth', function (req, res) {
    if (userTokens[token] && userTokens[token] == user) {
        res.setHeader("X-Forwarded-User", user);
        res.setHeader("X-Idcs-User", user);
        res.end()
    }

然后下nginx的配置文件里:

      location / {

        auth_request_set $user $upstream_http_x_forwarded_user;
        proxy_set_header X-Forwarded-User $user;

        auth_request_set $idcsuser  $upstream_http_x_idcs_user;
        proxy_set_header X-Idcs-User $idcsuser;
    }

從認(rèn)證服務(wù)器的upstream獲取x_forwarded_user和x_idcs_user啤覆,注意用小寫(xiě)窗声,然后設(shè)置到應(yīng)用服務(wù)器的request里面去笨觅。

11.2 設(shè)置瀏覽器cookie

在認(rèn)證服務(wù)器的post/login成功時(shí):

app.post('/login', function (req, res) {
    var user = req.body.username;
    var pass = req.body.password;
    console.log("/login POST req.session.id=", req.session.id);
    if (userDatabase.hasOwnProperty(user) && userDatabase[user] == pass) {
        res.cookie('user', user);
        res.cookie('token', token);
    }
}

這樣瀏覽器就能看到cookie的內(nèi)容了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市苍苞,隨后出現(xiàn)的幾起案子羹呵,更是在濱河造成了極大的恐慌镇防,老刑警劉巖凑耻,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件类缤,死亡現(xiàn)場(chǎng)離奇詭異弃衍,居然都是意外死亡镜盯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)剧董,“玉大人破停,你說(shuō)我怎么就攤上這事真慢。” “怎么了管嬉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蚯撩,是天一觀的道長(zhǎng)烛占。 經(jīng)常有香客問(wèn)我忆家,道長(zhǎng),這世上最難降的妖魔是什么项栏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任沼沈,我火速辦了婚禮列另,結(jié)果婚禮上旦装,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好店乐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布艰躺。 她就那樣靜靜地躺著,像睡著了一般眨八。 火紅的嫁衣襯著肌膚如雪腺兴。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天廉侧,我揣著相機(jī)與錄音页响,去河邊找鬼。 笑死段誊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的连舍。 我是一名探鬼主播没陡,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼烟瞧!你這毒婦竟也來(lái)了诗鸭?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤参滴,失蹤者是張志新(化名)和其女友劉穎强岸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體砾赔,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝌箍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暴心。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妓盲。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖专普,靈堂內(nèi)的尸體忽然破棺而出悯衬,到底是詐尸還是另有隱情,我是刑警寧澤檀夹,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布筋粗,位于F島的核電站,受9級(jí)特大地震影響炸渡,放射性物質(zhì)發(fā)生泄漏娜亿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一蚌堵、第九天 我趴在偏房一處隱蔽的房頂上張望买决。 院中可真熱鬧沛婴,春花似錦、人聲如沸督赤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)够挂。三九已至旁仿,卻和暖如春藕夫,著一層夾襖步出監(jiān)牢的瞬間孽糖,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工毅贮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留办悟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓滩褥,卻偏偏與公主長(zhǎng)得像病蛉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瑰煎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容