本文目录导读:

我来为您提供一个完整的Spring Cloud OAuth2认证案例。
项目结构
oauth2-demo
├── auth-server # 认证服务器(端口:9000)
├── resource-server # 资源服务器(端口:9001)
├── gateway-server # API网关(端口:8080)
└── user-service # 用户微服务(端口:9002)
认证服务器 (auth-server)
1 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.3.12.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>auth-server</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2 application.yml
server:
port: 9000
spring:
application:
name: auth-server
datasource:
url: jdbc:mysql://localhost:3306/oauth2_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
cloud:
nacos:
discovery:
server-addr: localhost:8848
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.authserver.entity
logging:
level:
com.example.authserver: DEBUG
3 认证服务器配置
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.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.token.DefaultTokenServices;
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.redis.RedisTokenStore;
import javax.sql.DataSource;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private UserServiceDetail userServiceDetail;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("oauth2-jwt-secret");
return converter;
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenEnhancer(jwtAccessTokenConverter());
tokenServices.setAccessTokenValiditySeconds(3600); // 1小时
tokenServices.setRefreshTokenValiditySeconds(86400); // 24小时
return tokenServices;
}
@Bean
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.tokenServices(tokenServices())
.userDetailsService(userServiceDetail)
.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter());
}
}
4 安全管理配置
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.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceDetail userServiceDetail;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceDetail)
.passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/oauth/**", "/login", "/logout", "/actuator/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/");
}
}
5 用户服务实现
package com.example.authserver.service;
import com.example.authserver.entity.User;
import com.example.authserver.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.ArrayList;
import java.util.List;
@Service
public class UserServiceDetail implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// 从数据库获取用户角色
userMapper.findRolesByUserId(user.getId()).forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
});
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.getEnabled(),
true, true, true,
authorities
);
}
}
6 用户实体类
package com.example.authserver.entity;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Long id;
private String username;
private String password;
private String email;
private String phone;
private Boolean enabled;
private Date createTime;
private Date updateTime;
// getter and setter 方法
// ...
}
7 数据库初始化脚本
-- 创建数据库
CREATE DATABASE IF NOT EXISTS oauth2_demo DEFAULT CHARACTER SET utf8mb4;
USE oauth2_demo;
-- OAuth2 客户端表
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)
);
-- 用户表
CREATE TABLE IF NOT EXISTS t_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
phone VARCHAR(20),
enabled BOOLEAN DEFAULT TRUE,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 角色表
CREATE TABLE IF NOT EXISTS t_role (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
role_name VARCHAR(50) UNIQUE NOT NULL,
description VARCHAR(255)
);
-- 用户角色关联表
CREATE TABLE IF NOT EXISTS t_user_role (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
FOREIGN KEY (user_id) REFERENCES t_user(id),
FOREIGN KEY (role_id) REFERENCES t_role(id)
);
-- 插入测试数据
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_app',
'resource-server',
'$2a$10$X.N8kF7IuHq1uM1xVhjTOu1SnXqE7Lc5Yh5QZ1V8HJzJ8W5Y2U1fG', -- 密码:secret (BCrypt加密)
'read,write',
'authorization_code,password,refresh_token,client_credentials,implicit',
'http://localhost:8080/login/oauth2/code/client',
'ROLE_CLIENT',
3600,
86400,
'true'
);
-- 插入用户 (密码:123456,BCrypt加密)
INSERT INTO t_user (username, password, email, phone, enabled)
VALUES ('admin', '$2a$10$hK8fCvQm4bKaFh3hJ3zqkO7X5DyFhLJnUgKxNA0Pj8nCkQ7Mv3W0S', 'admin@example.com', '13800138000', true);
INSERT INTO t_user (username, password, email, phone, enabled)
VALUES ('user', '$2a$10$hK8fCvQm4bKaFh3hJ3zqkO7X5DyFhLJnUgKxNA0Pj8nCkQ7Mv3W0S', 'user@example.com', '13800138001', true);
-- 插入角色
INSERT INTO t_role (role_name, description) VALUES ('ROLE_ADMIN', '管理员');
INSERT INTO t_role (role_name, description) VALUES ('ROLE_USER', '普通用户');
-- 关联用户和角色
INSERT INTO t_user_role (user_id, role_id) VALUES (1, 1); -- admin -> ROLE_ADMIN
INSERT INTO t_user_role (user_id, role_id) VALUES (2, 2); -- user -> ROLE_USER
资源服务器 (resource-server)
1 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.3.12.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>resource-server</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2 application.yml
server:
port: 9001
spring:
application:
name: resource-server
redis:
host: localhost
port: 6379
security:
oauth2:
client:
client-id: client_app
client-secret: secret
user-authorization-uri: http://localhost:9000/oauth/authorize
access-token-uri: http://localhost:9000/oauth/token
resource:
user-info-uri: http://localhost:9000/userinfo
token-info-uri: http://localhost:9000/oauth/check_token
# JWT 配置
jwt:
secret: oauth2-jwt-secret
management:
endpoints:
web:
exposure:
include: "*"
3 资源服务器配置
package com.example.resourceserver.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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.DefaultTokenServices;
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;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${jwt.secret}")
private String jwtSecret;
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(jwtSecret);
return converter;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId("resource-server")
.tokenStore(jwtTokenStore())
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated();
}
}
4 用户控制器
package com.example.resourceserver.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/public/info")
public Map<String, String> publicInfo() {
Map<String, String> info = new HashMap<>();
info.put("message", "This is public information");
return info;
}
@GetMapping("/user/info")
public Map<String, Object> userInfo(@AuthenticationPrincipal Authentication authentication) {
Map<String, Object> result = new HashMap<>();
result.put("username", authentication.getName());
result.put("authorities", authentication.getAuthorities());
result.put("message", "User information");
return result;
}
@GetMapping("/user/profile")
public Map<String, Object> userProfile(Principal principal) {
Map<String, Object> result = new HashMap<>();
result.put("profile", "User profile for " + principal.getName());
return result;
}
@GetMapping("/admin/management")
@PreAuthorize("hasRole('ADMIN')")
public Map<String, Object> adminManagement() {
Map<String, Object> result = new HashMap<>();
result.put("message", "Admin management access");
return result;
}
@GetMapping("/users")
public Map<String, Object> getUsers() {
Map<String, Object> result = new HashMap<>();
result.put("users", new String[]{"admin", "user", "guest"});
return result;
}
}
API 网关 (gateway-server)
1 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.3.12.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>gateway-server</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2 application.yml
server:
port: 8080
spring:
application:
name: gateway-server
redis:
host: localhost
port: 6379
cloud:
gateway:
routes:
- id: resource-server
uri: lb://resource-server
predicates:
- Path=/api/**
filters:
- StripPrefix=0
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@ipKeyResolver}"
security:
oauth2:
client:
client-id: client_app
client-secret: secret
access-token-uri: http://localhost:9000/oauth/token
user-authorization-uri: http://localhost:9000/oauth/authorize
resource:
user-info-uri: http://localhost:9000/userinfo
token-info-uri: http://localhost:9000/oauth/check_token
3 网关配置
package com.example.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectWebFilter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/oauth/**", "/login", "/actuator/**").permitAll()
.pathMatchers("/api/public/**").permitAll()
.pathMatchers("/api/admin/**").hasRole("ADMIN")
.pathMatchers("/api/**").authenticated()
.anyExchange().permitAll()
.and()
.oauth2Login();
return http.build();
}
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.addAllowedOrigin("*");
corsConfig.addAllowedHeader("*");
corsConfig.addAllowedMethod("*");
corsConfig.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
}
客户端调用示例
1 获取token
# 密码模式获取token curl -X POST "http://localhost:9000/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=password&username=admin&password=123456&scope=read&client_id=client_app&client_secret=secret" # 授权码模式 - 第一步:获取授权码 curl -X GET "http://localhost:9000/oauth/authorize?client_id=client_app&response_type=code&scope=read&redirect_uri=http://localhost:8080/login/oauth2/code/client" # 授权码模式 - 第二步:获取token curl -X POST "http://localhost:9000/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=http://localhost:8080/login/oauth2/code/client&client_id=client_app&client_secret=secret" # 客户端凭证模式 curl -X POST "http://localhost:9000/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=client_app&client_secret=secret" # 刷新token curl -X POST "http://localhost:9000/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token&refresh_token=REFRESH_TOKEN&client_id=client_app&client_secret=secret"
2 调用资源服务器
# 使用token调用受保护资源 curl -X GET "http://localhost:8080/api/user/info" \ -H "Authorization: Bearer ACCESS_TOKEN" # 调用管理接口 curl -X GET "http://localhost:8080/api/admin/management" \ -H "Authorization: Bearer ACCESS_TOKEN"
实际应用案例
1 前端登录流程
// 使用axios封装的认证服务
class AuthService {
constructor() {
this.api = axios.create({
baseURL: 'http://localhost:9000',
headers: {
'Content-Type': 'application/json'
}
});
}
async login(username, password) {
try {
const params = new URLSearchParams();
params.append('grant_type', 'password');
params.append('username', username);
params.append('password', password);
params.append('scope', 'read write');
const authHeaders = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa('client_app:secret')
}
};
const response = await this.api.post('/oauth/token', params, authHeaders);
if (response.data.access_token) {
localStorage.setItem('access_token', response.data.access_token);
localStorage.setItem('refresh_token', response.data.refresh_token);
return response.data;
}
return null;
} catch (error) {
console.error('Login failed:', error);
throw error;
}
}
async refreshToken() {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) return false;
try {
const params = new URLSearchParams();
params.append('grant_type', 'refresh_token');
params.append('refresh_token', refreshToken);
const authHeaders = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa('client_app:secret')
}
};
const response = await this.api.post('/oauth/token', params, authHeaders);
if (response.data.access_token) {
localStorage.setItem('access_token', response.data.access_token);
localStorage.setItem('refresh_token', response.data.refresh_token);
return true;
}
return false;
} catch (error) {
console.error('Refresh token failed:', error);
return false;
}
}
async logout() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
// 可选:调用服务器的注销接口
await this.api.post('/oauth/logout');
}
async getUserInfo(token) {
try {
const response = await this.api.get('/userinfo', {
headers: {
'Authorization': 'Bearer ' + token
}
});
return response.data;
} catch (error) {
console.error('Get user info failed:', error);
throw error;
}
}
}
class ApiInterceptor {
static attach(api) {
api.interceptors.request.use(
config => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
api.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const authService = new AuthService();
const refreshed = await authService.refreshToken();
if (refreshed) {
const newToken = localStorage.getItem('access_token');
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return api(originalRequest);
}
}
return Promise.reject(error);
}
);
}
}
2 REST API 使用示例
// 使用RestTemplate调用认证服务器
@Service
public class AdminClientService {
@Autowired
private RestTemplate restTemplate;
@Value("${auth-server.token-uri}")
private String tokenUri;
@Value("${auth-server.client-id}")
private String clientId;
@Value("${auth-server.client-secret}")
private String clientSecret;
public String getAdminToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(clientId, clientSecret);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "client_credentials");
params.add("scope", "read");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
try {
ResponseEntity<OAuth2TokenResponse> response = restTemplate.postForEntity(
tokenUri,
request,
OAuth2TokenResponse.class
);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody().getAccessToken();
}
} catch (Exception e) {
throw new RuntimeException("Failed to get admin token", e);
}
return null;
}
static class OAuth2TokenResponse {
private String accessToken;
private String tokenType;
private int expiresIn;
private String scope;
// getter and setter
}
}
安全最佳实践
1 自定义Token增强器
package com.example.authserver.config;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
// 添加自定义信息
additionalInfo.put("organization", "MyCompany");
additionalInfo.put("environment", "production");
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
additionalInfo.put("username", userDetails.getUsername());
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
2 动态权限配置
package com.example.resourceserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private UserPermissionService permissionService;
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
if (authentication == null || targetDomainObject == null || !(permission instanceof String)) {
return false;
}
// 获取用户权限
return permissionService.checkPermission(
authentication.getName(),
targetDomainObject.getClass().getSimpleName(),
permission.toString()
);
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
if (authentication == null || targetId == null || targetType == null || !(permission instanceof String)) {
return false;
}
return permissionService.checkPermission(
authentication.getName(),
targetType,
targetId,
permission.toString()
);
}
}
3 使用OAuth2 Feign客户端
@FeignClient(name = "resource-server", configuration = OAuth2FeignConfig.class)
public interface UserClient {
@GetMapping("/api/users")
List<User> getUsers();
@GetMapping("/api/users/{id}")
User getUserById(@PathVariable("id") Long id);
@GetMapping("/api/admin/users")
List<User> getAdminUsers();
}
public class OAuth2FeignConfig {
@Bean
public RequestInterceptor o