阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 认证授权方案实现

认证授权方案实现

作者:陈川 阅读数:31337人阅读 分类: Node.js

认证与授权的基本概念

认证(Authentication)是验证用户身份的过程,授权(Authorization)则是确定用户是否有权限访问特定资源。在Express应用中,这两者通常结合使用来保护路由和API端点。常见的认证方式包括基于会话的认证、JWT(JSON Web Token)和OAuth,而授权通常通过角色或权限系统实现。

基于会话的认证实现

Express中可以使用express-session中间件实现基于会话的认证。首先需要安装依赖:

npm install express-session

然后配置中间件:

const express = require('express');
const session = require('express-session');

const app = express();

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false } // 生产环境应设为true并启用HTTPS
}));

用户登录时可以这样设置会话:

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  // 验证用户凭证(实际项目中应使用数据库验证)
  if (username === 'admin' && password === 'password') {
    req.session.user = { username, role: 'admin' };
    return res.send('登录成功');
  }
  
  res.status(401).send('认证失败');
});

保护路由的中间件示例:

function requireAuth(req, res, next) {
  if (req.session.user) {
    return next();
  }
  res.status(401).send('需要登录');
}

app.get('/protected', requireAuth, (req, res) => {
  res.send('这是受保护的内容');
});

JWT认证实现

JWT是一种无状态的认证机制,适合RESTful API。首先安装jsonwebtoken

npm install jsonwebtoken

生成和验证Token的示例:

const jwt = require('jsonwebtoken');
const secret = 'your-jwt-secret';

// 登录路由生成Token
app.post('/api/login', (req, res) => {
  const { username, password } = req.body;
  
  if (username === 'user' && password === 'pass') {
    const token = jwt.sign(
      { username, role: 'user' },
      secret,
      { expiresIn: '1h' }
    );
    return res.json({ token });
  }
  
  res.status(401).json({ error: '认证失败' });
});

// 验证Token的中间件
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) return res.sendStatus(401);
  
  jwt.verify(token, secret, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

// 受保护的路由
app.get('/api/protected', authenticateToken, (req, res) => {
  res.json({ message: `欢迎, ${req.user.username}` });
});

基于角色的授权控制

在认证基础上,可以添加角色检查实现授权:

// 角色检查中间件
function requireRole(role) {
  return (req, res, next) => {
    if (req.user?.role !== role) {
      return res.status(403).send('无权访问');
    }
    next();
  };
}

// 管理员专属路由
app.get('/admin/dashboard', 
  authenticateToken,
  requireRole('admin'),
  (req, res) => {
    res.send('管理员仪表板');
  }
);

OAuth2.0集成

Express可以集成Passport.js实现OAuth认证。以GitHub OAuth为例:

npm install passport passport-github2

配置示例:

const passport = require('passport');
const GitHubStrategy = require('passport-github2').Strategy;

passport.use(new GitHubStrategy({
    clientID: 'your-client-id',
    clientSecret: 'your-client-secret',
    callbackURL: 'http://localhost:3000/auth/github/callback'
  },
  (accessToken, refreshToken, profile, done) => {
    // 在此处查找或创建用户
    return done(null, profile);
  }
));

// 序列化和反序列化用户
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));

// 初始化Passport
app.use(passport.initialize());
app.use(passport.session());

// 认证路由
app.get('/auth/github', passport.authenticate('github'));

app.get('/auth/github/callback',
  passport.authenticate('github', { failureRedirect: '/login' }),
  (req, res) => res.redirect('/')
);

权限管理系统

更复杂的系统可能需要基于权限的访问控制(PBAC):

// 权限检查中间件
function checkPermission(permission) {
  return (req, res, next) => {
    const userPermissions = getUserPermissions(req.user.id); // 假设的函数
    
    if (!userPermissions.includes(permission)) {
      return res.status(403).send('无权执行此操作');
    }
    next();
  };
}

// 使用示例
app.delete('/api/posts/:id',
  authenticateToken,
  checkPermission('delete_post'),
  (req, res) => {
    // 删除文章逻辑
  }
);

安全最佳实践

  1. 始终使用HTTPS传输敏感数据
  2. 设置安全的Cookie属性:
app.use(session({
  // ...其他配置
  cookie: {
    secure: true,
    httpOnly: true,
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000 // 1天
  }
}));
  1. 密码存储应使用bcrypt等库进行哈希处理:
const bcrypt = require('bcrypt');
const saltRounds = 10;

// 注册时哈希密码
app.post('/register', async (req, res) => {
  const { username, password } = req.body;
  const hashedPassword = await bcrypt.hash(password, saltRounds);
  
  // 存储username和hashedPassword到数据库
});
  1. 实现速率限制防止暴力破解:
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 5 // 每个IP最多5次请求
});

app.use('/login', limiter);

错误处理

专门的认证错误处理中间件:

function authErrorHandler(err, req, res, next) {
  if (err.name === 'UnauthorizedError') {
    return res.status(401).json({ error: '无效的Token' });
  }
  if (err.name === 'ForbiddenError') {
    return res.status(403).json({ error: '无权访问' });
  }
  next(err);
}

app.use(authErrorHandler);

测试认证授权

使用Supertest进行端点测试:

const request = require('supertest');
const app = require('../app');

describe('认证测试', () => {
  it('未认证用户访问受保护路由应返回401', async () => {
    const res = await request(app).get('/protected');
    expect(res.statusCode).toEqual(401);
  });

  it('有效Token应允许访问', async () => {
    const loginRes = await request(app)
      .post('/login')
      .send({ username: 'user', password: 'pass' });
    
    const token = loginRes.body.token;
    
    const res = await request(app)
      .get('/protected')
      .set('Authorization', `Bearer ${token}`);
    
    expect(res.statusCode).toEqual(200);
  });
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