SpringSecurity JWT基于令牌的無(wú)狀態(tài)認(rèn)證實(shí)現(xiàn)
引言
在微服務(wù)架構(gòu)與分布式系統(tǒng)日益普及的今天,傳統(tǒng)的基于會(huì)話(Session)的認(rèn)證方式面臨著諸多挑戰(zhàn)。JSON Web Token(JWT)作為一種基于令牌的認(rèn)證機(jī)制,因其無(wú)狀態(tài)、自包含以及易于跨服務(wù)傳遞的特性,已成為現(xiàn)代應(yīng)用認(rèn)證的優(yōu)選方案。Spring Security作為Java生態(tài)系統(tǒng)中最流行的安全框架,提供了對(duì)JWT的全面支持。本文將深入探討如何在Spring Security中實(shí)現(xiàn)基于JWT的無(wú)狀態(tài)認(rèn)證,包括令牌生成、驗(yàn)證、續(xù)期等核心環(huán)節(jié),幫助開(kāi)發(fā)者構(gòu)建安全、高效的身份認(rèn)證系統(tǒng)。通過(guò)采用JWT認(rèn)證,系統(tǒng)可以更好地支持水平擴(kuò)展、減輕服務(wù)器存儲(chǔ)負(fù)擔(dān),并簡(jiǎn)化跨服務(wù)認(rèn)證的復(fù)雜性。
一、JWT基本原理與結(jié)構(gòu)
JWT(JSON Web Token)是一種開(kāi)放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊且自包含的方式,用于在各方之間安全地傳輸信息。每個(gè)JWT由三部分組成:頭部(Header)、載荷(Payload)和簽名(Signature)。頭部描述令牌類(lèi)型和使用的算法,載荷包含需要傳遞的數(shù)據(jù)(如用戶ID、角色等),簽名則確保令牌的完整性和真實(shí)性。JWT的設(shè)計(jì)理念是服務(wù)端不存儲(chǔ)令牌狀態(tài),而是通過(guò)驗(yàn)證簽名和檢查內(nèi)置的過(guò)期時(shí)間來(lái)判斷令牌有效性,這種方式特別適合分布式系統(tǒng)和微服務(wù)架構(gòu)。
// JWT結(jié)構(gòu)示例 public class JwtStructure { // 頭部示例(實(shí)際使用時(shí)會(huì)進(jìn)行Base64URL編碼) String header = "{\n" + " \"alg\": \"HS256\",\n" + // 簽名算法 " \"typ\": \"JWT\"\n" + // 令牌類(lèi)型 "}"; // 載荷示例(實(shí)際使用時(shí)會(huì)進(jìn)行Base64URL編碼) String payload = "{\n" + " \"sub\": \"1234567890\",\n" + // 主題(通常是用戶ID) " \"name\": \"John Doe\",\n" + // 用戶名 " \"admin\": true,\n" + // 自定義聲明 " \"iat\": 1516239022,\n" + // 令牌簽發(fā)時(shí)間 " \"exp\": 1516242622\n" + // 令牌過(guò)期時(shí)間 "}"; // 簽名過(guò)程偽代碼 String signatureAlgorithm = "HMACSHA256"; String signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret ); // 最終的JWT形式:Header.Payload.Signature String jwt = base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + signature; }
二、Spring Security JWT依賴(lài)配置
實(shí)現(xiàn)JWT認(rèn)證首先需要引入相關(guān)依賴(lài)。主要包括Spring Security核心庫(kù)以及處理JWT的庫(kù),如jjwt
或java-jwt
。此外,還需要添加JSON處理庫(kù)以及Spring Boot相關(guān)依賴(lài)。在Spring Boot項(xiàng)目中,通過(guò)Maven或Gradle可以方便地管理這些依賴(lài)。配置好依賴(lài)后,可以進(jìn)一步設(shè)置JWT的參數(shù),如密鑰、令牌有效期等,這些通常在應(yīng)用的配置文件中定義。
// Maven依賴(lài)配置(pom.xml片段) public class Dependencies { String mavenDependencies = "<!-- Spring Boot安全依賴(lài) -->\n" + "<dependency>\n" + " <groupId>org.springframework.boot</groupId>\n" + " <artifactId>spring-boot-starter-security</artifactId>\n" + "</dependency>\n" + "\n" + "<!-- Spring Boot Web依賴(lài) -->\n" + "<dependency>\n" + " <groupId>org.springframework.boot</groupId>\n" + " <artifactId>spring-boot-starter-web</artifactId>\n" + "</dependency>\n" + "\n" + "<!-- JWT依賴(lài) - JJWT -->\n" + "<dependency>\n" + " <groupId>io.jsonwebtoken</groupId>\n" + " <artifactId>jjwt-api</artifactId>\n" + " <version>0.11.5</version>\n" + "</dependency>\n" + "<dependency>\n" + " <groupId>io.jsonwebtoken</groupId>\n" + " <artifactId>jjwt-impl</artifactId>\n" + " <version>0.11.5</version>\n" + " <scope>runtime</scope>\n" + "</dependency>\n" + "<dependency>\n" + " <groupId>io.jsonwebtoken</groupId>\n" + " <artifactId>jjwt-jackson</artifactId>\n" + " <version>0.11.5</version>\n" + " <scope>runtime</scope>\n" + "</dependency>"; // 應(yīng)用配置文件(application.yml片段) String applicationConfig = "jwt:\n" + " secret: mySecretKey123456789012345678901234567890\n" + " expiration: 86400000 # 24小時(shí),單位毫秒\n" + " header: Authorization\n" + " prefix: Bearer "; }
三、JWT令牌生成與處理
JWT令牌的生成是認(rèn)證流程的核心環(huán)節(jié)。在用戶成功通過(guò)身份驗(yàn)證后,系統(tǒng)需要?jiǎng)?chuàng)建包含用戶身份和權(quán)限信息的JWT,并將其發(fā)送給客戶端。令牌處理服務(wù)負(fù)責(zé)JWT的創(chuàng)建、簽名和驗(yàn)證等操作。通過(guò)合理封裝JWT操作,可以確保令牌的安全性和一致性。在實(shí)際項(xiàng)目中,通常將JWT相關(guān)操作封裝在專(zhuān)門(mén)的服務(wù)類(lèi)中,該類(lèi)負(fù)責(zé)令牌的生成、解析和驗(yàn)證。
@Service public class JwtTokenProvider { @Value("${jwt.secret}") private String jwtSecret; @Value("${jwt.expiration}") private long jwtExpiration; @Autowired private UserDetailsService userDetailsService; // 生成令牌 public String generateToken(Authentication authentication) { UserDetails userDetails = (UserDetails) authentication.getPrincipal(); Date now = new Date(); Date expiryDate = new Date(now.getTime() + jwtExpiration); return Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(now) .setExpiration(expiryDate) // 添加用戶角色信息 .claim("roles", userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())) // 可添加額外的自定義聲明 .claim("additional", "custom value") // 使用HS512算法和密鑰簽名JWT .signWith(Keys.hmacShaKeyFor(jwtSecret.getBytes()), SignatureAlgorithm.HS512) .compact(); } // 從令牌中獲取用戶名 public String getUsernameFromToken(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(jwtSecret.getBytes())) .build() .parseClaimsJws(token) .getBody(); return claims.getSubject(); } // 獲取令牌中的所有聲明 public Claims getAllClaimsFromToken(String token) { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(jwtSecret.getBytes())) .build() .parseClaimsJws(token) .getBody(); } // 驗(yàn)證令牌 public boolean validateToken(String token) { try { Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(jwtSecret.getBytes())) .build() .parseClaimsJws(token); return true; } catch (MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException e) { // 捕獲各種JWT異常并記錄日志 return false; } } // 從令牌解析認(rèn)證信息 public Authentication getAuthentication(String token) { String username = getUsernameFromToken(token); UserDetails userDetails = userDetailsService.loadUserByUsername(username); return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); } }
四、Spring Security配置與過(guò)濾器實(shí)現(xiàn)
在Spring Security中集成JWT認(rèn)證需要自定義安全配置和過(guò)濾器。首先,需要?jiǎng)?chuàng)建JWT認(rèn)證過(guò)濾器,攔截請(qǐng)求并驗(yàn)證JWT的有效性。其次,配置安全規(guī)則,定義哪些URL需要認(rèn)證,哪些可以匿名訪問(wèn)。最后,禁用會(huì)話管理,因?yàn)镴WT是無(wú)狀態(tài)的,不需要在服務(wù)器端維護(hù)會(huì)話。通過(guò)這些配置,可以將JWT認(rèn)證機(jī)制無(wú)縫集成到Spring Security框架中。
// JWT認(rèn)證過(guò)濾器 @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenProvider tokenProvider; @Value("${jwt.header}") private String tokenHeader; @Value("${jwt.prefix}") private String tokenPrefix; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { // 從請(qǐng)求中提取JWT String jwt = getJwtFromRequest(request); // 驗(yàn)證JWT是否存在且有效 if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { // 從JWT中獲取用戶認(rèn)證信息 Authentication authentication = tokenProvider.getAuthentication(jwt); // 將認(rèn)證信息設(shè)置到Spring Security上下文 SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception ex) { // 記錄解析JWT時(shí)的異常,但不中斷過(guò)濾器鏈 logger.error("Could not set user authentication in security context", ex); } // 繼續(xù)執(zhí)行過(guò)濾器鏈 filterChain.doFilter(request, response); } // 從請(qǐng)求頭中提取JWT private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader(tokenHeader); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(tokenPrefix)) { return bearerToken.substring(tokenPrefix.length()); } return null; } } // Spring Security配置 @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Autowired private UserDetailsService userDetailsService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 禁用CSRF保護(hù),因?yàn)镴WT是無(wú)狀態(tài)的 .csrf().disable() // 配置異常處理 .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> { response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("{\"error\":\"Unauthorized\",\"message\":\"" + authException.getMessage() + "\"}"); }) .and() // 禁用會(huì)話管理,使用JWT我們不需要會(huì)話 .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() // 配置請(qǐng)求授權(quán) .authorizeRequests() // 允許所有人訪問(wèn)登錄和注冊(cè)接口 .antMatchers("/api/auth/**").permitAll() // 允許所有人訪問(wèn)靜態(tài)資源 .antMatchers("/static/**").permitAll() // 所有其他請(qǐng)求需要認(rèn)證 .anyRequest().authenticated(); // 添加JWT過(guò)濾器 http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
五、認(rèn)證控制器與登錄流程實(shí)現(xiàn)
為實(shí)現(xiàn)完整的JWT認(rèn)證流程,需要?jiǎng)?chuàng)建認(rèn)證控制器處理登錄請(qǐng)求??刂破鹘邮沼脩魬{據(jù),驗(yàn)證身份后生成JWT令牌并返回給客戶端。此外,還可以實(shí)現(xiàn)刷新令牌、注銷(xiāo)等功能。在前后端分離的架構(gòu)中,控制器通常返回JSON格式的響應(yīng),包含令牌和基本用戶信息。
@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenProvider tokenProvider; @PostMapping("/login") public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { try { // 驗(yàn)證用戶憑據(jù) Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginRequest.getUsername(), loginRequest.getPassword() ) ); // 設(shè)置認(rèn)證信息到安全上下文 SecurityContextHolder.getContext().setAuthentication(authentication); // 生成JWT令牌 String jwt = tokenProvider.generateToken(authentication); // 獲取用戶詳情 UserDetails userDetails = (UserDetails) authentication.getPrincipal(); List<String> roles = userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()); // 構(gòu)建并返回響應(yīng) JwtAuthResponse response = new JwtAuthResponse(); response.setToken(jwt); response.setUsername(userDetails.getUsername()); response.setRoles(roles); return ResponseEntity.ok(response); } catch (BadCredentialsException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(new ErrorResponse("Invalid username or password")); } } // 用于刷新令牌的端點(diǎn) @PostMapping("/refresh") public ResponseEntity<?> refreshToken(@RequestBody TokenRefreshRequest request) { // 驗(yàn)證刷新令牌(實(shí)際項(xiàng)目中應(yīng)使用專(zhuān)門(mén)的刷新令牌) String requestRefreshToken = request.getRefreshToken(); try { // 驗(yàn)證刷新令牌有效性(簡(jiǎn)化示例) if (!tokenProvider.validateToken(requestRefreshToken)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(new ErrorResponse("Invalid refresh token")); } // 從刷新令牌中獲取用戶信息 String username = tokenProvider.getUsernameFromToken(requestRefreshToken); // 創(chuàng)建新的認(rèn)證對(duì)象 UserDetails userDetails = customUserDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); // 生成新的訪問(wèn)令牌 String newAccessToken = tokenProvider.generateToken(authentication); return ResponseEntity.ok(new JwtAuthResponse(newAccessToken, username, null)); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ErrorResponse("Could not refresh token")); } } } // 登錄請(qǐng)求DTO class LoginRequest { private String username; private String password; // getters and setters } // JWT認(rèn)證響應(yīng)DTO class JwtAuthResponse { private String token; private String type = "Bearer"; private String username; private List<String> roles; // constructors, getters and setters }
總結(jié)
基于JWT的無(wú)狀態(tài)認(rèn)證為現(xiàn)代應(yīng)用提供了高效、靈活的安全機(jī)制。通過(guò)Spring Security與JWT的結(jié)合,可以實(shí)現(xiàn)既符合標(biāo)準(zhǔn)又易于維護(hù)的認(rèn)證系統(tǒng)。JWT的無(wú)狀態(tài)特性使其特別適合微服務(wù)和分布式環(huán)境,無(wú)需在服務(wù)器端存儲(chǔ)會(huì)話狀態(tài),大大減輕了服務(wù)器負(fù)擔(dān),同時(shí)支持系統(tǒng)的水平擴(kuò)展。在實(shí)現(xiàn)過(guò)程中,關(guān)鍵環(huán)節(jié)包括JWT令牌的生成與驗(yàn)證、安全過(guò)濾器的配置、認(rèn)證流程的設(shè)計(jì)等。通過(guò)本文介紹的實(shí)現(xiàn)方法,開(kāi)發(fā)者可以構(gòu)建安全可靠的JWT認(rèn)證系統(tǒng),滿足現(xiàn)代應(yīng)用的認(rèn)證需求。需要注意的是,雖然JWT提供了許多優(yōu)勢(shì),但也存在一些局限,如令牌撤銷(xiāo)困難、令牌大小限制等。在實(shí)際項(xiàng)目中,應(yīng)根據(jù)具體需求選擇適合的認(rèn)證機(jī)制,并遵循安全最佳實(shí)踐,確保系統(tǒng)的安全性和可靠性。隨著應(yīng)用架構(gòu)的不斷演進(jìn),基于令牌的無(wú)狀態(tài)認(rèn)證將繼續(xù)發(fā)揮重要作用,成為構(gòu)建安全分布式系統(tǒng)的基礎(chǔ)。
到此這篇關(guān)于SpringSecurity JWT基于令牌的無(wú)狀態(tài)認(rèn)證實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity JWT令牌無(wú)狀態(tài)認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity6.0 如何通過(guò)JWTtoken進(jìn)行認(rèn)證授權(quán)
- springSecurity自定義登錄接口和JWT認(rèn)證過(guò)濾器的流程
- SpringSecurity+jwt+captcha登錄認(rèn)證授權(quán)流程總結(jié)
- SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringSecurity+jwt+redis基于數(shù)據(jù)庫(kù)登錄認(rèn)證的實(shí)現(xiàn)
- SpringBoot+SpringSecurity+JWT實(shí)現(xiàn)系統(tǒng)認(rèn)證與授權(quán)示例
- SpringBoot整合SpringSecurity實(shí)現(xiàn)JWT認(rèn)證的項(xiàng)目實(shí)踐
- SpringSecurity整合jwt權(quán)限認(rèn)證的全流程講解
- SpringSecurity構(gòu)建基于JWT的登錄認(rèn)證實(shí)現(xiàn)
相關(guān)文章
spring cloud 使用Eureka 進(jìn)行服務(wù)治理方法
這篇文章主要介紹了spring cloud 使用Eureka 進(jìn)行服務(wù)治理方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05JSP頁(yè)面無(wú)法識(shí)別EL表達(dá)式問(wèn)題解決方案
這篇文章主要介紹了JSP頁(yè)面無(wú)法識(shí)別EL表達(dá)式問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07Java中Lambda表達(dá)式之Lambda語(yǔ)法與作用域解析
這篇文章主要介紹了Java中Lambda表達(dá)式之Lambda語(yǔ)法與作用域解析重點(diǎn)介紹Lambda表達(dá)式基礎(chǔ)知識(shí),需要的朋友可以參考下2017-02-02解讀Mapper與Mapper.xml文件之間匹配的問(wèn)題
這篇文章主要介紹了解讀Mapper與Mapper.xml文件之間匹配的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01SpringBoot定義Bean的幾種實(shí)現(xiàn)方式
本文主要介紹了SpringBoot定義Bean的幾種實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05如何在springboot中配置和使用mybatis-plus
這篇文章主要給大家介紹了關(guān)于如何在springboot中配置和使用mybatis-plus的相關(guān)資料,MyBatis?Plus是MyBatis的增強(qiáng)版,旨在提供更多便捷的特性,減少開(kāi)發(fā)工作,同時(shí)保留了MyBatis的靈活性和強(qiáng)大性能,需要的朋友可以參考下2023-11-11Spark Streaming編程初級(jí)實(shí)踐詳解
這篇文章主要為大家介紹了Spark Streaming編程初級(jí)實(shí)踐詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04mall整合SpringSecurity及JWT實(shí)現(xiàn)認(rèn)證授權(quán)實(shí)戰(zhàn)
這篇文章主要為大家介紹了mall整合SpringSecurity及JWT實(shí)現(xiàn)認(rèn)證授權(quán)實(shí)戰(zhàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06