<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Spring Security是Spring的一個核心專案,它是一個功能強大且高度可客製化的認證和存取控制框架。它提供了認證和授權功能以及抵禦常見的攻擊,它已經成為保護基於spring的應用程式的事實標準。
Spring Boot提供了自動設定,引入starter依賴即可使用。
Spring Security特性總結:
JWT(Json web token),是為了在網路應用環境間傳遞宣告而執行的一種基於JSON的開放標準(RFC 7519).該token被設計為緊湊且安全的,特別適用於分散式站點的單點登入(SSO)場景。JWT的宣告一般被用來在身份提供者和服務提供者間傳遞被認證的使用者身份資訊,以便於從資源伺服器獲取資源,也可以增加一些額外的其它業務邏輯所必須的宣告資訊(例如,許可權資訊)。一旦使用者被授予token,使用者即可通過該token存取伺服器上的資源。
https://jwt.io/,該網站提供了一個debuggr,便於初學者學習理解JWT。
注意本篇文章演示使用JDK和Spring Boot的版本如下:
Spring Boot:2.7.2
JDK: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> <!-- 下面的依賴包含了OAuth2 JWT認證實現 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency>
以上兩個依賴即可。
注意: 不同的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.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.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_字首 .oauth2ResourceServer().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呢?
很簡單,可以提供一個登入介面,讓使用者輸入使用者名稱和密碼,匹配成功後頒發令牌即可。
其實並不是必須這樣做,還有其他方式,比如我們呼叫第三方介面,我們經常的做法是先去第三方申請,申請通過後我們就可以得到一個令牌。這個過程和上面的登入通過後頒發一個令牌是一樣的,都是通過合法的途徑獲得一個令牌!
登入介面只有一個目的,就是給合法使用者頒發令牌!
登入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 OAuth2 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(); } }
其他非核心程式碼這裡就不貼出來了,我將程式碼放到github上了,具體可以轉到https://github.com/cloudgyb/spring-security-study-jwt。
使用postman測試一下:
使用錯誤的密碼,會返回401 Unauthorized的狀態碼,表示我們認證失敗!
使用正確的使用者名稱和密碼:
返回了JWT令牌。
此時使用者端拿到了合法的令牌,接下來就可以存取伺服器上有權存取的資源了。
我寫了一個測試介面:
@RestController public class HelloController { @GetMapping("/") @PreAuthorize("hasAuthority('test')") public String hello(Authentication authentication) { return "Hello, " + authentication.getName() + "!"; } }
該介面需要使用者擁有"test"的許可權,但是登入使用者沒有該許可權(只有一個app的許可權),此時呼叫該介面:
首先將上一步登入獲得的令牌貼上到token中:
我們傳送請求得到了403 Forbidden的響應,意思就是我們沒有存取許可權,此時我們將介面許可權改為“app”:
@RestController public class HelloController { @GetMapping("/") @PreAuthorize("hasAuthority('app')") public String hello(Authentication authentication) { return "Hello, " + authentication.getName() + "!"; } }
重啟專案。再次發起請求:
我們已經可以正常存取了!
Spring Security專業性很強,有些術語對於初學者可能有點難度,但是一旦掌握這些概念,你會喜歡上Spring Security的!
這兒有一個可直接執行的demo供參考:https://github.com/cloudgyb/spring-security-study-jwt。
到此這篇關於SpringBoot+SpringSecurity+JWT實現系統認證與授權範例的文章就介紹到這了,更多相關SpringBoot SpringSecurity JWT認證授權內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45