oauth-implementation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OAuth Implementation

OAuth 实现

Overview

概述

Implement industry-standard OAuth 2.0 and OpenID Connect authentication flows with JWT tokens, refresh tokens, and secure session management.
实现符合行业标准的OAuth 2.0和OpenID Connect认证流程,包含JWT令牌、刷新令牌以及安全会话管理功能。

When to Use

适用场景

  • User authentication systems
  • Third-party API integration
  • Single Sign-On (SSO) implementation
  • Mobile app authentication
  • Microservices security
  • Social login integration
  • 用户认证系统
  • 第三方API集成
  • 单点登录(SSO)实现
  • 移动应用认证
  • 微服务安全
  • 社交登录集成

Implementation Examples

实现示例

1. Node.js OAuth 2.0 Server

1. Node.js OAuth 2.0 服务器

javascript
// oauth-server.js - Complete OAuth 2.0 implementation
const express = require('express');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const bcrypt = require('bcrypt');

class OAuthServer {
  constructor() {
    this.app = express();
    this.clients = new Map();
    this.authorizationCodes = new Map();
    this.refreshTokens = new Map();
    this.accessTokens = new Map();

    // JWT signing keys
    this.privateKey = process.env.JWT_PRIVATE_KEY;
    this.publicKey = process.env.JWT_PUBLIC_KEY;

    this.setupRoutes();
  }

  // Register OAuth client
  registerClient(clientId, clientSecret, redirectUris) {
    this.clients.set(clientId, {
      clientSecret: bcrypt.hashSync(clientSecret, 10),
      redirectUris,
      grants: ['authorization_code', 'refresh_token']
    });
  }

