搜尋
首頁Javajava教程怎麼使用SpringBoot+SpringSecurity+JWT實現系統認證與授權

1. Spring Security簡介

Spring Security是Spring的一個核心項目,它是一個功能強大且高度可自訂的認證和存取控制框架。它提供了認證和授權功能以及抵禦常見的攻擊,它已經成為保護基於spring的應用程式的事實標準。

Spring Boot提供了自動配置,引入starter依賴即可使用。
Spring Security特性摘要:

  • 使用簡單,提供Spring Boot starter依賴,極易與Spring Boot專案整合。

  • 專業,提供CSRF防護、點擊劫持防護、XSS防護等,並提供各種安全頭整合(X-XSS-Protection,X-Frame-Options等)。

  • 密碼加密存儲,支援多種加密演算法

  • #可擴展性和可自訂性極強

##OAuth3 JWT認證支援




… …

2. JWT簡介

##JWT(Json web token ),是為了在網路應用環境間傳遞聲明而執行的一種基於JSON的開放標準(RFC 7519).該token被設計為緊湊且安全的,特別適用於分佈式站點的單點登錄(SSO)場景。 JWT的聲明一般被用來在身分提供者和服務提供者間傳遞被認證的使用者身分訊息,以便於從資源伺服器取得資源,也可以增加一些額外的其它業務邏輯所必須的聲明資訊(例如,權限資訊).一旦用戶被授予token,用戶即可透過該token存取伺服器上的資源。

3. Spring Boot整合Spring Security

注意這篇文章示範使用JDK和Spring Boot的版本如下:Spring Boot:2.7.2JDK: 11

不同的Spring Boot版本配置不同,但是原理相同。

在Spring Boot專案的pom.xml檔案中加入下面的依賴:

<!-- Spring Security的Spring boot starter,引入后将自动启动Spring Security的自动配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 下面的依赖包含了OAuth3 JWT认证实现 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth3-resource-server</artifactId>
</dependency>
以上兩個依賴即可。

4. 設定Spring Security使用JWT認證


注意:

不同的Spring Boot版本配置不同,但是原理相同,本文使用的是Spring Boot: 2.7.2。

主要是設定HttpSecurity Bean產生SecurityFilterBean,設定如下:
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth3.jwt.JwtDecoder;
import org.springframework.security.oauth3.jwt.JwtEncoder;
import org.springframework.security.oauth3.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth3.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth3.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth3.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth3.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth3.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.SecurityFilterChain;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

/**
 * Spring Security 配置
 *
 * @author cloudgyb
 * @since 2022/7/30 18:31
 */
@Configuration(proxyBeanMethods = false)
@EnableMethodSecurity
public class WebSecurityConfigurer {
    //使用RSA对JWT做签名,所以这里需要一对秘钥。
    //秘钥文件的路径在application.yml文件中做了配置(具体配置在下面)。
    @Value("${jwt.public.key}")
    private RSAPublicKey key; 
    @Value("${jwt.private.key}")
    private RSAPrivateKey priv;

