Spring Boot整合OAuth2案例

wen java案例 3

本文目录导读:

Spring Boot整合OAuth2案例

  1. 项目结构
  2. 授权服务器实现
  3. 资源服务器实现
  4. 客户端应用实现
  5. 测试用例
  6. 扩展功能

我将为您提供一个完整的Spring Boot整合OAuth2的案例,包括授权服务器和资源服务器的完整实现。

项目结构

spring-boot-oauth2/
├── auth-server/          # 授权服务器
├── resource-server/      # 资源服务器
└── client/               # 客户端应用

授权服务器实现

创建授权服务器项目

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>auth-server</artifactId>
    <version>1.0.0</version>
    <name>auth-server</name>
    <description>OAuth2 授权服务器</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Spring Security OAuth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.6.8</version>
        </dependency>
        <!-- JWT支持 -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.1.1.RELEASE</version>
        </dependency>
        <!-- 数据库 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

授权服务器主配置类

OAuth2AuthorizationServerConfig.java

package com.example.authserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import javax.sql.DataSource;
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private DataSource dataSource;
    // 客户端信息服务
    @Bean
    @Primary
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }
    // 令牌存储方式(使用JDBC存储或JWT)
    // 选择1:JDBC存储
    // @Bean
    // public TokenStore tokenStore() {
    //     return new JdbcTokenStore(dataSource);
    // }
    // 选择2:JWT令牌
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 使用对称密钥,生产环境应使用非对称密钥
        converter.setSigningKey("my-signing-key-123456789");
        return converter;
    }
    // 授权码服务
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    // 配置客户端信息
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
        // 也可以使用内存方式
        // clients.inMemory()
        //     .withClient("client")
        //     .secret(passwordEncoder.encode("secret"))
        //     .authorizedGrantTypes("authorization_code", "password", "refresh_token", "implicit")
        //     .scopes("read", "write")
        //     .redirectUris("http://localhost:8082/callback")
        //     .accessTokenValiditySeconds(3600)
        //     .refreshTokenValiditySeconds(86400);
    }
    // 配置令牌端点
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
            .authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService)
            .tokenStore(tokenStore())
            .tokenServices(tokenServices())
            .authorizationCodeServices(authorizationCodeServices())
            .accessTokenConverter(jwtAccessTokenConverter());
    }
    // 配置令牌服务
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setReuseRefreshToken(false);
        tokenServices.setAccessTokenValiditySeconds(3600);  // 1小时
        tokenServices.setRefreshTokenValiditySeconds(86400); // 24小时
        return tokenServices;
    }
    // 配置安全约束
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        // 允许客户端的表单认证
        security
            .tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()")
            .allowFormAuthenticationForClients();
    }
}

数据库初始化脚本

schema.sql

-- 创建客户端详情表
CREATE TABLE IF NOT EXISTS oauth_client_details (
    client_id VARCHAR(256) PRIMARY KEY,
    resource_ids VARCHAR(256),
    client_secret VARCHAR(256),
    scope VARCHAR(256),
    authorized_grant_types VARCHAR(256),
    web_server_redirect_uri VARCHAR(256),
    authorities VARCHAR(256),
    access_token_validity INTEGER,
    refresh_token_validity INTEGER,
    additional_information VARCHAR(4096),
    autoapprove VARCHAR(256)
);
-- 创建令牌存储表(如果使用JDBC存储方式)
CREATE TABLE IF NOT EXISTS oauth_access_token (
    token_id VARCHAR(256),
    token BLOB,
    authentication_id VARCHAR(256),
    user_name VARCHAR(256),
    client_id VARCHAR(256),
    authentication BLOB,
    refresh_token VARCHAR(256)
);
CREATE TABLE IF NOT EXISTS oauth_refresh_token (
    token_id VARCHAR(256),
    token BLOB,
    authentication BLOB
);
-- 创建授权码表
CREATE TABLE IF NOT EXISTS oauth_code (
    code VARCHAR(256),
    authentication BLOB
);
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(256) NOT NULL UNIQUE,
    password VARCHAR(256) NOT NULL,
    enabled BOOLEAN DEFAULT TRUE,
    account_non_expired BOOLEAN DEFAULT TRUE,
    account_non_locked BOOLEAN DEFAULT TRUE,
    credentials_non_expired BOOLEAN DEFAULT TRUE
);
-- 创建角色表
CREATE TABLE IF NOT EXISTS authorities (
    username VARCHAR(256) NOT NULL,
    authority VARCHAR(256) NOT NULL,
    PRIMARY KEY (username, authority),
    FOREIGN KEY (username) REFERENCES users (username)
);

