elice/WIL

[week_07] Express.js์™€ MongoDB๋กœ ์›น์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ (3) | JWT, OAuth, Nginx

๊ฑด๋ง๋”” 2022. 2. 25. 18:11

1. JWT

  • JSON Web Token
  • ์ธ์ฆ์„ ์œ„ํ•œ ์ •๋ณด๋ฅผ ์ „์ž ์„œ๋ช…์„ ์ด์šฉํ•ด์„œ ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ•
  • Web Token โžก ๋ฐ์ดํ„ฐ๋ฅผ ์›น์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์ŠคํŽ™ โžก base64 ์ธ์ฝ”๋”ฉ ์‚ฌ์šฉ
1. header
  - ํ† ํฐ์˜ ํƒ€์ž…(jwt), ๋ฐ์ดํ„ฐ ์„œ๋ช… ๋ฐฉ์‹
2. payload
  - ์ „๋‹ฌ๋˜๋Š” ๋ฐ์ดํ„ฐ
3. signature
  - ํ—ค๋”์™€ ํŽ˜์ด๋กœ๋“œ์˜ ์ „์ž์„œ๋ช…

  • payload๋Š” decode ์‹œ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ์ œ์™ธํ•˜๊ณ  ํ† ํฐ์„ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค.
  • ์„œ๋ฒ„๊ฐ€ JWT๋ฅผ ์ƒ์„ฑํ•  ๋•Œ, ๋น„๊ณต๊ฐœํ‚ค๋ฅผ ์ด์šฉํ•ด์„œ ์„œ๋ช… โžก payload ์กฐ์ž‘ํ•˜๋ฉด ์„œ๋ช…์ด ๋ถˆ์ผ์น˜ โžก ์ธ์ฆ ์‹คํŒจ
โœจ JWT ์ž‘๋™ ๋ฐฉ์‹
1. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ
2. ์„œ๋ฒ„๊ฐ€ ๋กœ๊ทธ์ธ๋œ ์œ ์ € ์ •๋ณด๋ฅผ JWT๋กœ ์ƒ์„ฑํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌ
3. ํด๋ผ์ด์–ธํŠธ๋Š” ์ „๋‹ฌ๋ฐ›์€ JWT๋ฅผ ์ด์šฉํ•˜์—ฌ ์ธ์ฆ์ด ํ•„์š”ํ•œ ์š”์ฒญ์— ์‚ฌ์šฉ
  • session์˜ ๊ฒฝ์šฐ, ์›น ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์•„๋‹Œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒฝ์šฐ ํ™œ์šฉํ•˜๊ธฐ ๋ถ€์ ํ•ฉํ•˜๋‹ค. JWT๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋А ํด๋ผ์ด์–ธํŠธ์—์„œ๋‚˜ ๋™์ผํ•œ ๋ฐฉ์‹์˜ ์‚ฌ์šฉ์ž ์ธ์ฆ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, JWT๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

1-1. JWT +Cookie ์‚ฌ์šฉํ•˜๊ธฐ

๐Ÿ”น Session์„ ์‚ฌ์šฉํ•œ ์œ ์ € ๋กœ๊ทธ์ธ
Cookie์— Session ID ์ €์žฅ โžก Session Store์—์„œ ์œ ์ € ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

๐Ÿ”น JWT๋ฅผ ์ฟ ํ‚ค์— ์ €์žฅํ•˜๋Š” ๊ฒฝ์šฐ
JWT๋กœ ์š”์ฒญ โžก ์„œ๋ฒ„๊ฐ€ ์„œ๋ช… ํ™•์ธ ํ›„ ์œ ์ € ์ •๋ณด ์‚ฌ์šฉ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ์ด ์ค„์–ด์„œ ํšจ์œจ์ ์ธ ์ธ์ฆ ๊ตฌํ˜„ ๊ฐ€๋Šฅ!
โœจ JWT ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ
1. ๊ธฐ์กด ์„ธ์…˜์œผ๋กœ ๊ตฌํ˜„๋œ ๋กœ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™”
2. ๋กœ๊ทธ์ธ ๋กœ์ง์—์„œ JWT ์ƒ์„ฑ ํ›„ ์ฟ ํ‚ค๋กœ ์ „๋‹ฌ
3. passport-jwt ํŒจํ‚ค์ง€๋กœ JWT ๋กœ๊ทธ์ธ ๋ฏธ๋“ค์›จ์–ด ์ž‘์„ฑ ๋ฐ ์‚ฌ์šฉ

1) ๋กœ๊ทธ์ธ ๋กœ์ง์— JWT ํ† ํฐ ์ƒ์„ฑ ๋ฐ ์ฟ ํ‚ค ์ „๋‹ฌ

