kotlin + spirngboot3 + spring security6 配置登錄與JWT

1. 導包

implementation("com.auth0:java-jwt:3.14.0")
implementation("org.springframework.boot:spring-boot-starter-security")

配置用戶實體類


@Entity
@Table(name = "users")
data class User(@Id@GeneratedValue(strategy = GenerationType.IDENTITY)val uid: Long = 0,@Column(nullable = false, name = "username")val username: String = String.EMPTY_STRING,@Column(nullable = false, name = "password")val password: String = String.EMPTY_STRING,@Column(nullable = true)val email: String? = null,@Column(nullable = true)val phone: String? = null,
) {// 該方法作用是將實體類轉為 UserUserDetails ,下文需要用到fun convertUserUserDetails(): UserDetailsImpl = UserDetailsImpl(this)class UserDetailsImpl(val user: User) : UserDetails {override fun getAuthorities(): MutableCollection<out GrantedAuthority> {return mutableListOf(SimpleGrantedAuthority("ROLE_USER"))}override fun getPassword(): String = user.passwordoverride fun getUsername(): String = user.username}
}

配置用戶服務

@Service
class UserService(override val repository: UserRepository,
) : UserDetailsService {// ..... 其他方法// 需要繼承 UserDetailsService  并重寫該方法,用于查找用戶// 通常 identifier 傳入的 username,但是我的代碼并非使用 username 登錄,這里稍微替換下即可// 如果沒有查找到 user 拋出異常// 方法返回需要 UserDetails 需要將 user 實體類轉為 UserDetails override fun loadUserByUsername(identifier: String): UserDetailsImpl = when {identifier.isEmail -> findByEmail(identifier) ?: throw UsernameNotFoundException("Email not registered")identifier.isNumber -> findByPhone(identifier) ?: throw UsernameNotFoundException("Phone not registered")else -> throw UsernameNotFoundException("not found account $identifier")}.convertUserUserDetails()
}

配置 JWT 過濾器

