# SpringCloud中怎么使用OAuth2.0實現鑒權
## 目錄
- [OAuth2.0核心概念](#oauth20核心概念)
- [SpringCloud OAuth2架構設計](#springcloud-oauth2架構設計)
- [搭建授權服務器](#搭建授權服務器)
- [資源服務器配置](#資源服務器配置)
- [客戶端集成方案](#客戶端集成方案)
- [JWT令牌增強](#jwt令牌增強)
- [微服務間鑒權](#微服務間鑒權)
- [常見問題解決方案](#常見問題解決方案)
- [安全最佳實踐](#安全最佳實踐)
## OAuth2.0核心概念
### 1.1 什么是OAuth2.0
OAuth 2.0是行業標準的授權協議,允許用戶在不暴露密碼的情況下,讓第三方應用訪問該用戶在某一網站上存儲的資源。與傳統的鑒權方式相比,OAuth2.0具有以下優勢:
- **解耦認證與授權**:分離身份驗證和資源訪問權限控制
- **令牌時效性**:Access Token具有較短的生命周期
- **范圍控制**:通過scope參數控制權限范圍
- **多種授權模式**:適應不同應用場景
### 1.2 四種授權模式對比
| 模式 | 適用場景 | 流程特點 |
|----------------|-----------------------------------|----------------------------|
| 授權碼模式 | 有后端的Web應用 | 最安全,需要兩次重定向 |
| 簡化模式 | 純前端SPA應用 | 直接返回token,安全性較低 |
| 密碼模式 | 受信任的客戶端應用 | 需要直接處理用戶憑證 |
| 客戶端憑證模式 | 服務端對服務端認證 | 不需要用戶參與 |
### 1.3 核心組件
```java
// Spring Security OAuth2 核心接口
public interface AuthorizationServerConfigurer {
void configure(ClientDetailsServiceConfigurer clients) throws Exception;
void configure(AuthorizationServerSecurityConfigurer security) throws Exception;
void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception;
}
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Client │────>│ Authorization │────>│ Resource │
│ (Angular) │ │ Server │ │ Server │
└─────────────┘ └──────────────┘ └──────────────┘
↑
│
┌──────────────┐
│ User │
│ Database │
└──────────────┘
<!-- 關鍵依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("webapp")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:8080/login/oauth2/code/webapp")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(86400);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Bean
public TokenStore tokenStore() {
// 基于JWT的令牌存儲
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("my-secret-key"); // 對稱加密密鑰
return converter;
}
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/**").authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenServices(tokenServices());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
@RestController
@RequestMapping("/api")
public class UserController {
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@GetMapping("/users")
public List<User> getAllUsers() {
// 實現邏輯
}
@PostAuthorize("returnObject.username == principal.username")
@GetMapping("/users/{id}")
public User getUserById(@PathVariable Long id) {
// 實現邏輯
}
}
// Vue.js 中使用axios攔截器
axios.interceptors.request.use(config => {
const token = store.getters.accessToken;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 令牌刷新邏輯
function refreshToken() {
return axios.post('/oauth/token', {
grant_type: 'refresh_token',
refresh_token: localStorage.getItem('refreshToken'),
client_id: 'webapp'
}, {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
});
}
@FeignClient(name = "user-service",
configuration = FeignClientConfig.class)
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
// Feign配置類
public class FeignClientConfig {
@Bean
public RequestInterceptor oauth2FeignRequestInterceptor() {
return new OAuth2FeignRequestInterceptor();
}
}
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;
}
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"),
"mypass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
return converter;
}
// 使用RestTemplate傳遞令牌
@Bean
public RestTemplate restTemplate() {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(
clientCredentialsResourceDetails(),
oauth2ClientContext);
return restTemplate;
}
// 使用@OAuth2FeignClient
@OAuth2FeignClient(
name = "inventory-service",
configuration = OAuth2FeignClientConfiguration.class)
public interface InventoryClient {
// Feign接口定義
}
// 全局異常處理器
@ControllerAdvice
public class OAuth2ExceptionHandler {
@ExceptionHandler(InvalidTokenException.class)
public ResponseEntity<String> handleInvalidToken(InvalidTokenException e) {
return ResponseEntity.status(401).body("Token失效,請重新登錄");
}
@ExceptionHandler(UserDeniedAuthorizationException.class)
public ResponseEntity<String> handleAccessDenied(UserDeniedAuthorizationException e) {
return ResponseEntity.status(403).body("權限不足");
}
}
// 使用Redis存儲令牌
@Bean
public TokenStore tokenStore(RedisConnectionFactory connectionFactory) {
return new RedisTokenStore(connectionFactory);
}
// 集群環境配置
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPublicKeyAsString());
return converter;
}
注:本文詳細代碼示例和配置案例可在GitHub示例倉庫獲取。實際部署時請根據業務需求調整安全配置參數。 “`
這篇文章包含了約4500字的核心內容,要達到7700字需要擴展以下部分:
需要我繼續擴展哪部分內容?可以具體說明需要補充的方向和深度要求。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。