setUserToken = (res, user) => {
	const token = jwt.sign(user, secret); // ์œ ์ € jwt ํ† ํฐ ์ƒ์„ฑ
    res.cookie('token', token);
} // res.cookie ํ•จ์ˆ˜ ์‚ฌ์šฉ => ํ† ํฐ์„ ํด๋ผ์ด์–ธํŠธ์— ์ฟ ํ‚ค๋กœ ์ „๋‹ฌ


// --------------------


router.post('/', passport.authenticate('local'), (req, res, next) => {
	// ์œ ์ € ํ† ํฐ ์ƒ์„ฑ ๋ฐ ์ฟ ํ‚ค์— ์ „๋‹ฌํ•˜๊ธฐ
    setUserToken(res, req.user);
    res.redirect('/');
});

2) passport-jwt๋กœ ์š”์ฒญ๋œ JWT ํ† ํฐ์˜ ์„œ๋ช… ํ™•์ธํ•˜๊ณ  ์ธ์ฆํ•˜๊ธฐ

const JwtStrategy = require('passport-jwt').Strategy;
const cookieExtractor = (req) => {
	const { token } = req.cookies;
    
    return token;
};

const opts = {
	secretOrKey: secret,
    jwtFromRequest: cookieExtractor,
}

module.exports = new JwtStrategy(opts, (user, done) = {
	done(null, user);
});


// -----

passport.use(jwt);

3) JWT ๋ฏธ๋“ค์›จ์–ด ์ถ”๊ฐ€

  • JWT ํ† ํฐ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  ์š”์ฒญ์— ํฌํ•จ
  • ์š”์ฒญ์— ํ† ํฐ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ๋œ ์ƒํƒœ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด
  • ๋ชจ๋“  ์š”์ฒญ์— ๊ณตํ†ต์ ์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฏธ๋“ค์›จ์–ด๋กœ JWT ๋กœ๊ทธ์ธ์„ ์ถ”๊ฐ€
/* jwt middleware */
app.use((req, res, next) => {
	if(!req.cookies.token) { // ํ† ํฐ ์—†๋Š” ๊ฒฝ์šฐ
    	next();
        return;
    }
    
    return passport.authenticate('jwt')(req, res, next);
    // authenticate ํ•จ์ˆ˜์— req, res, next ์ „๋‹ฌ
    // ์ด ๋•Œ jwt Strategy๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
});


// app.js
// passport.initialize์™€ ๋‹ค๋ฅธ ๋ฏธ๋“ค์›จ์–ด๋“ค ์‚ฌ์ด์— ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.
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(passport.initialize());

// jwt ๋กœ๊ทธ์ธ ๋ฏธ๋“ค์›จ์–ด ์ถ”๊ฐ€
app.use(getUserFromJWT);

app.use('/', indexRouter);
app.use('/posts', loginRequired, postsRouter);
app.use('/users', loginRequired, usersRouter);
app.use('/api', loginRequired, apiRouter);
app.use('/auth', authRouter);

4) ๋กœ๊ทธ์•„์›ƒ

  • ํด๋ผ์ด์–ธํŠธ ์ฟ ํ‚ค๋ฅผ ์‚ญ์ œํ•˜์—ฌ ์ฒ˜๋ฆฌ
  • token ๊ฐ’์„ null๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ๊ณผ ํ•จ๊ป˜, cookie์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ 0์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฟ ํ‚ค๋ฅผ ๋ฐ”๋กœ ๋งŒ๋ฃŒ์‹œํ‚ค๋„๋ก ์ „๋‹ฌ
res.cookie('token', null, {
	maxAge: 0,
});

 

2. ํšŒ์› ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ๊ตฌํ˜„

1. ์ž„์˜์˜ ๋ฌธ์ž์—ด๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™”
2. ์ดˆ๊ธฐํ™”๋œ ๋ฌธ์ž์—ด์„ ๋ฉ”์ผ๋กœ ์ „๋‹ฌ โžก ๋ฉ”์ผ ๋ฐœ์†ก๊ธฐ๋Šฅ ๊ฐœ๋ฐœ ํ•„์š”
3. ์ดˆ๊ธฐํ™” ํ›„ ์ฒซ ๋กœ๊ทธ์ธ ์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์š”์ฒญ

2-1. ๋ฉ”์ผ ๋ฐœ์†ก๊ธฐ๋Šฅ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

  • SMTP ์„œ๋ฒ„ ์ด์šฉ : ๋„ค์ด๋ฒ„, ๊ตฌ๊ธ€ ๋“ฑ์˜ ๋ฉ”์ผ์„œ๋ฒ„ ์ด์šฉํ•˜์—ฌ ๋ฉ”์ผ ๋ฐœ์†ก ๋ฐ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ ์ง์ ‘ ๊ฐœ๋ฐœ
  • ๋ฉ”์ผ ๋ฐœ์†ก ์„œ๋น„์Šค ์ด์šฉ(Mailgun, Sendgrid, ...) : ๋ฉ”์ผ ๋ฐœ์†ก api ๋ฐ ๊ด€๋ฆฌ์šฉ ์›นํŽ˜์ด์ง€ ์ œ๊ณต

