使用spring security開發(fā)基于jwt驗證用戶身份的REST程序(Spring Boot)

假設(shè)我們已經(jīng)有了登錄注冊接口跨晴,接下來我們在 REST API 中使用 JWT 來驗證用戶身份呆细。我們將分幾個步驟來實現(xiàn)

技術(shù)棧

Spring Boot 3.4.1
Spring Security
jjwt 0.12.6


步驟 1:添加 JWT 依賴

首先,我們需要在 build.gradle 中添加相關(guān)的 JWT 依賴。使用 jjwt 庫來生成和驗證 JWT。

dependencies {
        implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
        runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
        runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
}

步驟 2:創(chuàng)建 JWT 工具類

然后哀峻,創(chuàng)建一個工具類來生成和解析 JWT。

package com.example.demo.util;

import io.jsonwebtoken.*;
import java.util.Date;


public class JwtUtil {
    private final String secretKey = "4D4A614E645267554B58703273357638756F423F4428472B4B6250653368566C";  // 替換為你的密鑰

    // 生成 JWT
    public String generateToken(String username) {
        return Jwts.builder()
                .subject(username)
                .issuedAt(new Date())
                .expiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    // 獲取用戶名
    public String extractUsername(String token) {
        return Jwts.parser()
                .setSigningKey(secretKey)
                .build()
                .parseSignedClaims(token)
                .getPayload()
                .getSubject();
    }

    // 驗證 token 是否有效
    public boolean isTokenExpired(String token) {
        return Jwts.parser()
                .setSigningKey(secretKey)
                .build()
                .parseSignedClaims(token)
                .getPayload()
                .getExpiration()
                .before(new Date());
    }

    // 解析 token
    public boolean validateToken(String token, String username) {
        return (username.equals(extractUsername(token)) && !isTokenExpired(token));
    }
}


步驟 3:修改登錄接口生成 JWT

在登錄接口中哲泊,當(dāng)用戶登錄成功后剩蟀,生成 JWT 并返回給客戶端。

package com.example.demo.controller;

import com.example.demo.model.User;
import com.example.demo.service.UserService;
import com.example.demo.util.JwtUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class AuthController {
    private final UserService userService;
    private final JwtUtil jwtUtil = new JwtUtil();

    public AuthController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody User user) {
        if (userService.loginUser(user.getUsername(), user.getPassword())) {
            String token = jwtUtil.generateToken(user.getUsername());
            return ResponseEntity.ok(new JwtResponse(token)); // 返回 token
        }
        return ResponseEntity.badRequest().body("Login fail");
    }

}

class JwtResponse {
    private String token;

    public JwtResponse(String token) {
        this.token = token;
    }

    getter and setter...
}


步驟 4:添加 JWT 過濾器來驗證每個請求

為了保護 REST API切威,我們需要一個過濾器來驗證每個請求中的 JWT 是否有效育特。在 Spring Security 中,你可以通過自定義一個 OncePerRequestFilter 來實現(xiàn)這一點先朦。

package com.example.demo;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.io.IOException;
import java.util.ArrayList;
import com.example.demo.util.JwtUtil;

@WebFilter("/*")
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
    private JwtUtil jwtUtil = new JwtUtil();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 從請求頭獲取 token
        String token = request.getHeader("Authorization");

        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7); // 移除 "Bearer " 前綴

            // 從 token 中提取用戶名并驗證
            String username = jwtUtil.extractUsername(token);
            if (username != null && jwtUtil.validateToken(token, username)) {
                // 創(chuàng)建一個認(rèn)證對象
                Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        filterChain.doFilter(request, response); // 繼續(xù)請求處理
    }
}

步驟 5:配置 Spring Security

需要確保 Spring Security 配置正確缰冤,以便 JWT 過濾器能夠生效并保護你的 API。

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.example.demo.JwtRequestFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtRequestFilter jwtRequestFilter;

    public SecurityConfig(JwtRequestFilter jwtRequestFilter) {
        this.jwtRequestFilter = jwtRequestFilter;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
//                .csrf(csrf -> csrf
//                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
//                        .ignoringRequestMatchers("/api/login", "/api/register")
//                )
                .authorizeHttpRequests((requests) -> requests
                        .requestMatchers("/api/register", "/api/login").permitAll()
                        .anyRequest().authenticated()
                )
                // 添加 JWT 過濾器
                .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);  // 添加自定義的 JWT 過濾器

        return http.build();
    }

測試 API

  1. 使用 /login 接口登錄喳魏,獲取到一個 JWT棉浸。
  2. 在隨后的請求中(比如訪問 /test 接口),在請求頭中帶上 Authorization: Bearer <your-jwt-token>刺彩,來驗證身份迷郑。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市创倔,隨后出現(xiàn)的幾起案子嗡害,更是在濱河造成了極大的恐慌,老刑警劉巖畦攘,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霸妹,死亡現(xiàn)場離奇詭異,居然都是意外死亡知押,警方通過查閱死者的電腦和手機叹螟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門鹃骂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人罢绽,你說我怎么就攤上這事偎漫。” “怎么了有缆?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長温亲。 經(jīng)常有香客問我棚壁,道長,這世上最難降的妖魔是什么栈虚? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任袖外,我火速辦了婚禮,結(jié)果婚禮上魂务,老公的妹妹穿的比我還像新娘曼验。我一直安慰自己,他們只是感情好粘姜,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布鬓照。 她就那樣靜靜地躺著,像睡著了一般孤紧。 火紅的嫁衣襯著肌膚如雪豺裆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天号显,我揣著相機與錄音臭猜,去河邊找鬼。 笑死押蚤,一個胖子當(dāng)著我的面吹牛蔑歌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揽碘,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼次屠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钾菊?” 一聲冷哼從身側(cè)響起帅矗,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎煞烫,沒想到半個月后浑此,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡滞详,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年凛俱,在試婚紗的時候發(fā)現(xiàn)自己被綠了紊馏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蒲犬,死狀恐怖朱监,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情原叮,我是刑警寧澤赫编,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站奋隶,受9級特大地震影響擂送,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唯欣,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一嘹吨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧境氢,春花似錦蟀拷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脐区,卻和暖如春愈诚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牛隅。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工炕柔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人媒佣。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓匕累,卻偏偏與公主長得像,于是被迫代替她去往敵國和親默伍。 傳聞我的和親對象是個殘疾皇子欢嘿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容