Spring?Security?Oauth2整合JWT的詳細步驟和核心配置
先說步驟:
在 Spring Security 中整合 OAuth2 與 JWT,可實現(xiàn)基于令牌的認證授權(quán)機制,適合分布式系統(tǒng)場景。以下是詳細的整合步驟和核心配置:
1. 依賴引入
在pom.xml中添加核心依賴(以 Spring Boot 為例):
<!-- Spring Security OAuth2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.8.RELEASE</version>
</dependency>
<!-- JWT支持 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Spring Web(用于測試接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 核心配置
2.1 JWT 工具類
用于生成、解析 JWT 令牌:
@Component
public class JwtTokenUtil {
// 密鑰(實際項目中需加密存儲)
private static final String SECRET = "your-secret-key";
// 過期時間(7天)
private static final long EXPIRATION = 604800000L;
// 生成JWT令牌
public String generateToken(String username) {
Date now = new Date();
Date expirationDate = new Date(now.getTime() + EXPIRATION);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
// 從令牌中獲取用戶名
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
// 驗證令牌有效性
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
2.2 OAuth2 配置(授權(quán)服務(wù)器)
配置授權(quán)服務(wù)器,指定 JWT 作為令牌格式:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
// 配置客戶端信息(如客戶端ID、密鑰、授權(quán)類型等)
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client-id") // 客戶端ID
.secret(passwordEncoder().encode("client-secret")) // 客戶端密鑰
.authorizedGrantTypes("password", "refresh_token") // 支持的授權(quán)類型
.scopes("read", "write") // 權(quán)限范圍
.accessTokenValiditySeconds(3600) // 訪問令牌過期時間
.refreshTokenValiditySeconds(86400); // 刷新令牌過期時間
}
// 配置令牌存儲(使用JWT)
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {
@Override
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
// 自定義JWT載荷(可選)
Map<String, Object> claims = new HashMap<>(accessToken.getAdditionalInformation());
claims.put("username", authentication.getName());
claims.put("authorities", authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
DefaultOAuth2AccessToken customToken = new DefaultOAuth2AccessToken(accessToken);
customToken.setAdditionalInformation(claims);
return super.encode(customToken, authentication);
}
};
converter.setSigningKey(jwtTokenUtil.SECRET); // 設(shè)置簽名密鑰
return converter;
}
// 配置令牌服務(wù)
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService());
services.setSupportRefreshToken(true);
services.setTokenStore(tokenStore());
services.setAccessTokenValiditySeconds(3600);
services.setRefreshTokenValiditySeconds(86400);
return services;
}
// JWT令牌存儲
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2.3 資源服務(wù)器配置
配置受保護的資源,驗證 JWT 令牌:
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
// 資源ID(需與授權(quán)服務(wù)器配置一致)
private static final String RESOURCE_ID = "resource-id";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId(RESOURCE_ID)
.tokenStore(tokenStore()); // 使用JWT令牌存儲
}
// 配置資源訪問規(guī)則
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll() // 公開接口
.antMatchers("/api/**").authenticated() // 需認證的接口
.antMatchers("/admin/**").hasRole("ADMIN"); // 需ADMIN角色
}
@Bean
public TokenStore tokenStore() {
// 配置JWT驗證
return new JwtTokenStore(new JwtAccessTokenConverter() {{
setSigningKey(jwtTokenUtil.SECRET);
}});
}
}
2.4 Spring Security 配置
配置用戶認證信息和密碼加密:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
public UserDetailsService userDetailsService() {
// 內(nèi)存用戶(實際項目中替換為數(shù)據(jù)庫查詢)
UserDetails user = User.withUsername("user")
.password(passwordEncoder().encode("123456"))
.roles("USER")
.build();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("123456"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3. 測試接口
編寫測試接口驗證效果:
@RestController
public class TestController {
// 公開接口
@GetMapping("/public/hello")
public String publicHello() {
return "Public Hello!";
}
// 需認證的接口
@GetMapping("/api/hello")
public String apiHello(Authentication authentication) {
return "API Hello, " + authentication.getName() + "!";
}
// 需ADMIN角色的接口
@GetMapping("/admin/hello")
public String adminHello(Authentication authentication) {
return "Admin Hello, " + authentication.getName() + "!";
}
}
4. 測試流程
4.1 獲取令牌
通過password模式請求授權(quán)服務(wù)器的令牌端點:
POST http://localhost:8080/oauth/token Content-Type: application/x-www-form-urlencoded Authorization: Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ= # client-id:client-secret的Base64編碼 grant_type=password&username=user&password=123456
返回結(jié)果(JWT 格式的 access_token):
{
"access_token": "eyJhbGciOiJIUzUxMiJ9...",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzUxMiJ9...",
"expires_in": 3599,
"scope": "read write"
}4.2 訪問受保護資源
使用獲取的access_token訪問接口:
GET http://localhost:8080/api/hello Authorization: Bearer eyJhbGciOiJIUzUxMiJ9...
返回:
API Hello, user!
關(guān)鍵注意事項
- 密鑰安全:JWT 簽名密鑰需妥善保管,避免硬編碼(可通過配置中心或環(huán)境變量注入)。
- 令牌過期:合理設(shè)置
access_token和refresh_token的過期時間,平衡安全性和用戶體驗。 - 自定義載荷:可在 JWT 中添加用戶角色、權(quán)限等信息,減少資源服務(wù)器查詢數(shù)據(jù)庫的次數(shù)。
- HTTPS:生產(chǎn)環(huán)境必須使用 HTTPS 傳輸令牌,防止中間人攻擊。
通過以上配置,即可實現(xiàn) Spring Security OAuth2 與 JWT 的整合,支持基于令牌的認證授權(quán)。
在來講幾個簡單的概念:
JWT 令牌包含了用戶信息(或其他數(shù)據(jù)),并通過數(shù)字簽名來保證這些信息的完整性和真實性。
具體來說,JWT 令牌由三部分組成(用 . 分隔):
- 頭部(Header):說明簽名算法(比如 HMAC、RSA 等)。
- 載荷(Payload):存放實際要傳遞的信息(比如用戶 ID、角色、過期時間等,這些就是 “用戶信息”)。
- 簽名(Signature):用頭部指定的算法,結(jié)合服務(wù)器的密鑰(或私鑰),對 “頭部 + 載荷” 進行加密生成的數(shù)字簽名。
所以,JWT 不只是數(shù)字簽名,而是 “信息 + 簽名” 的組合體。簽名的作用是:
- 證明載荷里的信息沒被篡改(因為篡改后簽名會失效);
- 證明信息確實來自可信的服務(wù)器(因為只有服務(wù)器有密鑰能生成正確的簽名)。
簡單講,JWT 就像一封 “帶防偽簽名的信”:信里寫了內(nèi)容(用戶信息),信封上的簽名(數(shù)字簽名)保證信沒被拆過、沒被改,且確實是發(fā)件人(服務(wù)器)發(fā)的。
JWT可以使用HMAC算法或使用RSA的公鑰/私鑰對來簽名,防止被篡改。 官網(wǎng):https://jwt.io/
JWT 令牌的優(yōu)點:
jwt 基于json,非常方便解析。
可以在令牌中自定義豐富的內(nèi)容,易擴展。
通過非對稱加密算法及數(shù)字簽名技術(shù),JWT防止篡改,安全性高。
資源服務(wù)使用JWT可不依賴認證服務(wù)即可完成授權(quán)。
缺點:
JWT 令牌較長,占存儲空間比較大
總結(jié)
到此這篇關(guān)于Spring Security Oauth2整合JWT的詳細步驟和核心配置的文章就介紹到這了,更多相關(guān)Spring Security Oauth2整合JWT內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Java實現(xiàn)文件流轉(zhuǎn)base64
這篇文章主要為大家詳細介紹了如何使用Java實現(xiàn)文件流轉(zhuǎn)base64效果,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
Java中ByteArrayInputStream和ByteArrayOutputStream用法詳解
這篇文章主要介紹了Java中ByteArrayInputStream和ByteArrayOutputStream用法詳解,?ByteArrayInputStream?的內(nèi)部額外的定義了一個計數(shù)器,它被用來跟蹤?read()?方法要讀取的下一個字節(jié)2022-06-06
Vue3源碼解讀effectScope API及實現(xiàn)原理
這篇文章主要為大家介紹了Vue3源碼解讀effectScope API及實現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03
SpringDataElasticsearch與SpEL表達式實現(xiàn)ES動態(tài)索引
這篇文章主要介紹了SpringDataElasticsearch與SpEL表達式實現(xiàn)ES動態(tài)索引,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-09-09
Java中的緩沖區(qū)(Buffer)及其作用是什么詳解
緩沖區(qū)用于提升I/O效率,Java提供字符流、字節(jié)流及ByteBuffer類型,擁有capacity、position等屬性,這篇文章主要介紹了Java中的緩沖區(qū)(Buffer)及其作用是什么的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2025-05-05