1) Nodemailer + Gmail ์‚ฌ์šฉํ•˜๊ธฐ

/* Nodemailer + Gmail ์‚ฌ์šฉํ•˜๊ธฐ */
const nodemailer = require('nodemailer');

const transport = nodemailer.createTransport({
	service: 'Gmail',
    auth: {
    	user: "google account",
        pass: "app password",
    },
});
const message = {
	from: "login account",
    to: "mail address",
    subjet: "title",
    text: "message"
};

transport.sendMail(message, (err, info) => {
	if (err) {
    	console.error('err', err);
        return;
    }
    
    console.log('ok', info);
});

2) ๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™” ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ

function generateRandomPassword() { // ์ž„์˜์˜ ๋ฌธ์ž์—ด ์ƒ์„ฑ
	return Math.floor(
      Math.random() * (10 ** 8)
    ).toString().padStart('0', 8);
}

//---

router.post('/reset-password', asyncHandler(... => {
	const { email } = req.body; // ์ด๋ฉ”์ผ์„ ๋ฐ›์•„์„œ
    const password = generateRandomPassword(); // ์ƒ์„ฑ๋œ ์ž„์˜์˜ ๋ฌธ์ž์—ด๋กœ ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™”
    await User.findOneAndUpdate({ email }, { // ์ „๋‹ฌ๋ฐ›์€ ์ด๋ฉ”์ผ๋กœ ์œ ์ € ํŒจ์Šค์›Œ๋“œ ๋ณ€๊ฒฝ
    	password: getHash(password),
    });
    
    await sendEmail(email, '...', password); // ์ดˆ๊ธฐํ™”ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฉ”์ผ๋กœ ๋ฐœ์†ก
    res.redirect('/');
}));

3) ์ดˆ๊ธฐํ™” ํ›„ ๋กœ๊ทธ์ธ ์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์š”์ฒญ

/* ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ */
const UserSchema = {
	...
    passwordReset: {
    	type: Boolean,
        default: false,
    }
    ...
}

// ---

router.post('/reset-password', ...
	await User.findOneAndUpdate({
    	...
        passwordReset: true, // ํŒจ์Šค์›Œ๋“œ ๋ฆฌ์…‹๋œ ๊ฒฝ์šฐ true๋กœ ๋ณ€๊ฒฝ
    });
/* ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์š”์ฒญ */
function checkPasswordReset(req, res, next) {
	if(req.user && req.user.passwordReset) {
    	res.redirect('/update-password');
        return;
    }
    next();
}

// ---

router.post('/update-password', ...
	await User.findOneAndUpdate({
    	...
        passwordReset: false, // ๋น„๋ฐ€๋ฒˆํ˜ธ ์—…๋ฐ์ดํŠธ ๋˜๋ฉด ๋‹ค์‹œ false๋กœ ๋ณ€๊ฒฝ
    });

 

3. OAuth

  • Open Authorization
  • ์„œ๋น„์Šค ์ œ๊ณต์ž๊ฐ€ ๋‹ค๋ฅธ ์„œ๋น„์Šค์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋น„์Šค ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•˜๋Š” ์‚ฌ์šฉ์ž ์ธ์ฆ๋ฐฉ์‹์˜ ํ‘œ์ค€
  • ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ์„ ์ œ๊ณตํ•˜๋Š” ํ‘œ์ค€
  • ์ด๋ฅผ ํ™œ์šฉํ•ด์„œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์›น ์„œ๋น„์Šค ์ œ๊ณต์ž๋Š” ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋กœ๊ทธ์ธ์„ ๊ตฌํ˜„ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
    • ์›น ์„œ๋น„์Šค ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ ์‹œ ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
1. ์„œ๋น„์Šค ์ œ๊ณต์ž์—๊ฒŒ ์ธ์ฆ ์š”์ฒญ
2. ์ธ์ฆ ์™„๋ฃŒ ํ›„ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์š”์ฒญํ•œ ์„œ๋น„์Šค๋กœ ์ „๋‹ฌ
3. ์ธ์ฆ ์ •๋ณด๋ฅผ ์ด์šฉํ•ด ์„œ๋น„์Šค ์ œ๊ณต์ž์˜ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ

ex) ๊ตฌ๊ธ€ ์บ˜๋ฆฐ๋” ์—ฐ๋™ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ ๋‹ค๊ณ  ๊ฐ€์ •
1. ๊ตฌ๊ธ€ OAuth ์ธ์ฆ ์š”์ฒญ
2. ์ธ์ฆ๋œ OAuth Token์„ ๊ธฐ๋ก
3. OAuth Token์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ๊ธ€ ์บ˜๋ฆฐ๋” API ์‚ฌ์šฉ

 

4. ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ๊ตฌํ˜„

๐Ÿ™„ ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ ์ˆœ์„œ
- ๊ตฌ๊ธ€ ํด๋ผ์šฐ๋“œ ํ”Œ๋žซํผ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
- API ๋ฐ ์„œ๋น„์Šค โžก OAuth ๋™์˜ํ™”๋ฉด ์„ค์ •
- ์‚ฌ์šฉ์ž ์ธ์ฆ์ •๋ณด โžก OAuth ํด๋ผ์ด์–ธํŠธ ID ๋งŒ๋“ค๊ธฐ
- passport-google-oauth20 ์—ฐ๋™

4-1. passport-google-oauth20

  • passport-strategy ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ๊ตฌํ˜„์ฒด
  • ์†์‰ฝ๊ฒŒ ๊ตฌ๊ธ€ OAuth 2.0์„ ๊ตฌํ˜„ํ•ด์ฃผ๋Š” ํŒจํ‚ค์ง€

1) passport-google-oauth20 ์ž‘์„ฑ