     /**
      * 构建SecurityFilterChain bean
      */
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        //"/login"是系统的登录接口,所以需要匿名可访问
        http.authorizeRequests().antMatchers("/login").anonymous();
        //其他请求都需认证后才能访问
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                
                //采用JWT认证无需session保持,所以禁用掉session管理器
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                //login接口可能来自其他站点,所以对login不做csrf防护
                .csrf((csrf) -> csrf.ignoringAntMatchers("/login"))
                //配置认证方式为JWT,并且配置了一个JWT认证装换器,用于去掉解析权限时的SCOOP_前缀
                .oauth3ResourceServer().jwt().jwtAuthenticationConverter(
                        JwtAuthenticationConverter()
                );
        //配置认证失败或者无权限时的处理器
        http.exceptionHandling((exceptions) -> exceptions
                .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
        );
         //根据配置生成SecurityFilterChain对象
        return http.build();
    }


    /**
     * JWT解码器,用于认证时的JWT解码 
     */
    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(this.key).build();
    }
    /**
     * JWT编码器,生成JWT
     */
    @Bean
    JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }
    
    /**
     * JWT认证解码时,去掉Spring Security对权限附带的默认前缀SCOOP_
     */
    @Bean
    JwtAuthenticationConverter JwtAuthenticationConverter() {
        final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
        final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

application.yml

jwt:
  private.key: classpath:app.key
  public.key: classpath:app.pub
上邊的設定需要在Spring Boot專案的Resource目錄下產生一對RSA秘鑰。

可以使用下面的網站進行產生:http://tools.jb51.net/password/rsa_encode/,
注意: 金鑰格式使用 PKCS#8,私鑰密碼為空。

還有一點要說明,我在程式碼中使用了Spring Boot的值注入:

@Value("${jwt.public.key}")
 private RSAPublicKey key; 
@Value("${jwt.private.key}")
private RSAPrivateKey priv;

有沒有很好奇Spring Boot是如何將yaml檔案中的字串對應的檔案轉換為RSAPublicKey和RSAPrivateKey ?

其實是Spring Security幫我們做了處理,在Spring Security中幫我們實作了一個轉換器ResourceKeyConverterAdapter,可以閱讀相關原始碼來更深入的了解。

至此我們的專案已經支援JWT認證了。

但是使用者需要在請求頭Authorization中攜帶合法的JWT才能通過認證,進而存取伺服器資源,那麼如何給使用者頒發一個合法的JWT呢?

很簡單,可以提供一個登入接口,讓使用者輸入使用者名稱和密碼,匹配成功後頒發令牌即可。

其實並不是必須這樣做,還有其他方式,例如我們調用第三方接口,我們經常的做法是先去第三方申請,申請通過後我們就可以得到一個令牌。這個過程和上面的登入通過後頒發一個令牌是一樣的,都是透過合法的途徑獲得一個令牌!


5. 實作登入介面

怎麼使用SpringBoot+SpringSecurity+JWT實現系統認證與授權登入介面只有一個目的,就是要頒發給合法使用者令牌!

登入API介面:

@RestController
public class SysLoginController {
    private final SysLoginService sysLoginService;

    public SysLoginController(SysLoginService sysLoginService) {
        this.sysLoginService = sysLoginService;
    }

    @PostMapping("/login")
    public String login(@RequestBody LoginInfo loginInfo) {
        return sysLoginService.login(loginInfo);
    }
}

登入邏輯實作:

@Service
public class SysLoginService {
    private final JwtEncoder jwtEncoder;
    private final SpringSecurityUserDetailsService springSecurityUserDetailsService;

    public SysLoginService(JwtEncoder jwtEncoder, SpringSecurityUserDetailsService springSecurityUserDetailsService) {
        this.jwtEncoder = jwtEncoder;
        this.springSecurityUserDetailsService = springSecurityUserDetailsService;
    }

    public String login(LoginInfo loginInfo) {
        //从用户信息存储库中获取用户信息
        final UserDetails userDetails = springSecurityUserDetailsService.loadUserByUsername(loginInfo.getUsername());
        final String password = userDetails.getPassword();
        //匹配密码,匹配成功生成JWT令牌
        if (password.equals(loginInfo.getPassword())) {
            return generateToken(userDetails);
        }
        //密码不匹配,抛出异常,Spring Security发现抛出该异常后会将http响应状态码设置为401 unauthorized
        throw new BadCredentialsException("密码错误!");
    }

    private String generateToken(UserDetails userDetails) {
        Instant now = Instant.now();
        //JWT过期时间为36000秒,也就是600分钟,10小时
        long expiry = 36000L;
        String scope = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(" "));
         //将用户权限信息使用空格分割拼为字符串,放到JWT的payload的scope字段中,注意不要改变scope这个属性,这是Spring Security OAuth3 JWT默认处理方式,在JWT解码时需要读取该字段,转为用户的权限信息!
        JwtClaimsSet claims = JwtClaimsSet.builder()
                .issuer("self")
                .issuedAt(now)
                .expiresAt(now.plusSeconds(expiry))
                .subject(userDetails.getUsername())
                .claim("scope", scope)
                .build();
        return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
    }
}
怎麼使用SpringBoot+SpringSecurity+JWT實現系統認證與授權其他非核心程式碼這裡就不貼出來了,我將程式碼放到github上了,具體可以轉到https ://github.com/cloudgyb/spring-security-study-jwt。

6. 測試

使用postman測試一下:
使用錯誤的密碼,會回傳401 Unauthorized的狀態碼,表示我們認證失敗!


使用正確的使用者名稱和密碼:怎麼使用SpringBoot+SpringSecurity+JWT實現系統認證與授權

#########傳回了JWT令牌。 ######此時客戶端拿到了合法的令牌,接下來就可以存取伺服器上有權存取的資源了。 ###我寫了一個測試介面:###
@RestController
public class HelloController {

    @GetMapping("/")
    @PreAuthorize("hasAuthority(&#39;test&#39;)")
    public String hello(Authentication authentication) {
        return "Hello, " + authentication.getName() + "!";
    }
}
###該介面需要使用者擁有"test"的權限,但登入使用者沒有該權限(只有一個app的權限),此時呼叫該介面:## #首先將上一步驟登入所獲得的令牌貼到token中:############