  setupRoutes() {
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));

    // Authorization endpoint
    this.app.get('/oauth/authorize', (req, res) => {
      const { client_id, redirect_uri, response_type, scope, state } = req.query;

      // Validate client
      if (!this.clients.has(client_id)) {
        return res.status(400).json({ error: 'invalid_client' });
      }

      const client = this.clients.get(client_id);

      // Validate redirect URI
      if (!client.redirectUris.includes(redirect_uri)) {
        return res.status(400).json({ error: 'invalid_redirect_uri' });
      }

      // Validate response type
      if (response_type !== 'code') {
        return res.status(400).json({ error: 'unsupported_response_type' });
      }

      // Generate authorization code
      const code = crypto.randomBytes(32).toString('hex');

      this.authorizationCodes.set(code, {
        clientId: client_id,
        redirectUri: redirect_uri,
        scope: scope || 'read',
        userId: req.user?.id, // From session
        expiresAt: Date.now() + 600000 // 10 minutes
      });

      // Redirect with authorization code
      const redirectUrl = new URL(redirect_uri);
      redirectUrl.searchParams.set('code', code);
      if (state) redirectUrl.searchParams.set('state', state);

      res.redirect(redirectUrl.toString());
    });

    // Token endpoint
    this.app.post('/oauth/token', async (req, res) => {
      const { grant_type, code, refresh_token, client_id, client_secret, redirect_uri } = req.body;

      // Validate client credentials
      const client = this.clients.get(client_id);
      if (!client || !bcrypt.compareSync(client_secret, client.clientSecret)) {
        return res.status(401).json({ error: 'invalid_client' });
      }

      if (grant_type === 'authorization_code') {
        return this.handleAuthorizationCodeGrant(req, res, code, client_id, redirect_uri);
      } else if (grant_type === 'refresh_token') {
        return this.handleRefreshTokenGrant(req, res, refresh_token, client_id);
      }

      res.status(400).json({ error: 'unsupported_grant_type' });
    });

    // Token introspection endpoint
    this.app.post('/oauth/introspect', (req, res) => {
      const { token } = req.body;

      try {
        const decoded = jwt.verify(token, this.publicKey, { algorithms: ['RS256'] });

        res.json({
          active: true,
          scope: decoded.scope,
          client_id: decoded.client_id,
          user_id: decoded.sub,
          exp: decoded.exp
        });
      } catch (error) {
        res.json({ active: false });
      }
    });

    // Token revocation endpoint
    this.app.post('/oauth/revoke', (req, res) => {
      const { token, token_type_hint } = req.body;

      if (token_type_hint === 'refresh_token') {
        this.refreshTokens.delete(token);
      } else {
        this.accessTokens.delete(token);
      }

      res.status(200).json({ success: true });
    });
  }

  handleAuthorizationCodeGrant(req, res, code, clientId, redirectUri) {
    const authCode = this.authorizationCodes.get(code);

    if (!authCode) {
      return res.status(400).json({ error: 'invalid_grant' });
    }

    // Validate authorization code
    if (authCode.clientId !== clientId || authCode.redirectUri !== redirectUri) {
      return res.status(400).json({ error: 'invalid_grant' });
    }

    if (authCode.expiresAt < Date.now()) {
      this.authorizationCodes.delete(code);
      return res.status(400).json({ error: 'expired_grant' });
    }

    // Delete used authorization code
    this.authorizationCodes.delete(code);

    // Generate tokens
    const tokens = this.generateTokens(clientId, authCode.userId, authCode.scope);

    res.json(tokens);
  }

  handleRefreshTokenGrant(req, res, refreshToken, clientId) {
    const storedToken = this.refreshTokens.get(refreshToken);

    if (!storedToken || storedToken.clientId !== clientId) {
      return res.status(400).json({ error: 'invalid_grant' });
    }

    if (storedToken.expiresAt < Date.now()) {
      this.refreshTokens.delete(refreshToken);
      return res.status(400).json({ error: 'expired_refresh_token' });
    }

    // Generate new access token
    const tokens = this.generateTokens(clientId, storedToken.userId, storedToken.scope);

    res.json(tokens);
  }

  generateTokens(clientId, userId, scope) {
    // Generate access token (JWT)
    const accessToken = jwt.sign(
      {
        sub: userId,
        client_id: clientId,
        scope: scope,
        type: 'access_token'
      },
      this.privateKey,
      {
        algorithm: 'RS256',
        expiresIn: '1h',
        issuer: 'https://auth.example.com',
        audience: 'https://api.example.com'
      }
    );

    // Generate refresh token
    const refreshToken = crypto.randomBytes(64).toString('hex');

    this.refreshTokens.set(refreshToken, {
      clientId,
      userId,
      scope,
      expiresAt: Date.now() + 2592000000 // 30 days
    });

    return {
      access_token: accessToken,
      token_type: 'Bearer',
      expires_in: 3600,
      refresh_token: refreshToken,
      scope: scope
    };
  }

  // Middleware to protect routes
  authenticate() {
    return (req, res, next) => {
      const authHeader = req.headers.authorization;

      if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({ error: 'missing_token' });
      }

      const token = authHeader.substring(7);

      try {
        const decoded = jwt.verify(token, this.publicKey, {
          algorithms: ['RS256'],
          issuer: 'https://auth.example.com',
          audience: 'https://api.example.com'
        });

        req.user = {
          id: decoded.sub,
          clientId: decoded.client_id,
          scope: decoded.scope
        };

        next();
      } catch (error) {
        if (error.name === 'TokenExpiredError') {
          return res.status(401).json({ error: 'token_expired' });
        }
        return res.status(401).json({ error: 'invalid_token' });
      }
    };
  }

  start(port = 3000) {
    this.app.listen(port, () => {
      console.log(`OAuth server running on port ${port}`);
    });
  }
}

// Usage
const oauthServer = new OAuthServer();

// Register OAuth client
oauthServer.registerClient(
  'client-app-123',
  'super-secret-key',
  ['https://myapp.com/callback']
);

// Protected API endpoint
oauthServer.app.get('/api/user/profile',
  oauthServer.authenticate(),
  (req, res) => {
    res.json({
      userId: req.user.id,
      scope: req.user.scope
    });
  }
);

oauthServer.start(3000);
javascript
// oauth-server.js - Complete OAuth 2.0 implementation
const express = require('express');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const bcrypt = require('bcrypt');

class OAuthServer {
  constructor() {
    this.app = express();
    this.clients = new Map();
    this.authorizationCodes = new Map();
    this.refreshTokens = new Map();
    this.accessTokens = new Map();

    // JWT signing keys
    this.privateKey = process.env.JWT_PRIVATE_KEY;
    this.publicKey = process.env.JWT_PUBLIC_KEY;

    this.setupRoutes();
  }

  // Register OAuth client
  registerClient(clientId, clientSecret, redirectUris) {
    this.clients.set(clientId, {
      clientSecret: bcrypt.hashSync(clientSecret, 10),
      redirectUris,
      grants: ['authorization_code', 'refresh_token']
    });
  }

