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.mzph.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;封装是…