data.sql

-- 初始化客户端信息
INSERT INTO oauth_client_details (
    client_id, 
    resource_ids, 
    client_secret, 
    scope, 
    authorized_grant_types, 
    web_server_redirect_uri, 
    authorities, 
    access_token_validity, 
    refresh_token_validity,
    autoapprove
) VALUES (
    'client',
    'resource-server',
    '$2a$10$XlW/e0pD5GXw4xW5X7M5YOuXyEnfIL5gQyGWwVKmXp1IcVpV/xqyO', -- 明文为: secret
    'read,write',
    'authorization_code,password,refresh_token,client_credentials,implicit',
    'http://localhost:8082/callback',
    'ROLE_CLIENT',
    3600,
    86400,
    'true'
);
-- 初始化用户
INSERT INTO users (username, password, enabled) VALUES 
('admin', '$2a$10$XlW/e0pD5GXw4xW5X7M5YOuXyEnfIL5gQyGWwVKmXp1IcVpV/xqyO', true); -- 密码: 123456
-- 添加角色
INSERT INTO authorities (username, authority) VALUES 
('admin', 'ROLE_ADMIN'),
('admin', 'ROLE_USER');

用户详情服务

UserDetailsServiceImpl.java

package com.example.authserver.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 查询用户
        List<String> user = jdbcTemplate.queryForList(
            "SELECT * FROM users WHERE username = ?", 
            new Object[]{username}, 
            String.class
        );
        if (user.isEmpty()) {
            throw new UsernameNotFoundException("用户不存在");
        }
        // 查询用户角色
        List<SimpleGrantedAuthority> authorities = jdbcTemplate.query(
            "SELECT authority FROM authorities WHERE username = ?",
            new Object[]{username},
            (rs, rowNum) -> new SimpleGrantedAuthority(rs.getString("authority"))
        );
        return new User(username, 
            jdbcTemplate.queryForObject(
                "SELECT password FROM users WHERE username = ?", 
                String.class, 
                username
            ),
            authorities
        );
    }
}

安全配置

SecurityConfig.java