  setupRoutes() {
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));

    // Authorization endpoint
    this.app.get('/oauth/authorize', (req, res) => {
      const { client_id, redirect_uri, response_type, scope, state } = req.query;

      // Validate client
      if (!this.clients.has(client_id)) {
        return res.status(400).json({ error: 'invalid_client' });
      }

      const client = this.clients.get(client_id);

      // Validate redirect URI
      if (!client.redirectUris.includes(redirect_uri)) {
        return res.status(400).json({ error: 'invalid_redirect_uri' });
      }

      // Validate response type
      if (response_type !== 'code') {
        return res.status(400).json({ error: 'unsupported_response_type' });
      }

      // Generate authorization code
      const code = crypto.randomBytes(32).toString('hex');

      this.authorizationCodes.set(code, {
        clientId: client_id,
        redirectUri: redirect_uri,
        scope: scope || 'read',
        userId: req.user?.id, // From session
        expiresAt: Date.now() + 600000 // 10 minutes
      });

      // Redirect with authorization code
      const redirectUrl = new URL(redirect_uri);
      redirectUrl.searchParams.set('code', code);
      if (state) redirectUrl.searchParams.set('state', state);

      res.redirect(redirectUrl.toString());
    });

    // Token endpoint
    this.app.post('/oauth/token', async (req, res) => {
      const { grant_type, code, refresh_token, client_id, client_secret, redirect_uri } = req.body;

      // Validate client credentials
      const client = this.clients.get(client_id);
      if (!client || !bcrypt.compareSync(client_secret, client.clientSecret)) {
        return res.status(401).json({ error: 'invalid_client' });
      }

      if (grant_type === 'authorization_code') {
        return this.handleAuthorizationCodeGrant(req, res, code, client_id, redirect_uri);
      } else if (grant_type === 'refresh_token') {
        return this.handleRefreshTokenGrant(req, res, refresh_token, client_id);
      }

      res.status(400).json({ error: 'unsupported_grant_type' });
    });

    // Token introspection endpoint
    this.app.post('/oauth/introspect', (req, res) => {
      const { token } = req.body;

      try {
        const decoded = jwt.verify(token, this.publicKey, { algorithms: ['RS256'] });

        res.json({
          active: true,
          scope: decoded.scope,
          client_id: decoded.client_id,
          user_id: decoded.sub,
          exp: decoded.exp
        });
      } catch (error) {
        res.json({ active: false });
      }
    });

    // Token revocation endpoint
    this.app.post('/oauth/revoke', (req, res) => {
      const { token, token_type_hint } = req.body;

      if (token_type_hint === 'refresh_token') {
        this.refreshTokens.delete(token);
      } else {
        this.accessTokens.delete(token);
      }

      res.status(200).json({ success: true });
    });
  }

  handleAuthorizationCodeGrant(req, res, code, clientId, redirectUri) {
    const authCode = this.authorizationCodes.get(code);

    if (!authCode) {
      return res.status(400).json({ error: 'invalid_grant' });
    }

    // Validate authorization code
    if (authCode.clientId !== clientId || authCode.redirectUri !== redirectUri) {
      return res.status(400).json({ error: 'invalid_grant' });
    }

    if (authCode.expiresAt < Date.now()) {
      this.authorizationCodes.delete(code);
      return res.status(400).json({ error: 'expired_grant' });
    }

    // Delete used authorization code
    this.authorizationCodes.delete(code);

    // Generate tokens
    const tokens = this.generateTokens(clientId, authCode.userId, authCode.scope);

    res.json(tokens);
  }

  handleRefreshTokenGrant(req, res, refreshToken, clientId) {
    const storedToken = this.refreshTokens.get(refreshToken);

    if (!storedToken || storedToken.clientId !== clientId) {
      return res.status(400).json({ error: 'invalid_grant' });
    }

    if (storedToken.expiresAt < Date.now()) {
      this.refreshTokens.delete(refreshToken);
      return res.status(400).json({ error: 'expired_refresh_token' });
    }

    // Generate new access token
    const tokens = this.generateTokens(clientId, storedToken.userId, storedToken.scope);

    res.json(tokens);
  }

  generateTokens(clientId, userId, scope) {
    // Generate access token (JWT)
    const accessToken = jwt.sign(
      {
        sub: userId,
        client_id: clientId,
        scope: scope,
        type: 'access_token'
      },
      this.privateKey,
      {
        algorithm: 'RS256',
        expiresIn: '1h',
        issuer: 'https://auth.example.com',
        audience: 'https://api.example.com'
      }
    );

    // Generate refresh token
    const refreshToken = crypto.randomBytes(64).toString('hex');

    this.refreshTokens.set(refreshToken, {
      clientId,
      userId,
      scope,
      expiresAt: Date.now() + 2592000000 // 30 days
    });

    return {
      access_token: accessToken,
      token_type: 'Bearer',
      expires_in: 3600,
      refresh_token: refreshToken,
      scope: scope
    };
  }

  // Middleware to protect routes
  authenticate() {
    return (req, res, next) => {
      const authHeader = req.headers.authorization;

      if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({ error: 'missing_token' });
      }

      const token = authHeader.substring(7);

      try {
        const decoded = jwt.verify(token, this.publicKey, {
          algorithms: ['RS256'],
          issuer: 'https://auth.example.com',
          audience: 'https://api.example.com'
        });

        req.user = {
          id: decoded.sub,
          clientId: decoded.client_id,
          scope: decoded.scope
        };

        next();
      } catch (error) {
        if (error.name === 'TokenExpiredError') {
          return res.status(401).json({ error: 'token_expired' });
        }
        return res.status(401).json({ error: 'invalid_token' });
      }
    };
  }

  start(port = 3000) {
    this.app.listen(port, () => {
      console.log(`OAuth server running on port ${port}`);
    });
  }
}