// 主要方法為 isPass
// jwtConfig 是自己寫的用于驗證和簽發 jwt,并存儲 jwt 的設置,可以自己自行替換
class JwtTokenFilter(private val userService: UserService, private val jwtConfig: JwtConfig) : OncePerRequestFilter() {private fun isPass(request: ServletRequest, response: HttpServletResponse): Boolean {val jwt = getJwtString(request) ?: return falseval id = jwtConfig.decodeUserId(jwt) ?: return trueval user = userService.findByUid(id) ?: return trueval decodedJWT = jwtConfig.verifyLoginJwt(jwt, user) ?: return true// 主要邏輯:驗證成功,在上下文中設置 AuthenticationTokensetAuthenticationToken(user)if (jwtConfig.autoRefresh) refresh(response, user, decodedJWT)return false}private fun refresh(response: ServletResponse,user: User,decodedJWT: DecodedJWT,): ResultLoginBean {if (decodedJWT.expiresAt.time - System.currentTimeMillis() <= 60 * 1000) {return ResultLoginBean.success(user, jwtConfig).apply {if (!jwtConfig.autoSetCookie) return@applyval cookie = Cookie(jwtConfig.cookieName, data?.token)cookie.isHttpOnly = truecookie.secure = falsecookie.maxAge = (jwtConfig.effectiveTime).toInt()cookie.path = "/"(response as HttpServletResponse).addCookie(cookie)}}return ResultLoginBean.success(ResultLoginBean.DataBean(decodedJWT.token))}private fun getJwtString(request: ServletRequest): String? {val value = getCookie(request)?.firstOrNull() { it.name == jwtConfig.cookieName }?.value ?: ""val value2 = getHeaders(request, "Authorization")?.replace(jwtConfig.headerPrefix, "") ?: ""return if (jwtConfig.fromCookie && value.isNotBlank()) {value} else if (jwtConfig.fromHeader && value2.isNotBlank()) {value2} else {null}}private fun getCookie(request: ServletRequest): Array<out Cookie>? {if (request is RequestFacade) return request.cookiesif (request is HttpServletRequest) return request.cookiesif (request is ServletRequestWrapper) {return getCookie(request.request)}return null}private fun getHeaders(request: ServletRequest, name: String): String? {if (request is RequestFacade) return request.getHeader(name)if (request is HttpServletRequest) return request.getHeader(name)if (request is ServletRequestWrapper) {return getHeaders(request.request, name)}return null}private fun setAuthenticationToken(user: User) {SecurityContextHolder.getContext().authentication = usernamePasswordAuthenticationToken(user)}// 這個類很長有用的不多,注意以下幾點// 1. 認證成功,則在上下文中設置 usernamePasswordAuthenticationToken// 2. 認證失敗,清空上下文// 3. 無論成功與失敗都調用下一個過濾器,當然如果失敗了你不調用下一個過濾器也行override fun doFilterInternal(request: HttpServletRequest,response: HttpServletResponse,filterChain: FilterChain,) {// 如果前端沒有傳入 token 則清理上下文if (isPass(request, response)) {SecurityContextHolder.clearContext()}// 無論Token 是否驗證成功都傳給下一個過濾器filterChain.doFilter(request, response)}}

配置 Spring Security

spring security 6 需要使用 filterChain 來配置認證鏈,并且 推薦使用 DSL 方式進行配置即Lambda方式

@Configuration
@EnableWebSecurity
class SpringSecurityConfig(private val userService: UserService, // 你的 User 服務用于查詢用戶的,但是需要實現 UserDetailsService 接口
){@Beanfun filterChain(http: HttpSecurity): SecurityFilterChain = http.run {// 由于使用 JWT 所以這里 關閉 sessionsessionsessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }// 配置哪些請求需要驗證或者放行authorizeHttpRequests {it.requestMatchers("/api/authority/**").permitAll()// 這種模糊匹配的范圍很大需要在精確的路徑之后配置,否則精確配置不生效it.requestMatchers("/api/**").authenticated() it.anyRequest().permitAll()}// 配置異常處理exceptionHandling {// 沒有登錄的錯誤it.authenticationEntryPoint { _, response, _ ->response.close(BaseBean(BaseBean.Code.LOGIN_EXPIRED))}// 沒有權限的錯誤it.accessDeniedHandler { _, response, _ ->response.close(BaseBean(BaseBean.Code.PERMISSION_DENIED))}}// 添加 JWT 的認證過濾器到 UsernamePasswordAuthenticationFilter 鏈中addFilterBefore(JwtTokenFilter(userService, jwtConfig), UsernamePasswordAuthenticationFilter::class.java)isDisableFormLogin(false)httpBasic { it.disable() }rememberMe { it.disable() }// 生產下必須關閉 csrfcsrf { it.disable() }cors(Customizer.withDefaults())build()}
}/*** 兩種登錄方式,一種使用自定義登錄接口,,一種使用框架本身的表單登錄* 使用框架本身的表單登錄會覆蓋  /api/authority/login Controller* /api/authority/login Controller 實現不展出了,邏輯是驗證用戶名和密碼之后返回 toekn(jwt)*/private fun HttpSecurity.isDisableFormLogin(isDisable: Boolean) {if (isDisable) {formLogin { it.disable() }return}formLogin {it.loginProcessingUrl("/api/authority/login") // 指定你的登錄接口it.usernameParameter("identifier")it.passwordParameter("password")it.successHandler { request, response, auth ->// 這里將登錄后 Token 發生回了前端// auth.principal as User.UserDetailsImpl 能轉為 UserDetailsImpl  的愿意為,下面驗證的時候傳入的 UserDetailsImpl val user = (auth.principal as User.UserDetailsImpl).getUser()response.close(ResultLoginBean.success(user, jwtConfig, response))}it.failureHandler { request, response, exception ->response.close(BaseBean.fail("登錄失敗: ${exception.message}"))}}}/*** 身份校驗機制、身份驗證提供程序(isDisableFormLogin 中設置為 false)* 驗證成功后會在認證鏈里面傳遞 UsernamePasswordAuthenticationToken */@Beanfun authenticationProvider(passwordEncoder: PasswordEncoder): AuthenticationProvider =object : AuthenticationProvider {override fun authenticate(authentication: Authentication): Authentication {val identifier = authentication.nameval password = authentication.credentials.toString()val user = userService.loadUserByUsername(identifier)if (passwordEncoder.matches(password, user.password)) {return usernamePasswordAuthenticationToken(user)} else {throw BadCredentialsException("The password is wrong")}}override fun supports(authentication: Class<*>): Boolean {return UsernamePasswordAuthenticationToken::class.java.isAssignableFrom(authentication)}}/*** 基于用戶名和密碼或使用用戶名和密碼進行身份驗證*/@Beanfun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager =config.getAuthenticationManager()// 配置拓展方法fun <T : Any> ServletResponse.close(data: BaseBean<T>) {this.contentType = "application/json;charset=UTF-8"this.writer.write(data.toString())this.writer.flush()this.writer.close()}@Bean(name = ["passwordEncoder", "bcryptPasswordEncoder"])fun passwordEncoder(): PasswordEncoder {return BCryptPasswordEncoder()}fun usernamePasswordAuthenticationToken(user: User): UsernamePasswordAuthenticationToken {return usernamePasswordAuthenticationToken(user.convertUserUserDetails())}fun usernamePasswordAuthenticationToken(user: User.UserDetailsImpl): UsernamePasswordAuthenticationToken {return UsernamePasswordAuthenticationToken(user, user.password,user.authorities)}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/902300.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/902300.shtml
英文地址,請注明出處:http://en.pswp.cn/news/902300.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【JavaWeb后端開發03】MySQL入門

文章目錄 1. 前言1.1 引言1.2 相關概念 2. MySQL概述2.1 安裝2.2 連接2.2.1 介紹2.2.2 企業使用方式(了解) 2.3 數據模型2.3.1 **關系型數據庫&#xff08;RDBMS&#xff09;**2.3.2 數據模型 3. SQL語句3.1 DDL語句3.1.1 數據庫操作3.1.1.1 查詢數據庫3.1.1.2 創建數據庫3.1.1…

人工智能在智能家居中的應用與發展

隨著人工智能&#xff08;AI&#xff09;技術的飛速發展&#xff0c;智能家居逐漸成為現代生活的重要組成部分。從智能語音助手到智能家電&#xff0c;AI正在改變我們與家居環境的互動方式&#xff0c;讓生活更加便捷、舒適和高效。本文將探討人工智能在智能家居中的應用現狀、…

【EasyPan】項目常見問題解答(自用持續更新中…)

EasyPan 網盤項目介紹 一、項目概述 EasyPan 是一個基于 Vue3 SpringBoot 的網盤系統&#xff0c;支持文件存儲、在線預覽、分享協作及后臺管理&#xff0c;技術棧涵蓋主流前后端框架及中間件&#xff08;MySQL、Redis、FFmpeg&#xff09;。 二、核心功能模塊 用戶認證 注冊…

4.1騰訊校招簡歷優化與自我介紹攻略:公式化表達+結構化呈現

騰訊校招簡歷優化與自我介紹攻略&#xff1a;公式化表達結構化呈現 在騰訊校招中&#xff0c;簡歷是敲開面試大門的第一塊磚&#xff0c;自我介紹則是展現個人魅力的黃金30秒。本文結合騰訊面試官偏好&#xff0c;拆解簡歷撰寫公式、自我介紹黃金結構及分崗位避坑指南&#xf…

【Easylive】consumes = MediaType.MULTIPART_FORM_DATA_VALUE 與 @RequestPart

【Easylive】項目常見問題解答&#xff08;自用&持續更新中…&#xff09; 匯總版 consumes MediaType.MULTIPART_FORM_DATA_VALUE 的作用 1. 定義請求的數據格式 ? 作用&#xff1a;告訴 Feign 和 HTTP 客戶端&#xff0c;這個接口 接收的是 multipart/form-data 格式的…

OpenSSL1.1.1d windows安裝包資源使用

環境&#xff1a; QT版本&#xff1a;5.14.2 用途: openssl1.1.1d版本 問題描述&#xff1a; 今天嘗試用百度云人臉識別api搭載QT的人臉識別程序&#xff0c;需要用到 QNetworkManager 訪問 https 開頭的網址。 但是遇到了QT缺乏 openssl 的相關問題&#xff0c;找了大半天…

代碼實戰保險花銷預測

文章目錄 摘要項目地址實戰代碼&#xff08;初級版&#xff09;實戰代碼&#xff08;進階版&#xff09; 摘要 本文介紹了一個完整的機器學習流程項目&#xff0c;重點涵蓋了多元線性回歸的建模與評估方法。項目詳細講解了特征工程中的多項實用技巧&#xff0c;包括&#xff1…

RS232 串行通信:C++ 實現指南

文章目錄 一、RS232 簡介1. 電氣特性2. 傳輸速率3. 傳輸距離 二、在 C 中實現 RS232 通信1. Windows 平臺&#xff08;1&#xff09;打開串行端口&#xff08;2&#xff09;配置串行通信參數&#xff08;3&#xff09;發送數據&#xff08;4&#xff09;接收數據&#xff08;5&…

Linux指令合集

一、VI的使用 命令行模式&#xff1a;默認此模式&#xff0c;從輸入模式回到命令行模式&#xff1a;esc &#xff0c; esc按完&#xff0c;insert消失 輸入模式&#xff1a;按 i 進入 &#xff0c; 看到insert就能編輯代碼 退出vi 保存代碼 命令行模式下 按&#xf…

IDEA使用jclasslib Bytecode Viewer查看jvm字節碼

學習jvm的時候&#xff0c;想查看字節碼和局部變量表&#xff0c;可以使用idea安裝jclasslib Bytecode View插件查看。 &#xff08;1&#xff09;安裝工具&#xff1a; 安裝完成后需要重啟idea. &#xff08;2&#xff09;準備一段代碼&#xff0c;編譯運行 package com.te…

從多個Excel批量篩查數據后合并到一起

這篇文章將講解如何批量的從多個Excel文件中篩選出需要的數據&#xff0c;最后合并到一張新的Excel。 全程0代碼圖形化界面操作。 準備數據 這里準備了3個測試文件&#xff0c;每個文件的格式是一樣的 現在我們需要篩選出每個文件里面&#xff0c;基金簡稱包含“南方遠見”&a…

Debian GNU/Linux的新手入門介紹

Debian GNU/Linux&#xff1a;起源、基本介紹與發行版對比 一、起源與發展歷程 Debian GNU/Linux 是現存最古老的 Linux 發行版之一&#xff0c;由 Ian Murdock 于 1993 年 8 月 16 日創立。其名稱結合了他的女友&#xff08;后成為妻子&#xff09;Debra 和他自己的名字 Ian…

Sentinel源碼—7.參數限流和注解的實現一

大綱 1.參數限流的原理和源碼 2.SentinelResource注解的使用和實現 1.參數限流的原理和源碼 (1)參數限流規則ParamFlowRule的配置Demo (2)ParamFlowSlot根據參數限流規則驗證請求 (1)參數限流規則ParamFlowRule的配置Demo 一.參數限流的應用場景 二.參數限流規則的屬性 …

多數據源配置(MyBatis-Plus vs AbstractRoutingDataSource)

MyBatis-Plus vs AbstractRoutingDataSource MyBatis-Plus多數據源配 1.添加依賴 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.0</version> <…

聊透多線程編程-線程互斥與同步-13. C# Mutex類實現線程互斥

目錄 一、什么是臨界區&#xff1f; 二、Mutex類簡介 三、Mutex的基本用法 解釋&#xff1a; 四、Mutex的工作原理 五、使用示例1-保護共享資源 解釋&#xff1a; 六、使用示例2-跨進程同步 示例場景 1. 進程A - 主進程 2. 進程B - 第二個進程 輸出結果 ProcessA …

stm32week12

stm32學習 九.stm32與HAL庫 2.HAL庫框架 總架構&#xff1a; 文件介紹&#xff1a; ppp是某一外設&#xff0c;ex是拓展功能 HAL庫API函數和變量命名規則&#xff1a; HAL庫對寄存器位操作的相關宏定義&#xff1a; HAL庫的回調函數&#xff1a; 3.STM32啟動過程 MDK編譯過…

opencv HSV的具體描述

色調H&#xff1a; 使用角度度量&#xff0c;取值范圍為0\~360&#xff0c;從紅色開始按逆時針方向計算&#xff0c;紅色為0&#xff0c;綠色為120&#xff0c;藍色為240。它們的補色是&#xff1a;黃色為60&#xff0c;青色為180&#xff0c;紫色為300。通過改變H的值&#x…

Java Lambda表達式指南

一、Lambda表達式基礎 1. 什么是Lambda表達式&#xff1f; 匿名函數&#xff1a;沒有名稱的函數函數式編程&#xff1a;可作為參數傳遞的代碼塊簡潔語法&#xff1a;替代匿名內部類的更緊湊寫法 2. 基本語法 (parameters) -> expression 或 (parameters) -> { statem…

面向對象設計中的類的分類:實體類、控制類和邊界類

目錄 前言1. 實體類&#xff08;Entity Class&#xff09;1.1 定義和作用1.2 實體類的特點1.3 實體類的示例 2. 控制類&#xff08;Control Class&#xff09;2.1 定義和作用2.2 控制類的特點2.3 控制類的示例 3. 邊界類&#xff08;Boundary Class&#xff09;3.1 定義和作用3…

C# 封裝教程

原文&#xff1a;C# 封裝_w3cschool &#xff08;注&#xff1a;本文為教程文章&#xff0c;請勿標記為付費文章&#xff01;特此聲明&#xff09; 封裝 被定義為"把一個或多個項目封閉在一個物理的或者邏輯的包中"。在面向對象程序設計方法論中&#xff0c;封裝是…