Spring?Security?Oauth2整合JWT的詳細步驟和核心配置
先說步驟:
在 Spring Security 中整合 OAuth2 與 JWT,可實現(xià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 配置(授權服務器)
配置授權服務器,指定 JWT 作為令牌格式:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
// 配置客戶端信息(如客戶端ID、密鑰、授權類型等)
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client-id") // 客戶端ID
.secret(passwordEncoder().encode("client-secret")) // 客戶端密鑰
.authorizedGrantTypes("password", "refresh_token") // 支持的授權類型
.scopes("read", "write") // 權限范圍
.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); // 設置簽名密鑰
return converter;
}
// 配置令牌服務
@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 資源服務器配置
配置受保護的資源,驗證 JWT 令牌:
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
// 資源ID(需與授權服務器配置一致)
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模式請求授權服務器的令牌端點:
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
返回結果(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!
關鍵注意事項
- 密鑰安全:JWT 簽名密鑰需妥善保管,避免硬編碼(可通過配置中心或環(huán)境變量注入)。
- 令牌過期:合理設置
access_token和refresh_token的過期時間,平衡安全性和用戶體驗。 - 自定義載荷:可在 JWT 中添加用戶角色、權限等信息,減少資源服務器查詢數(shù)據(jù)庫的次數(shù)。
- HTTPS:生產(chǎn)環(huán)境必須使用 HTTPS 傳輸令牌,防止中間人攻擊。
通過以上配置,即可實現(xiàn) Spring Security OAuth2 與 JWT 的整合,支持基于令牌的認證授權。
在來講幾個簡單的概念:
JWT 令牌包含了用戶信息(或其他數(shù)據(jù)),并通過數(shù)字簽名來保證這些信息的完整性和真實性。
具體來說,JWT 令牌由三部分組成(用 . 分隔):
- 頭部(Header):說明簽名算法(比如 HMAC、RSA 等)。
- 載荷(Payload):存放實際要傳遞的信息(比如用戶 ID、角色、過期時間等,這些就是 “用戶信息”)。
- 簽名(Signature):用頭部指定的算法,結合服務器的密鑰(或私鑰),對 “頭部 + 載荷” 進行加密生成的數(shù)字簽名。
所以,JWT 不只是數(shù)字簽名,而是 “信息 + 簽名” 的組合體。簽名的作用是:
- 證明載荷里的信息沒被篡改(因為篡改后簽名會失效);
- 證明信息確實來自可信的服務器(因為只有服務器有密鑰能生成正確的簽名)。
簡單講,JWT 就像一封 “帶防偽簽名的信”:信里寫了內(nèi)容(用戶信息),信封上的簽名(數(shù)字簽名)保證信沒被拆過、沒被改,且確實是發(fā)件人(服務器)發(fā)的。
JWT可以使用HMAC算法或使用RSA的公鑰/私鑰對來簽名,防止被篡改。 官網(wǎng):https://jwt.io/
JWT 令牌的優(yōu)點:
jwt 基于json,非常方便解析。
可以在令牌中自定義豐富的內(nèi)容,易擴展。
通過非對稱加密算法及數(shù)字簽名技術,JWT防止篡改,安全性高。
資源服務使用JWT可不依賴認證服務即可完成授權。
缺點:
JWT 令牌較長,占存儲空間比較大
總結
到此這篇關于Spring Security Oauth2整合JWT的詳細步驟和核心配置的文章就介紹到這了,更多相關Spring Security Oauth2整合JWT內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
如何用Java來進行文件切割和簡單的內(nèi)容過濾的實現(xiàn)
這篇文章主要介紹了如何用Java來進行文件切割和簡單的內(nèi)容過濾的實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-01-01
spring @Scheduled注解的使用誤區(qū)及解決
這篇文章主要介紹了spring @Scheduled注解的使用誤區(qū)及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
基于java.lang.IllegalArgumentException異常報錯問題及解決
這篇文章主要介紹了基于java.lang.IllegalArgumentException異常報錯問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03