// Usage
const oauthServer = new OAuthServer();

// Register OAuth client
oauthServer.registerClient(
  'client-app-123',
  'super-secret-key',
  ['https://myapp.com/callback']
);

// Protected API endpoint
oauthServer.app.get('/api/user/profile',
  oauthServer.authenticate(),
  (req, res) => {
    res.json({
      userId: req.user.id,
      scope: req.user.scope
    });
  }
);

oauthServer.start(3000);

2. Python OpenID Connect Implementation

2. Python OpenID Connect 实现

python
undefined
python
undefined

oidc_provider.py

oidc_provider.py

from flask import Flask, request, jsonify, redirect from authlib.integrations.flask_oauth2 import AuthorizationServer from authlib.integrations.flask_oauth2 import ResourceProtector from authlib.oauth2.rfc6749 import grants from authlib.jose import jwt import secrets import time from datetime import datetime, timedelta
app = Flask(name) app.config['SECRET_KEY'] = secrets.token_hex(32)
class OIDCProvider: def init(self): self.clients = {} self.authorization_codes = {} self.access_tokens = {} self.id_tokens = {}
    # RSA keys for JWT signing
    self.private_key = self._load_private_key()
    self.public_key = self._load_public_key()

def _load_private_key(self):
    # Load from environment or key management service
    return """-----BEGIN RSA PRIVATE KEY-----
    ... Your private key ...
    -----END RSA PRIVATE KEY-----"""

def _load_public_key(self):
    return """-----BEGIN PUBLIC KEY-----
    ... Your public key ...
    -----END PUBLIC KEY-----"""

def register_client(self, client_id, client_secret, redirect_uris, scopes):
    """Register OIDC client"""
    self.clients[client_id] = {
        'client_secret': client_secret,
        'redirect_uris': redirect_uris,
        'scopes': scopes,
        'response_types': ['code', 'id_token', 'token']
    }

def generate_id_token(self, user_id, client_id, nonce=None):
    """Generate OpenID Connect ID Token"""
    now = int(time.time())

    payload = {
        'iss': 'https://auth.example.com',
        'sub': user_id,
        'aud': client_id,
        'exp': now + 3600,
        'iat': now,
        'auth_time': now,
        'nonce': nonce
    }

    # Add optional claims
    payload.update({
        'email': f'{user_id}@example.com',
        'email_verified': True,
        'name': 'John Doe',
        'given_name': 'John',
        'family_name': 'Doe',
        'picture': 'https://example.com/avatar.jpg'
    })

    header = {'alg': 'RS256', 'typ': 'JWT'}

    return jwt.encode(header, payload, self.private_key)

def generate_access_token(self, user_id, client_id, scope):
    """Generate OAuth 2.0 access token"""
    token = secrets.token_urlsafe(32)

    self.access_tokens[token] = {
        'user_id': user_id,
        'client_id': client_id,
        'scope': scope,
        'expires_at': datetime.now() + timedelta(hours=1)
    }

    return token

def verify_token(self, token):
    """Verify JWT token"""
    try:
        claims = jwt.decode(token, self.public_key)
        claims.validate()
        return claims
    except Exception as e:
        return None