我们发送请求得到了403 Forbidden的响应,意思就是我们没有访问权限,此时我们将接口权限改为“app”:

@RestController
public class HelloController {

    @GetMapping("/")
    @PreAuthorize("hasAuthority(&#39;app&#39;)")
    public String hello(Authentication authentication) {
        return "Hello, " + authentication.getName() + "!";
    }
}

重启项目。再次发起请求:

怎麼使用SpringBoot+SpringSecurity+JWT實現系統認證與授權

以上是怎麼使用SpringBoot+SpringSecurity+JWT實現系統認證與授權的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:亿速云。如有侵權,請聯絡admin@php.cn刪除
說明JVM如何充當Java代碼和基礎操作系統之間的中介。說明JVM如何充當Java代碼和基礎操作系統之間的中介。Apr 29, 2025 am 12:23 AM

JVM的工作原理是將Java代碼轉換為機器碼並管理資源。 1)類加載:加載.class文件到內存。 2)運行時數據區:管理內存區域。 3)執行引擎:解釋或編譯執行字節碼。 4)本地方法接口:通過JNI與操作系統交互。

解釋Java虛擬機(JVM)在Java平台獨立性中的作用。解釋Java虛擬機(JVM)在Java平台獨立性中的作用。Apr 29, 2025 am 12:21 AM

JVM使Java實現跨平台運行。 1)JVM加載、驗證和執行字節碼。 2)JVM的工作包括類加載、字節碼驗證、解釋執行和內存管理。 3)JVM支持高級功能如動態類加載和反射。

您將採取哪些步驟來確保Java應用程序在不同的操作系統上正確運行?您將採取哪些步驟來確保Java應用程序在不同的操作系統上正確運行?Apr 29, 2025 am 12:11 AM

Java應用可通過以下步驟在不同操作系統上運行:1)使用File或Paths類處理文件路徑;2)通過System.getenv()設置和獲取環境變量;3)利用Maven或Gradle管理依賴並測試。 Java的跨平台能力依賴於JVM的抽象層,但仍需手動處理某些操作系統特定的功能。

Java是否需要特定於平台的配置或調整區域?Java是否需要特定於平台的配置或調整區域?Apr 29, 2025 am 12:11 AM

Java在不同平台上需要進行特定配置和調優。 1)調整JVM參數,如-Xms和-Xmx設置堆大小。 2)選擇合適的垃圾回收策略,如ParallelGC或G1GC。 3)配置Native庫以適應不同平台,這些措施能讓Java應用在各種環境中發揮最佳性能。

哪些工具或庫可以幫助您解決Java開發中特定於平台的挑戰?哪些工具或庫可以幫助您解決Java開發中特定於平台的挑戰?Apr 29, 2025 am 12:01 AM

Osgi,Apachecommonslang,JNA和JvMoptionsareeForhandlingForhandlingPlatform-specificchallengesinjava.1)osgimanagesdeppedendendencenciesandisolatescomponents.2)apachecommonslangprovidesitorityfunctions.3)

JVM如何在不同平台上管理垃圾收集?JVM如何在不同平台上管理垃圾收集?Apr 28, 2025 am 12:23 AM

JVMmanagesgarbagecollectionacrossplatformseffectivelybyusingagenerationalapproachandadaptingtoOSandhardwaredifferences.ItemploysvariouscollectorslikeSerial,Parallel,CMS,andG1,eachsuitedfordifferentscenarios.Performancecanbetunedwithflagslike-XX:NewRa

為什麼Java代碼可以在不同的操作系統上運行,而無需修改?為什麼Java代碼可以在不同的操作系統上運行,而無需修改?Apr 28, 2025 am 12:14 AM

Java代碼可以在不同操作系統上無需修改即可運行,這是因為Java的“一次編寫,到處運行”哲學,由Java虛擬機(JVM)實現。 JVM作為編譯後的Java字節碼與操作系統之間的中介,將字節碼翻譯成特定機器指令,確保程序在任何安裝了JVM的平台上都能獨立運行。

描述編譯和執行Java程序的過程,突出平台獨立性。描述編譯和執行Java程序的過程,突出平台獨立性。Apr 28, 2025 am 12:08 AM

Java程序的編譯和執行通過字節碼和JVM實現平台獨立性。 1)編寫Java源碼並編譯成字節碼。 2)使用JVM在任何平台上執行字節碼,確保代碼的跨平台運行。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具