/* google strategy */
const GoogleStrategy = require('passport-google-oauth20').Strategy;

const config = { // OAuth ํด๋ผ์ด์–ธํŠธ ์„ค์ •๊ฐ’ ๋ฐ ์™„๋ฃŒ ๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌ๋ฐ›์„ callbackURL
	clientID: 'clientID',
    clientSecret: 'clientSecret',
    callbackURL: 'callbackUrl',
};

...
// accessToken, RefreshToken์€ ๋‹ค๋ฅธ ๊ตฌ๊ธ€ API๋“ค์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ํ† ํฐ
new GoogleStrategy(config, (accessToken, refreshToken, profile, done) => {
	const { email, name } = profile._json; // profile: ์ „๋‹ฌ๋ฐ›์€ ์œ ์ € ์ •๋ณด, ์œ ์ € ์ƒ์„ฑ ๋˜๋Š” OAuth ์ •๋ณด ์—…๋ฐ์ดํŠธ์— ์‚ฌ์šฉ
    ..
    //create or update user
})

 

  • ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ์ด ์™„๋ฃŒ๋œ ํ›„ ๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š” ๋ถ€๋ถ„

2) passport-google-oauth20 ์‚ฌ์šฉ

passport.use(google);

// ---

// /auth/google ์ ‘๊ทผ ์‹œ ์ž๋™์œผ๋กœ ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋„˜์–ด๊ฐ
router.get('/google', passport.authenticate('google', {
	scope: ['profile', 'email']
}));

// ๋กœ๊ทธ์ธ ์™„๋ฃŒ ํ›„ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ config์— ์„ค์ •๋œ ์ฃผ์†Œ์ธ /auth/google/callback์œผ๋กœ ์ „๋‹ฌ
// ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋Š” strategy์—์„œ ์ฒ˜๋ฆฌ, ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด request handler ์‹คํ–‰
router.get('/google/callback', passport.authenticate('google', {
	failureRedirect: '/login'
}), (req, res, next) => {
	res.redirect('/');
});

 

5. Nginx

  • ์›น ์„œ๋ฒ„ ์†Œํ”„ํŠธ์›จ์–ด (HTTP ์š”์ฒญ์„ ๋ฐ›์•„์„œ ํŒŒ์ผ์ด๋‚˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ HTTP ์‘๋‹ต์œผ๋กœ ๋ณด๋‚ด์ฃผ๋Š” ์†Œํ”„ํŠธ์›จ์–ด)
  • Node.js๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ HTTP ์š”์ฒญ์„ ์ˆ˜์‹ ํ•˜๊ณ  ์‘๋‹ตํ•˜๋Š” ๊ธฐ๋Šฅ์ด ์ด๋ฏธ ์กด์žฌ
  • ํ•˜์ง€๋งŒ, Node.js ๋‹จ๋…์œผ๋กœ production-level ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜๋Š” ์—†๋‹ค.
  • Nginx์˜ reverse-proxy ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด์„œ Node.js์™€ Nginx๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Œ
// http://www.example.com์œผ๋กœ ์ ‘์†ํ•œ ๋ชจ๋“  ์š”์ฒญ์„ localhost:3000์œผ๋กœ ์ „๋‹ฌํ•˜๋Š” ์„ค์ • ํŒŒ์ผ
server {
 listen 80;
 server_name www.example.com;
 
 location / {
 	proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
 }
}