from flask import Flask, request, jsonify, redirect from authlib.integrations.flask_oauth2 import AuthorizationServer from authlib.integrations.flask_oauth2 import ResourceProtector from authlib.oauth2.rfc6749 import grants from authlib.jose import jwt import secrets import time from datetime import datetime, timedelta
app = Flask(name) app.config['SECRET_KEY'] = secrets.token_hex(32)
class OIDCProvider: def init(self): self.clients = {} self.authorization_codes = {} self.access_tokens = {} self.id_tokens = {}
    # RSA keys for JWT signing
    self.private_key = self._load_private_key()
    self.public_key = self._load_public_key()

def _load_private_key(self):
    # Load from environment or key management service
    return """-----BEGIN RSA PRIVATE KEY-----
    ... Your private key ...
    -----END RSA PRIVATE KEY-----"""

def _load_public_key(self):
    return """-----BEGIN PUBLIC KEY-----
    ... Your public key ...
    -----END PUBLIC KEY-----"""

def register_client(self, client_id, client_secret, redirect_uris, scopes):
    """Register OIDC client"""
    self.clients[client_id] = {
        'client_secret': client_secret,
        'redirect_uris': redirect_uris,
        'scopes': scopes,
        'response_types': ['code', 'id_token', 'token']
    }

def generate_id_token(self, user_id, client_id, nonce=None):
    """Generate OpenID Connect ID Token"""
    now = int(time.time())

    payload = {
        'iss': 'https://auth.example.com',
        'sub': user_id,
        'aud': client_id,
        'exp': now + 3600,
        'iat': now,
        'auth_time': now,
        'nonce': nonce
    }

    # Add optional claims
    payload.update({
        'email': f'{user_id}@example.com',
        'email_verified': True,
        'name': 'John Doe',
        'given_name': 'John',
        'family_name': 'Doe',
        'picture': 'https://example.com/avatar.jpg'
    })

    header = {'alg': 'RS256', 'typ': 'JWT'}

    return jwt.encode(header, payload, self.private_key)

def generate_access_token(self, user_id, client_id, scope):
    """Generate OAuth 2.0 access token"""
    token = secrets.token_urlsafe(32)

    self.access_tokens[token] = {
        'user_id': user_id,
        'client_id': client_id,
        'scope': scope,
        'expires_at': datetime.now() + timedelta(hours=1)
    }

    return token

def verify_token(self, token):
    """Verify JWT token"""
    try:
        claims = jwt.decode(token, self.public_key)
        claims.validate()
        return claims
    except Exception as e:
        return None

OIDC Endpoints

OIDC Endpoints

provider = OIDCProvider()
@app.route('/.well-known/openid-configuration') def openid_configuration(): """OpenID Connect Discovery endpoint""" return jsonify({ 'issuer': 'https://auth.example.com', 'authorization_endpoint': 'https://auth.example.com/oauth/authorize', 'token_endpoint': 'https://auth.example.com/oauth/token', 'userinfo_endpoint': 'https://auth.example.com/oauth/userinfo', 'jwks_uri': 'https://auth.example.com/.well-known/jwks.json', 'response_types_supported': ['code', 'id_token', 'token id_token'], 'subject_types_supported': ['public'], 'id_token_signing_alg_values_supported': ['RS256'], 'scopes_supported': ['openid', 'profile', 'email'], 'token_endpoint_auth_methods_supported': ['client_secret_basic', 'client_secret_post'], 'claims_supported': ['sub', 'iss', 'aud', 'exp', 'iat', 'name', 'email'] })
@app.route('/.well-known/jwks.json') def jwks(): """JSON Web Key Set endpoint""" # Return public key in JWK format return jsonify({ 'keys': [ { 'kty': 'RSA', 'use': 'sig', 'kid': '1', 'n': '...', # Public key modulus 'e': 'AQAB' } ] })
@app.route('/oauth/userinfo') def userinfo(): """UserInfo endpoint""" auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
    return jsonify({'error': 'invalid_token'}), 401

token = auth_header[7:]
claims = provider.verify_token(token)

if not claims:
    return jsonify({'error': 'invalid_token'}), 401