package com.example.authserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/oauth/**", "/login", "/resources/**").permitAll()
                .antMatchers("/actuator/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .permitAll()
            .and()
            .logout()
                .permitAll();
    }
}

应用配置

application.yml

server:
  port: 8081
spring:
  application:
    name: auth-server
  datasource:
    url: jdbc:mysql://localhost:3306/oauth2_demo?useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialization-mode: always
    schema: classpath:db/schema.sql
    data: classpath:db/data.sql
  sql:
    init:
      mode: always
      schema-locations: classpath:db/schema.sql
      data-locations: classpath:db/data.sql
  jpa:
    hibernate:
      ddl-auto: none
    show-sql: true
logging:
  level:
    org.springframework.security: INFO
    org.springframework.web: INFO

资源服务器实现

资源服务器主配置

OAuth2ResourceServerConfig.java

package com.example.resourceserver.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Value("${security.oauth2.client.client-id}")
    private String clientId;
    @Value("${security.oauth2.client.client-secret}")
    private String clientSecret;
    @Value("${security.oauth2.authorization.check-token-access}")
    private String checkTokenEndpointUrl;
    @Bean
    public RemoteTokenServices tokenServices() {
        RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setClientId(clientId);
        tokenServices.setClientSecret(clientSecret);
        tokenServices.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
        return tokenServices;
    }
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
            .resourceId("resource-server")
            .tokenServices(tokenServices());
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                .antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated();
    }
}

资源API控制器

UserController.java

package com.example.resourceserver.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class UserController {
    @GetMapping("/public/message")
    public Map<String, String> publicMessage() {
        Map<String, String> response = new HashMap<>();
        response.put("message", "这是公开信息");
        return response;
    }
    @GetMapping("/user/info")
    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    public Map<String, Object> getUserInfo(Authentication authentication) {
        Map<String, Object> response = new HashMap<>();
        response.put("username", authentication.getName());
        response.put("authorities", authentication.getAuthorities());
        return response;
    }
    @GetMapping("/admin/manage")
    @PreAuthorize("hasRole('ADMIN')")
    public Map<String, String> adminManage() {
        Map<String, String> response = new HashMap<>();
        response.put("message", "这是管理员才能看到的信息");
        return response;
    }
    @GetMapping("/user/profile")
    public Map<String, Object> getUserProfile() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Map<String, Object> response = new HashMap<>();
        response.put("username", authentication.getName());
        response.put("timestamp", System.currentTimeMillis());
        return response;
    }
}

资源服务器配置

application.yml

server:
  port: 8082
spring:
  application:
    name: resource-server
security:
  oauth2:
    client:
      client-id: client
      client-secret: secret
    authorization:
      check-token-access: http://localhost:8081/oauth/check_token
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
logging:
  level:
    org.springframework.security: DEBUG

客户端应用实现

创建客户端应用

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- OAuth2 Client -->
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>2.6.8</version>
    </dependency>
    <!-- WebClient for calling resource server -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
</dependencies>

客户端控制器

ClientController.java

package com.example.client.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/client")
public class ClientController {
    @Autowired
    private OAuth2RestTemplate oAuth2RestTemplate;
    @GetMapping("/login")
    public Map<String, Object> login(
            @RequestParam String username, 
            @RequestParam String password,
            HttpSession session) {
        // 使用密码模式获取令牌
        ResourceOwnerPasswordResourceDetails resourceDetails = 
            new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setUsername(username);
        resourceDetails.setPassword(password);
        resourceDetails.setAccessTokenUri("http://localhost:8081/oauth/token");
        resourceDetails.setClientId("client");
        resourceDetails.setClientSecret("secret");
        resourceDetails.setGrantType("password");
        resourceDetails.setScope(Arrays.asList("read", "write"));
        OAuth2RestTemplate oauth2RestTemplate = new OAuth2RestTemplate(resourceDetails);
        OAuth2AccessToken accessToken = oauth2RestTemplate.getAccessToken();
        // 保存令牌到session
        session.setAttribute("accessToken", accessToken);
        Map<String, Object> response = new HashMap<>();
        response.put("success", true);
        response.put("accessToken", accessToken.getValue());
        response.put("expiresIn", accessToken.getExpiresIn());
        response.put("refreshToken", accessToken.getRefreshToken() != null ? 
            accessToken.getRefreshToken().getValue() : null);
        return response;
    }
    @GetMapping("/call-user-api")
    public String callUserApi(HttpServletRequest request) {
        try {
            // 调用资源服务器
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.setAuthorization("Bearer " + 
                ((OAuth2AccessToken) request.getSession().getAttribute("accessToken")).getValue());
            HttpEntity<String> entity = new HttpEntity<>(headers);
            ResponseEntity<String> response = oAuth2RestTemplate.exchange(
                "http://localhost:8082/api/user/info",
                HttpMethod.GET,
                entity,
                String.class
            );
            return "资源服务器响应: " + response.getBody();
        } catch (Exception e) {
            return "调用失败: " + e.getMessage();
        }
    }
    @GetMapping("/refresh-token")
    public Map<String, Object> refreshToken(HttpServletRequest request) {
        OAuth2AccessToken oldToken = 
            (OAuth2AccessToken) request.getSession().getAttribute("accessToken");
        // 刷新令牌
        OAuth2AccessToken newToken = oAuth2RestTemplate.refreshAccessToken();
        request.getSession().setAttribute("accessToken", newToken);
        Map<String, Object> response = new HashMap<>();
        response.put("success", true);
        response.put("newAccessToken", newToken.getValue());
        return response;
    }
}

客户端安全配置

SecurityConfig.java

package com.example.client.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import java.util.Arrays;
@Configuration
@EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public OAuth2RestTemplate oAuth2RestTemplate() {
        ClientCredentialsResourceDetails resourceDetails = 
            new ClientCredentialsResourceDetails();
        resourceDetails.setClientId("client");
        resourceDetails.setClientSecret("secret");
        resourceDetails.setAccessTokenUri("http://localhost:8081/oauth/token");
        resourceDetails.setScope(Arrays.asList("read", "write"));
        return new OAuth2RestTemplate(resourceDetails);
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/client/login", "/").permitAll()
                .anyRequest().authenticated()
            .and()
            .httpBasic();
    }
}

客户端配置

application.yml

server:
  port: 8083
spring:
  application:
    name: oauth2-client
security:
  oauth2:
    client:
      client-id: client
      client-secret: secret
      access-token-uri: http://localhost:8081/oauth/token
      user-authorization-uri: http://localhost:8081/oauth/authorize
      scope: read,write
    resource:
      user-info-uri: http://localhost:8082/api/user/info

测试用例

测试密码模式获取令牌

curl -X POST http://localhost:8081/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password&username=admin&password=123456&client_id=client&client_secret=secret&scope=read write"

测试客户端模式获取令牌

curl -X POST http://localhost:8081/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=client&client_secret=secret&scope=read"

测试刷新令牌

curl -X POST http://localhost:8081/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token&refresh_token=<REFRESH_TOKEN>&client_id=client&client_secret=secret"

测试访问资源服务器

curl http://localhost:8082/api/user/info \
  -H "Authorization: Bearer <ACCESS_TOKEN>"

扩展功能

JWT增强配置

@Configuration
public class JwtConfig {
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 使用非对称加密
        KeyPair keyPair = generateKeyPair();
        converter.setKeyPair(keyPair);
        return converter;
    }
    private KeyPair generateKeyPair() {
        // 生成RSA密钥对
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            return keyPairGenerator.generateKeyPair();
        } catch (Exception e) {
            throw new RuntimeException("生成密钥对失败", e);
        }
    }
}

添加自定义Token增强

@Component
public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(
            OAuth2AccessToken accessToken, 
            OAuth2Authentication authentication) {
        Map<String, Object> additionalInfo = new HashMap<>();
        additionalInfo.put("organization", authentication.getName());
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    }
}

使用Docker部署

docker-compose.yml

version: '3'
services:
  mysql:
    image: mysql:8.0
    container_name: oauth2-mysql
    environment:
      MYSQL_DATABASE: oauth2_demo
      MYSQL_ROOT_PASSWORD: root
    ports:
      - "3306:3306"
    volumes:
      - ./mysql-data:/var/lib/mysql
  auth-server:
    build:
      context: ./auth-server
    container_name: oauth2-auth-server
    ports:
      - "8081:8081"
    depends_on:
      - mysql
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/oauth2_demo
  resource-server:
    build:
      context: ./resource-server
    container_name: oauth2-resource-server
    ports:
      - "8082:8082"
    depends_on:
      - auth-server

这个完整的OAuth2实现包含了:

  1. 授权服务器:支持所有主流授权模式(密码、客户端、授权码、刷新令牌)
  2. 资源服务器:保护API资源,支持基于角色的访问控制
  3. 客户端应用:演示如何集成OAuth2认证

关键特性:

  • JWT令牌支持
  • 数据库存储客户端信息和令牌
  • 安全的密码加密(BCrypt)
  • 完善的错误处理
  • 可扩展架构

生产环境注意事项:

  • 使用非对称密钥加密JWT
  • 配置HTTPS
  • 实施适当的令牌撤销策略
  • 考虑使用Spring Authorization Server(新替代方案)

抱歉,评论功能暂时关闭!