SpringBoot3.0+SpringSecurity6.0+JWT的實現
JWT_SpringSecurity
SpringBoot3.0 + SpringSecurity6.0+JWT
Spring Security 是 Spring 家族中的一個安全管理框架。
一般Web應用的需要進行認證和授權。
認證:驗證當前訪問系統(tǒng)的是不是本系統(tǒng)的用戶,并且要確認具體是哪個用戶
授權:經過認證后判斷當前用戶是否有權限進行某個操作
1、快速入門
1.1、準備工作
搭建一個SpringBoot工程
① 設置父工程 添加依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <!-- DB相關 --> <!-- JDBC操作數據庫 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- MySQL依賴 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- mp依賴,簡化crud --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <!-- SpringSecurity依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- SpringWeb依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 熱部署依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- 懶人神器lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- API文檔 - swagger --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.0.0</version> </dependency> </dependencies>
配置文件application.yml
# 端口號 server: port: 48080 --- #################### 數據庫相關配置 #################### spring: # 數據源配置項 datasource: url: jdbc:mysql://127.0.0.1:3306/auth-system?useSSL=false&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true # MySQL Connector/J 8.X 連接的示例 driver-class-name: com.mysql.cj.jdbc.Driver username: root # 數據庫賬號 password: 123123123 # 數據庫密碼 # HikariCP 自定義配置,對應 HikariConfig 配置屬性類 hikari: minimum-idle: 10 # 池中維護的最小空閑連接數,默認為 10 個。 maximum-pool-size: 10 # 池中最大連接數,包括閑置和使用中的連接,默認為 10 個。 # springdoc-openapi項目配置 springdoc: swagger-ui: path: /swagger-ui.html tags-sorter: alpha operations-sorter: alpha api-docs: path: /v3/api-docs group-configs: - group: 'default' paths-to-match: '/**' packages-to-scan: org.pp.boot3 # knife4j的增強配置,不需要增強可以不配 knife4j: enable: true setting: language: zh_cn
② 創(chuàng)建啟動類
/** * @author ss_419 */ @SpringBootApplication public class SpringSecurity6JwtBoot3Application { public static void main(String[] args) { SpringApplication.run(SpringSecurity6JwtBoot3Application.class, args); } }
③ 創(chuàng)建Controller
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * TODO 測試接口 * * @author ss_419 * @version 1.0 * @date 2023/3/2 20:27 */ @RestController @RequestMapping("/api/v1/") @Tag(name = "測試接口") public class GreetingController { @GetMapping(value = "/hello") @Operation(summary = "hello") public ResponseEntity<String> sayHello() { String message = "Hello World!"; return ResponseEntity.ok(message); } }
啟動項目,查看接口文檔地址:http://localhost:48080/doc.html#/home
Knife4j的文檔地址:http://ip:port/doc.html
即可查看文檔
出現測試接口,表示項目啟動成功
1.2引入SpringSecurity
在SpringBoot項目中使用SpringSecurity我們只需要引入依賴即可實現入門案例。
注意??:1.1創(chuàng)建項目時已經引入過依賴
<!-- SpringSecurity依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
引入依賴后我們在嘗試去訪問之前的接口就會自動跳轉到一個SpringSecurity的默認登陸頁面,默認用戶名是user,密碼會輸出在控制臺。
須登陸之后才能對接口進行訪問
2、認證
2.1、原理初探
用戶認證流程:
SpringSecurity的原理是一個過濾器鏈,內部包含了提供各種功能的過濾器。
- UsernamePasswordAuthenticationFilter:負責處理我們在登陸頁面填寫了用戶名密碼后的登陸請求。入門案例的認證工作主要有它負責。
- ExceptionTranslationFilter: 處理過濾器鏈中拋出的任何AccessDeniedException和AuthenticationException 。
- FilterSecurityInterceptor: 負責權限校驗的過濾器。
- Authentication接口: 它的實現類,表示當前訪問系統(tǒng)的用戶,封裝了用戶相關信息。
- AuthenticationManager接口:定義了認證Authentication的方法
- UserDetailsService接口:加載用戶特定數據的核心接口。里面定義了一個根據用戶名查詢用戶信息的方法
- UserDetails接口:提供核心用戶信息。通過UserDetailsService根據用戶名獲取處理的用戶信息要封裝成
- UserDetails對象返回。然后將這些信息封裝到Authentication對象中。
2.2、用戶認證核心組件
Authentication`**,它存儲了認證信息,代表當前登錄用戶。
我們在程序中如何獲取并使用它呢?我們需要通過 SecurityContext
來獲取Authentication
,SecurityContext
就是我們的上下文對象!這個上下文對象則是交由 SecurityContextHolder
進行管理,你可以在程序任何地方使用它:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder
原理非常簡單,就是使用ThreadLocal
來保證一個線程中傳遞同一個對象!
現在我們已經知道了Spring Security中三個核心組件:
? 1、Authentication
:存儲了認證信息,代表當前登錄用戶
? 2、SeucirtyContext
:上下文對象,用來獲取Authentication
? 3、SecurityContextHolder
:上下文管理對象,用來在程序任何地方獲取SecurityContext
Authentication
中是什么信息呢:
? 1、Principal
:用戶信息,沒有認證時一般是用戶名,認證后一般是用戶對象
? 2、Credentials
:用戶憑證,一般是密碼
? 3、Authorities
:用戶權限
用戶認證:
Spring Security是怎么進行用戶認證的呢?
AuthenticationManager
就是Spring Security用于執(zhí)行身份驗證的組件,只需要調用它的authenticate
方法即可完成認證。Spring Security默認的認證方式就是在UsernamePasswordAuthenticationFilter
這個過濾器中進行認證的,該過濾器負責認證邏輯。
Spring Security用戶認證關鍵代碼如下:
// 生成一個包含賬號密碼的認證信息 Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, passwrod); // AuthenticationManager校驗這個認證信息,返回一個已認證的Authentication Authentication authentication = authenticationManager.authenticate(authenticationToken); // 將返回的Authentication存到上下文中 SecurityContextHolder.getContext().setAuthentication(authentication);
接下來我們分析一下一個請求發(fā)送到服務器都經歷了什么:
上圖中當有請求發(fā)送給服務器都要經過Check JWT Token機制,需要每次收到請求的時候,過濾器都處于活動狀態(tài)。因此每次用戶發(fā)送請求時希望過濾器被觸發(fā)并完成要做的所有工作。
- 如果我們有我們的用戶電子郵箱并且用戶未通過身份驗證,我們會從數據庫中獲取用戶詳細信息(loadUserByUsername --> UserDetails)
- 然后我們需要做的是檢查用戶是否有效,如果用戶和令牌有效,我們創(chuàng)建一個UsernamePasswordAuthenticationToken對象,傳遞UserDetails & 憑證 & 權限信息
- 擴展上面生成的authToken,包含我們請求的詳細信息,然后更新安全上下文中的身份驗證令牌
- 最后一步執(zhí)行過濾器chain,別忘記放行,將請求通過DispatchServlet分發(fā)響應給客戶端
登錄認證流程
登錄
①自定義登錄接口
調用ProviderManager的方法進行認證 如果認證通過生成jwt 把用戶信息存入redis中
②自定義UserDetailsService
在這個實現類中去查詢數據庫
校驗:
①定義Jwt認證過濾器
獲取token
解析token獲取其中的userid
從redis中獲取用戶信息
存入SecurityContextHolder
3、JWT_Security整合流程
3.1、什么是JWT
JWT 主要用于用戶登錄鑒權,所以我們從最傳統(tǒng)的 session 認證開始說起。
Session認證:
眾所周知,http 協議本身是無狀態(tài)的協議,那就意味著當有用戶向系統(tǒng)使用賬戶名稱和密碼進行用戶認證之后,下一次請求還要再一次用戶認證才行。因為我們不能通過 http 協議知道是哪個用戶發(fā)出的請求,所以如果要知道是哪個用戶發(fā)出的請求,那就需要在服務器保存一份用戶信息(保存至 session ),然后在認證成功后返回 cookie 值傳遞給瀏覽器,那么用戶在下一次請求時就可以帶上 cookie 值,服務器就可以識別是哪個用戶發(fā)送的請求,是否已認證,是否登錄過期等等。這就是傳統(tǒng)的 session 認證方式。
session 認證的缺點其實很明顯,由于 session 是保存在服務器里,所以如果分布式部署應用的話,會出現session不能共享的問題,很難擴展。于是乎為了解決 session 共享的問題,又引入了 redis,接著往下看。
Session認證還會引發(fā)CSRF(跨站請求偽造攻擊),因為是基于cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。
Token認證:
這種方式跟Session的方式流程差不多,不同的地方在于保存的是一個token值,token一般是一串隨機的字符(比如UUID),value 一般是用戶ID,并且設置一個過期時間。
每次請求服務的時候帶上 token 在請求頭,后端接收到token 則根據 token 查一下 redis 是否存在,如果存在則表示用戶已認證,如果 token 不存在則跳到登錄界面讓用戶重新登錄,登錄成功后返回一個 token 值給客戶端。
JWT認證:
JWT(全稱Json Web Token),是為了在網絡應用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標準.該token被設計為緊湊且安全的,特別適用于分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便于從資源服務器獲取資源,也可以增加一些額外的其它業(yè)務邏輯所必須的聲明信息,該token也可直接被用于認證,也可被加密。
3.1.1、JWT的數據結構:
JWT 一般是這樣一個字符串,分為三個部分,以 “.” 隔開:
xxxxx.yyyyy.zzzzz
JWT官網:https://jwt.io/
進入官網我們可以看到首頁有這樣一個頁面:
其中左側是生成的jwt編碼,我們可以看到它生成的格式就如上述所描述那樣,分成了三段
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
右側是對jwt字符串進行解碼
HEADER
jwt的頭部承載兩部分信息:
聲明類型,這里是jwt
聲明加密的算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
然后將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
PLAYLOAD:
載荷就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分
標準中注冊的聲明
公共的聲明
私有的聲明
標準中注冊的聲明 (建議但不強制使用) :
- iss: jwt簽發(fā)者
- sub: jwt所面向的用戶
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大于簽發(fā)時間
- nbf: 定義在什么時間之前,該jwt都是不可用的.
- iat: jwt的簽發(fā)時間
- jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
公共的聲明 : 公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業(yè)務需要的必要信息.但不建議添加敏感信息,因為該部分在客戶端可解密.
私有的聲明 : 私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味著該部分信息可以歸類為明文信息。
然后將其進行base64加密,得到JWT的第二部分:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
VERIFY SIGNATURE:
JWT的第三部分是一個簽證信息,這個簽證信息由三部分組成:
header (base64后的)
payload (base64后的)
secret
這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret組合加密,然后就構成了jwt的第三部分:
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
注意:secret是保存在服務器端的,jwt的簽發(fā)生成也是在服務器端的,secret就是用來進行jwt的簽發(fā)和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發(fā)jwt了。
如何應用:
一般是在請求頭里加入Authorization,并加上Bearer標注:
'Authorization': 'Bearer ' + token
3.1.2、簽名密鑰
在Json網絡令牌的安全上下文中,簽名密鑰是用于對JWT進行數字簽名的加密信息,簽名密鑰用于創(chuàng)建JWT的簽名部分,用于驗證JWT的發(fā)送者是否是已經經過確認的用戶,并確保消息在整個過程中沒有被更改(保證一致性),因此我們要確保發(fā)送此JWT密鑰的用戶是同一個人。
簽名密鑰通常與JWT標頭中指定的登錄算法結合使用,以創(chuàng)建簽名具體的登錄算法,密鑰大小將取決于應用程序的安全要求和信任級別(簽名方)
可以在allkeysgenertor中生成任意大小的簽名密鑰
注意??:在JWT中最低安全級別是256bit,因此在本教程中,我們將采用256bit的簽名密鑰,如下所示:
3.2、準備工作
①添加依賴
<!-- JWT 相關 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> </dependency> <!--redis依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--fastjson依賴--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.33</version> </dependency>
② 添加Redis相關配置
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import com.alibaba.fastjson.parser.ParserConfig; import org.springframework.util.Assert; import java.nio.charset.Charset; /** * Redis使用FastJson序列化 * @author ss_419 */ public class FastJsonRedisSerializer<T> implements RedisSerializer<T> { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class<T> clazz; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } public FastJsonRedisSerializer(Class<T> clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz); } protected JavaType getJavaType(Class<?> clazz) { return TypeFactory.defaultInstance().constructType(clazz); } }
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * TODO Redis配置 * * @author ss_419 * @version 1.0 * @date 2023/3/3 10:24 */ @Configuration public class RedisConfig { /** * Redis配置 */ @Bean @SuppressWarnings(value = {"unchecked", "rawtypes"}) public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Object,Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<>(Object.class); // 使用StringRedisSerializer來序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }
package org.pp.boot3.config.redis; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; /** * @author ss_419 */ @SuppressWarnings(value = {"unchecked", "rawtypes"}) @Component @RequiredArgsConstructor public class RedisCache { private final RedisTemplate redisTemplate; /** * 緩存基本的對象,Integer、String、實體類等 * * @param key 緩存的鍵值 * @param value 緩存的值 */ public <T> void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 緩存基本的對象,Integer、String、實體類等 * * @param key 緩存的鍵值 * @param value 緩存的值 * @param timeout 時間 * @param timeUnit 時間顆粒度 */ public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 設置有效時間 * * @param key Redis鍵 * @param timeout 超時時間 * @return true=設置成功;false=設置失敗 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 設置有效時間 * * @param key Redis鍵 * @param timeout 超時時間 * @param unit 時間單位 * @return true=設置成功;false=設置失敗 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 獲得緩存的基本對象。 * * @param key 緩存鍵值 * @return 緩存鍵值對應的數據 */ public <T> T getCacheObject(final String key) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 刪除單個對象 * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * 刪除集合對象 * * @param collection 多個對象 * @return */ public long deleteObject(final Collection collection) { return redisTemplate.delete(collection); } /** * 緩存List數據 * * @param key 緩存的鍵值 * @param dataList 待緩存的List數據 * @return 緩存的對象 */ public <T> long setCacheList(final String key, final List<T> dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 獲得緩存的list對象 * * @param key 緩存的鍵值 * @return 緩存鍵值對應的數據 */ public <T> List<T> getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 緩存Set * * @param key 緩存鍵值 * @param dataSet 緩存的數據 * @return 緩存數據的對象 */ public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 獲得緩存的set * * @param key * @return */ public <T> Set<T> getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 緩存Map * * @param key * @param dataMap */ public <T> void setCacheMap(final String key, final Map<String, T> dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 獲得緩存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入數據 * * @param key Redis鍵 * @param hKey Hash鍵 * @param value 值 */ public <T> void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 獲取Hash中的數據 * * @param key Redis鍵 * @param hKey Hash鍵 * @return Hash中的對象 */ public <T> T getCacheMapValue(final String key, final String hKey) { HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 刪除Hash中的數據 * * @param key * @param hkey */ public void delCacheMapValue(final String key, final String hkey) { HashOperations hashOperations = redisTemplate.opsForHash(); hashOperations.delete(key, hkey); } /** * 獲取多個Hash中的數據 * * @param key Redis鍵 * @param hKeys Hash鍵集合 * @return Hash對象集合 */ public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 獲得緩存的基本對象列表 * * @param pattern 字符串前綴 * @return 對象列表 */ public Collection<String> keys(final String pattern) { return redisTemplate.keys(pattern); } }
③ 響應類&工具類
import com.fasterxml.jackson.annotation.JsonInclude; /** * 統(tǒng)一響應類 * @author ss_419 */ @JsonInclude(JsonInclude.Include.NON_NULL) public class ResponseResult<T> { /** * 狀態(tài)碼 */ private Integer code; /** * 提示信息,如果有錯誤時,前端可以獲取該字段進行提示 */ private String msg; /** * 查詢到的結果數據, */ private T data; public ResponseResult(Integer code, String msg) { this.code = code; this.msg = msg; } public ResponseResult(Integer code, T data) { this.code = code; this.data = data; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public ResponseResult(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } }
import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; /** * TODO Web工具 * * @author ss_419 * @version 1.0 * @date 2023/3/3 10:39 */ public class WebUtil { /** * 將字符串渲染到客戶端 * * @param response 渲染對象 * @param string 待渲染的字符串 * @return null */ public static String renderString(HttpServletResponse response, String string) { try { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(string); } catch (IOException e) { e.printStackTrace(); } return null; } }
用戶實體類SysUser
/** * 用戶表 * @author ss_419 * @TableName sys_user */ @TableName(value ="sys_user") @Data public class SysUser implements Serializable { /** * 會員id */ @TableId(type = IdType.AUTO) private Long id; /** * 用戶名 */ private String username; /** * 密碼 */ private String password; /** * 姓名 */ private String name; /** * 手機 */ private String phone; /** * 頭像地址 */ private String headUrl; /** * 部門id */ private Long deptId; /** * 崗位id */ private Long postId; /** * 描述 */ private String description; /** * 狀態(tài)(1:正常 0:停用) */ private Integer status; /** * 創(chuàng)建時間 */ private Date createTime; /** * 更新時間 */ private Date updateTime; /** * 刪除標記(0:可用 1:已刪除) */ private Integer isDeleted; @TableField(exist = false) private static final long serialVersionUID = 1L; @Override public boolean equals(Object that) { if (this == that) { return true; } if (that == null) { return false; } if (getClass() != that.getClass()) { return false; } SysUser other = (SysUser) that; return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId())) && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername())) && (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword())) && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName())) && (this.getPhone() == null ? other.getPhone() == null : this.getPhone().equals(other.getPhone())) && (this.getHeadUrl() == null ? other.getHeadUrl() == null : this.getHeadUrl().equals(other.getHeadUrl())) && (this.getDeptId() == null ? other.getDeptId() == null : this.getDeptId().equals(other.getDeptId())) && (this.getPostId() == null ? other.getPostId() == null : this.getPostId().equals(other.getPostId())) && (this.getDescription() == null ? other.getDescription() == null : this.getDescription().equals(other.getDescription())) && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus())) && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime())) && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime())) && (this.getIsDeleted() == null ? other.getIsDeleted() == null : this.getIsDeleted().equals(other.getIsDeleted())); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getId() == null) ? 0 : getId().hashCode()); result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode()); result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode()); result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); result = prime * result + ((getPhone() == null) ? 0 : getPhone().hashCode()); result = prime * result + ((getHeadUrl() == null) ? 0 : getHeadUrl().hashCode()); result = prime * result + ((getDeptId() == null) ? 0 : getDeptId().hashCode()); result = prime * result + ((getPostId() == null) ? 0 : getPostId().hashCode()); result = prime * result + ((getDescription() == null) ? 0 : getDescription().hashCode()); result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode()); result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode()); result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode()); result = prime * result + ((getIsDeleted() == null) ? 0 : getIsDeleted().hashCode()); return result; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append(" ["); sb.append("Hash = ").append(hashCode()); sb.append(", id=").append(id); sb.append(", username=").append(username); sb.append(", password=").append(password); sb.append(", name=").append(name); sb.append(", phone=").append(phone); sb.append(", headUrl=").append(headUrl); sb.append(", deptId=").append(deptId); sb.append(", postId=").append(postId); sb.append(", description=").append(description); sb.append(", status=").append(status); sb.append(", createTime=").append(createTime); sb.append(", updateTime=").append(updateTime); sb.append(", isDeleted=").append(isDeleted); sb.append(", serialVersionUID=").append(serialVersionUID); sb.append("]"); return sb.toString(); } }
用戶表Mapper:
/** * @author ss_419 * @description 針對表【sys_user(用戶表)】的數據庫操作Mapper * @createDate 2023-03-03 10:41:42 * @Entity org.pp.boot3.domain.SysUser */ public interface SysUserMapper extends BaseMapper<SysUser> { }
用戶Service:
/** * @author ss_419 * @description 針對表【sys_user(用戶表)】的數據庫操作Service * @createDate 2023-03-03 10:41:42 */ public interface SysUserService extends IService<SysUser> { }
用戶ServiceImpl:
@Service public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService{ }
在啟動類上配置mapper掃描:
/** * @author ss_419 */ @SpringBootApplication @ComponentScan("org.pp.boot3.mapper") public class SpringSecurity6JwtBoot3Application { public static void main(String[] args) { SpringApplication.run(SpringSecurity6JwtBoot3Application.class, args); } }
創(chuàng)建一個用戶表,sql如下:
CREATE TABLE `sys_user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用戶名', `nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '昵稱', `password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '密碼', `status` CHAR(1) DEFAULT '0' COMMENT '賬號狀態(tài)(0正常 1停用)', `email` VARCHAR(64) DEFAULT NULL COMMENT '郵箱', `phonenumber` VARCHAR(32) DEFAULT NULL COMMENT '手機號', `sex` CHAR(1) DEFAULT NULL COMMENT '用戶性別(0男,1女,2未知)', `avatar` VARCHAR(128) DEFAULT NULL COMMENT '頭像', `user_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '用戶類型(0管理員,1普通用戶)', `create_by` BIGINT(20) DEFAULT NULL COMMENT '創(chuàng)建人的用戶id', `create_time` DATETIME DEFAULT NULL COMMENT '創(chuàng)建時間', `update_by` BIGINT(20) DEFAULT NULL COMMENT '更新人', `update_time` DATETIME DEFAULT NULL COMMENT '更新時間', `del_flag` INT(11) DEFAULT '0' COMMENT '刪除標志(0代表未刪除,1代表已刪除)', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用戶表'
3.3、核心代碼實現部分
1.創(chuàng)建ApplicationConfig提供全局的Bean對象,以供使用
/** * TODO 全局的Bean對象提供者 * @author ss_419 * * @RequiredArgsConstructor --> 代替原本的@Autowired */ @Configuration @RequiredArgsConstructor public class ApplicationConfig { // 注入數據庫操作DAO private final SysUserMapper repository; /** * * @return 用戶詳細信息 -> jwt身份驗證過濾器 */ @Bean public UserDetailsService userDetailsService() { return username -> repository.findByEmail(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); } /** * TODO 四 4.2 * @return 身份校驗機制、身份驗證提供程序 */ @Bean public AuthenticationProvider authenticationProvider() { // 創(chuàng)建一個用戶認證提供者 DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); // 設置用戶相信信息,可以從數據庫中讀取、或者緩存、或者配置文件 authProvider.setUserDetailsService(userDetailsService()); // 設置加密機制,若想要嘗試對用戶進行身份驗證,我們需要知道使用的是什么編碼 authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } /** * TODO 四 4.4 基于用戶名和密碼或使用用戶名和密碼進行身份驗證 * @param config * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } /** * TODO 四 4.3提供編碼機制 * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
2.創(chuàng)建JWT工具類(Service)
package org.pp.boot3.config.security; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import java.security.Key; import java.util.Date; import java.util.function.Function; /** * TODO 完成JWT的驗證服務 * JWT工具類 * * @author ss_419 * @version 1.0 * @date 2023/3/3 11:16 */ @Service public class JwtService { /** * 創(chuàng)建一個最終字符串,這個字符串稱為密鑰 * https://allkeysgenerator.com/ * * JWT最低要求的安全級別是256bit */ private static final String SECRET_KEY = "3F4428472B4B6250655368566D5971337336763979244226452948404D635166"; /** * 1、解析token字符串中的加密信息【加密算法&加密密鑰】, 提取所有聲明的方法 * @param token * @return */ private Claims extractAllClaims(String token){ return Jwts .parserBuilder() // 獲取alg開頭的信息 .setSigningKey(getSignInKey()) .build() // 解析token字符串 .parseClaimsJws(token) .getBody(); } /** * 2、獲取簽名密鑰的方法 * @return 基于指定的密鑰字節(jié)數組創(chuàng)建用于HMAC-SHA算法的新SecretKey實例 */ private Key getSignInKey() { byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY); return Keys.hmacShaKeyFor(keyBytes); } /** * 3、解析token字符串中的權限信息 * @param token * @return */ public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } /** * 4、從token中解析出username * @param token * @return */ public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } /** * 5、判斷token是否過期 * @param * @return */ public boolean isTokenValid(String token, UserDetails userDetails) { // 從token中獲取用戶名 final String username = extractUsername(token); return (username.equals(userDetails.getUsername())) &&!isTokenExpired(token); } /** * 6、驗證token是否過期 * @param token * @return */ private boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } /** * 6.1、從授權信息中獲取token過期時間 */ public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } }
3.完成JwtAuthenticationFilter身份驗證過濾器
/** * TODO 一、JWT身份驗證過濾器 * * @author ss_419 * @version 1.0 * @date 2023/3/3 10:56 */ @Component // 使用final,將服務注入class @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { /** * 需要每次收到請求的時候,過濾器都處于活動狀態(tài) * 因此每次用戶發(fā)送請求時希望過濾器被觸發(fā)并完成要做的所有工作 */ private final JwtService jwtService; /** * 加載用戶特定數據的核心接口。 * 它作為用戶DAO在整個框架中使用,并且是DaoAuthenticationProvider使用的策略 */ private final UserDetailsService userDetailsService;// 從ApplicationConfig中創(chuàng)建的Bean對象獲取 /** * 總體流程: * 如果我們有我們的用戶電子郵箱并且用戶未通過身份驗證,我們會從數據庫中獲取用戶詳細信息(loadUserByUsername --> UserDetails) * 然后我們需要做的是檢查用戶是否有效,如果用戶和令牌有效,我們創(chuàng)建一個UsernamePasswordAuthenticationToken對象,傳遞UserDetails & 憑證 & 權限信息 * 擴展上面生成的authToken,包含我們請求的詳細信息,然后更新安全上下文中的身份驗證令牌 * 最后一步執(zhí)行過濾器chain,別忘記放行 * @param request * @param response * @param filterChain * @throws ServletException * @throws IOException */ @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 從請求頭中獲取認證信息 final String authHeader = request.getHeader("Authorization"); final String jwt; final String username; if(authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } jwt = authHeader.substring(7); // 從token中解析出username username = jwtService.extractUsername(jwt); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null){ // 根據jwt解析出來的username,獲取數據庫中的用戶信息,封裝UserDetails對象 UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); // TODO 此處token有效性可以從redis|數據庫中獲取 Boolean isTokenValid = true; if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) { // TODO 如果令牌有效,封裝一個UsernamePasswordAuthenticationToken對象 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, // 用戶憑證 null, userDetails.getAuthorities()); authentication.setDetails( new WebAuthenticationDetailsSource().buildDetails(request)); // 更新安全上下文的持有用戶 SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } } }
4.改造自動生成的SysUser
package org.pp.boot3.domain; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Collection; import java.util.Date; import java.util.List; import com.github.xiaoymin.knife4j.annotations.Ignore; import lombok.Data; import org.pp.boot3.domain.enums.Role; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; /** * 用戶表 * @author ss_419 * @TableName sys_user */ @TableName(value ="sys_user") @Data public class SysUser implements UserDetails { /** * 會員id */ @TableId(type = IdType.AUTO) private Long id; /** * 用戶名 */ private String username; /** * 密碼 */ private String password; /** * 姓名 */ private String name; /** * 手機 */ private String phone; /** * 頭像地址 */ private String headUrl; /** * 部門id */ private Long deptId; /** * 崗位id */ private Long postId; /** * 描述 */ private String description; /** * 狀態(tài)(1:正常 0:停用) */ private Integer status; /** * 創(chuàng)建時間 */ private Date createTime; /** * 更新時間 */ private Date updateTime; /** * 刪除標記(0:可用 1:已刪除) */ private Integer isDeleted; /** * 角色集合 */ private Role role; @TableField(exist = false) private static final long serialVersionUID = 1L; @Override public boolean equals(Object that) { if (this == that) { return true; } if (that == null) { return false; } if (getClass() != that.getClass()) { return false; } SysUser other = (SysUser) that; return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId())) && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername())) && (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword())) && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName())) && (this.getPhone() == null ? other.getPhone() == null : this.getPhone().equals(other.getPhone())) && (this.getHeadUrl() == null ? other.getHeadUrl() == null : this.getHeadUrl().equals(other.getHeadUrl())) && (this.getDeptId() == null ? other.getDeptId() == null : this.getDeptId().equals(other.getDeptId())) && (this.getPostId() == null ? other.getPostId() == null : this.getPostId().equals(other.getPostId())) && (this.getDescription() == null ? other.getDescription() == null : this.getDescription().equals(other.getDescription())) && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus())) && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime())) && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime())) && (this.getIsDeleted() == null ? other.getIsDeleted() == null : this.getIsDeleted().equals(other.getIsDeleted())); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getId() == null) ? 0 : getId().hashCode()); result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode()); result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode()); result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); result = prime * result + ((getPhone() == null) ? 0 : getPhone().hashCode()); result = prime * result + ((getHeadUrl() == null) ? 0 : getHeadUrl().hashCode()); result = prime * result + ((getDeptId() == null) ? 0 : getDeptId().hashCode()); result = prime * result + ((getPostId() == null) ? 0 : getPostId().hashCode()); result = prime * result + ((getDescription() == null) ? 0 : getDescription().hashCode()); result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode()); result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode()); result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode()); result = prime * result + ((getIsDeleted() == null) ? 0 : getIsDeleted().hashCode()); return result; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append(" ["); sb.append("Hash = ").append(hashCode()); sb.append(", id=").append(id); sb.append(", username=").append(username); sb.append(", password=").append(password); sb.append(", name=").append(name); sb.append(", phone=").append(phone); sb.append(", headUrl=").append(headUrl); sb.append(", deptId=").append(deptId); sb.append(", postId=").append(postId); sb.append(", description=").append(description); sb.append(", status=").append(status); sb.append(", createTime=").append(createTime); sb.append(", updateTime=").append(updateTime); sb.append(", isDeleted=").append(isDeleted); sb.append(", serialVersionUID=").append(serialVersionUID); sb.append("]"); return sb.toString(); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { // return List.of(new SimpleGrantedAuthority(role.name())); } /** * 用戶沒有過期 * @return */ @Override public boolean isAccountNonExpired() { return true; } @Override public String getUsername(){ return username; } @Override public String getPassword() { return password; } /** * 用戶沒有鎖定 * @return */ @Override public boolean isAccountNonLocked() { return true; } /** * 用戶憑證沒有過期 * @return */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 用戶是否啟用 * @return */ @Override public boolean isEnabled() { return true; } }
創(chuàng)建角色枚舉:
package org.pp.boot3.domain.enums; /** * 用戶角色信息枚舉 * @author ss_419 */ public enum Role { USER, ADMIN }
5.配置Security以啟用上面配置的JwtAuthenticationFilter
package org.pp.boot3.config.security; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutHandler; /** * TODO 安全配置 * * @author ss_419 * @version 1.0 * @date 2023/3/3 14:04 */ @Configuration @EnableWebSecurity// 開啟網絡安全注解 @RequiredArgsConstructor public class SecurityConfiguration { // 將自定義JwtAuthenticationFilter注入 private final JwtAuthenticationFilter jwtAuthenticationFilter; // 在ApplicationConfig中提供Bean private final AuthenticationProvider authenticationProvider; private final LogoutHandler logoutHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http //禁用csrf(防止跨站請求偽造攻擊) .csrf() .disable() // 設置白名單 .authorizeHttpRequests() .requestMatchers("/api/v1/auth/**") .permitAll() // 對于其他任何請求,都保護起來 .anyRequest() .authenticated() .and() // 禁用緩存 .sessionManagement() // 使用無狀態(tài)session,即不使用session緩存數據 .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 添加身份驗證 .and() // TODO 添加身份驗證1 .authenticationProvider(authenticationProvider) // 添加JWT過濾器 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 登出操作 .logout() .logoutUrl("/api/v1/auth/logout") .addLogoutHandler(logoutHandler) .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext()) ; return http.build(); } }
定義請求響應實體
/** * 驗證請求實體 * @author ss_419 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class AuthenticationRequest { private String username; String password; }
/** * 請求響應實體 * @author ss_419 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class AuthenticationResponse { private String token; }
/** * 注冊請求實體 * @author ss_419 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class RegisterRequest { private String firstname; private String lastname; private String username; private String password; }
定義注冊認證服務
import org.pp.boot3.domain.AuthenticationRequest; import org.pp.boot3.domain.AuthenticationResponse; import org.pp.boot3.domain.RegisterRequest; import org.pp.boot3.domain.SysUser; /** * 授權測試服務 * @author ss_419 */ public interface AuthenticationService { /** * 注冊 * @param request * @return */ public AuthenticationResponse register(RegisterRequest request); /** * 登錄|認證 * @param request * @return */ public AuthenticationResponse authenticate(AuthenticationRequest request); /** * 保存用戶token信息 * @param user * @param jwtToken */ // void saveUserToken(SysUser user, String jwtToken); /** * 刪除用戶token信息 * @param user */ // void revokeAllUserTokens(SysUser user); }
/** * TODO * * @author ss_419 * @version 1.0 * @date 2023/3/3 14:27 */ @Service @RequiredArgsConstructor public class AuthenticationServiceImpl implements AuthenticationService { private final SysUserMapper repository; private final PasswordEncoder passwordEncoder; private final JwtService jwtService; private final RedisCache redisCache; private final AuthenticationManager authenticationManager; @Override public AuthenticationResponse register(RegisterRequest request) { SysUser user = SysUser.builder() .username(request.getUsername()) .password(passwordEncoder.encode(request.getPassword())) .role(Role.USER) .build(); repository.insert(user); String jwtToken = jwtService.generateToken(user); // 將token存儲 redisCache.setCacheObject("token:" ,jwtToken); // 將token返回響應 return AuthenticationResponse .builder() .token(jwtToken) .build(); } @Override public AuthenticationResponse authenticate(AuthenticationRequest request) { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( request.getUsername(), request.getPassword() ) ); SysUser user = repository.findByUsername(request.getUsername()); var jwtToken = jwtService.generateToken(user); // 將token存儲 redisCache.setCacheObject("token:" ,jwtToken); // 將token返回響應 return AuthenticationResponse.builder() .token(jwtToken) .build(); } private void saveUserToken(SysUser user, String jwtToken) { } private void revokeAllUserTokens(SysUser user) { } }
到此這篇關于SpringBoot3.0+SpringSecurity6.0+JWT的實現的文章就介紹到這了,更多相關SpringBoot SpringSecurity JWT內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- SpringSecurity多表多端賬戶登錄的實現
- SpringSecurity集成第三方登錄過程詳解(最新推薦)
- springsecurity實現用戶登錄認證快速使用示例代碼(前后端分離項目)
- SpringSecurity自動登錄流程與實現詳解
- SpringSecurity6自定義JSON登錄的實現
- SpringSecurity6.x多種登錄方式配置小結
- 如何使用JWT的SpringSecurity實現前后端分離
- SpringSecurity+Redis+Jwt實現用戶認證授權
- SpringSecurity角色權限控制(SpringBoot+SpringSecurity+JWT)
- springSecurity之如何添加自定義過濾器
- springSecurity自定義登錄接口和JWT認證過濾器的流程
相關文章
SpringBoot @ModelAttribute使用場景分析
這篇文章主要介紹了SpringBoot @ModelAttribute使用場景分析,文中通過實例代碼圖文相結合給大家介紹的非常詳細,需要的朋友可以參考下2021-08-08IDEA創(chuàng)建的maven項目中pom.xml增加新依賴無效問題及解決
在IDEA中,解決maven項目pom.xml增加依賴但外部庫未更新的問題,可以通過設置"構建腳本更改后同步項目"選項為"任何更改",然后刷新Maven項目來解決2025-01-01idea之Recompile、Rebuild和Build之間的區(qū)別及說明
這篇文章主要介紹了idea之Recompile、Rebuild和Build之間的區(qū)別及說明,具有很好的參考價值,希望對有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08