return jsonify({
    'sub': claims['sub'],
    'email': claims.get('email'),
    'name': claims.get('name'),
    'picture': claims.get('picture')
})
provider = OIDCProvider()
@app.route('/.well-known/openid-configuration') def openid_configuration(): """OpenID Connect Discovery endpoint""" return jsonify({ 'issuer': 'https://auth.example.com', 'authorization_endpoint': 'https://auth.example.com/oauth/authorize', 'token_endpoint': 'https://auth.example.com/oauth/token', 'userinfo_endpoint': 'https://auth.example.com/oauth/userinfo', 'jwks_uri': 'https://auth.example.com/.well-known/jwks.json', 'response_types_supported': ['code', 'id_token', 'token id_token'], 'subject_types_supported': ['public'], 'id_token_signing_alg_values_supported': ['RS256'], 'scopes_supported': ['openid', 'profile', 'email'], 'token_endpoint_auth_methods_supported': ['client_secret_basic', 'client_secret_post'], 'claims_supported': ['sub', 'iss', 'aud', 'exp', 'iat', 'name', 'email'] })
@app.route('/.well-known/jwks.json') def jwks(): """JSON Web Key Set endpoint""" # Return public key in JWK format return jsonify({ 'keys': [ { 'kty': 'RSA', 'use': 'sig', 'kid': '1', 'n': '...', # Public key modulus 'e': 'AQAB' } ] })
@app.route('/oauth/userinfo') def userinfo(): """UserInfo endpoint""" auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
    return jsonify({'error': 'invalid_token'}), 401

token = auth_header[7:]
claims = provider.verify_token(token)

if not claims:
    return jsonify({'error': 'invalid_token'}), 401

return jsonify({
    'sub': claims['sub'],
    'email': claims.get('email'),
    'name': claims.get('name'),
    'picture': claims.get('picture')
})

Register sample client

Register sample client

provider.register_client( 'sample-app', 'secret123', ['https://myapp.com/callback'], ['openid', 'profile', 'email'] )
if name == 'main': app.run(port=3000, debug=True)
undefined
provider.register_client( 'sample-app', 'secret123', ['https://myapp.com/callback'], ['openid', 'profile', 'email'] )
if name == 'main': app.run(port=3000, debug=True)
undefined

3. Java Spring Security OAuth

3. Java Spring Security OAuth

java
// OAuth2AuthorizationServerConfig.java
package com.example.oauth;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("client-app")
                .secret("{bcrypt}$2a$10$...")
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .scopes("read", "write")
                .redirectUris("https://myapp.com/callback")
                .accessTokenValiditySeconds(3600)
                .refreshTokenValiditySeconds(86400)
            .and()
            .withClient("mobile-app")
                .secret("{bcrypt}$2a$10$...")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("read")
                .accessTokenValiditySeconds(7200);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
            .tokenStore(tokenStore())
            .accessTokenConverter(accessTokenConverter());
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("secret-key"); // Use proper key management
        return converter;
    }
}
java
// OAuth2AuthorizationServerConfig.java
package com.example.oauth;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("client-app")
                .secret("{bcrypt}$2a$10$...")
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .scopes("read", "write")
                .redirectUris("https://myapp.com/callback")
                .accessTokenValiditySeconds(3600)
                .refreshTokenValiditySeconds(86400)
            .and()
            .withClient("mobile-app")
                .secret("{bcrypt}$2a$10$...")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("read")
                .accessTokenValiditySeconds(7200);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
            .tokenStore(tokenStore())
            .accessTokenConverter(accessTokenConverter());
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("secret-key"); // Use proper key management
        return converter;
    }
}

Best Practices

最佳实践

✅ DO

✅ 建议

  • Use PKCE for public clients
  • Implement token rotation
  • Store tokens securely
  • Use HTTPS everywhere
  • Validate redirect URIs
  • Implement rate limiting
  • Use short-lived access tokens
  • Log authentication events
  • 为公开客户端使用PKCE
  • 实现令牌轮换机制
  • 安全存储令牌
  • 全程使用HTTPS
  • 验证重定向URI
  • 实现速率限制
  • 使用短期访问令牌
  • 记录认证事件

❌ DON'T

❌ 禁止

  • Store tokens in localStorage
  • Use implicit flow
  • Skip state parameter
  • Expose client secrets
  • Allow open redirects
  • Use weak signing keys
  • 在localStorage中存储令牌
  • 使用隐式流程
  • 跳过state参数
  • 暴露客户端密钥
  • 允许开放重定向
  • 使用弱签名密钥

OAuth 2.0 Flows

OAuth 2.0 流程

  • Authorization Code: Web applications
  • PKCE: Mobile and SPA apps
  • Client Credentials: Service-to-service
  • Refresh Token: Token renewal
  • 授权码流程:Web应用
  • PKCE流程:移动应用和SPA应用
  • 客户端凭证流程:服务间通信
  • 刷新令牌流程:令牌续期

Resources

参考资源