Spring Security jwt 身份验证
Posted
技术标签:
【中文标题】Spring Security jwt 身份验证【英文标题】:Spring Security jwt authentication 【发布时间】:2022-01-15 21:27:31 【问题描述】:我正在使用 spring boot 和 jwt 创建一个后端应用程序以进行身份验证。
我的问题是我无法让当局按照我希望他们的方式工作。我可以登录我的用户并取回 jwt。但是,当在服务器上请求仅允许特定权限的路径时,即使我没有发送授权标头,我也会返回 200。
这是我的代码:
SecurityConfig.kt
@EnableWebSecurity
class SecurityConfig(
private val userDetailsService: UserDetailsService,
private val jwtAuthenticationFilter: JwtAuthenticationFilter)
: WebSecurityConfigurerAdapter()
override fun configure(httpSecurity: HttpSecurity)
httpSecurity.csrf().disable()
.authorizeRequests()
.antMatchers( "/$Constants.API_PATH/$Constants.USER_PATH/**") // translates to /api/v1/users/**
.hasAuthority("USER")
.antMatchers("/$Constants.API_PATH/$Constants.EMAIL_VERIFICATION_PATH/**")
.permitAll()
.antMatchers("/$Constants.API_PATH/$Constants.LOGIN_PATH")
.permitAll()
.anyRequest()
.authenticated()
httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
override fun configure(auth: AuthenticationManagerBuilder)
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder())
@Bean
fun encoder(): PasswordEncoder
return BCryptPasswordEncoder() // salts the password
@Bean(BeanIds.AUTHENTICATION_MANAGER)
override fun authenticationManagerBean(): AuthenticationManager
return super.authenticationManagerBean()
LoginService.kt(不知道这段代码是否相关,但也许这里有问题)
@Service
class LoginService(private val authenticationManager: AuthenticationManager,
private val jwtProvider: JwtProvider,
private val userService: UserService)
fun login(loginRequest: LoginRequest): LoginResponse
// authenticate internally call UserDetailsService
val authenticate = authenticationManager.authenticate(
UsernamePasswordAuthenticationToken(loginRequest.email, loginRequest.password))
SecurityContextHolder.getContext().authentication = authenticate
//TODO implement logic for employer
val jwtToken = jwtProvider.generateToken(authenticate)
val jobseeker = jobseekerService.getJobseeker(loginRequest.email)
return LoginResponse(jwtToken, jobseeker)
UserDetailsServiceImpl.kt
@Service
class UserDetailsServiceImpl(private val userRepository: UserRepository) : UserDetailsService
/* we don't use usernames, so we pass the email address here*/
override fun loadUserByUsername(username: String?): UserDetails
val user = userRepository.findByEmail(username) ?: throw CustomException("jobseeker not found")
return org.springframework.security.core.userdetails.User(user.email, user.getHashedSecret(),
jobseeker.getActivated(), true, true, true,
getAuthorities("USER"))
private fun getAuthorities(authority: String) = singletonList(SimpleGrantedAuthority(authority))
JwtProvider.kt(我知道“秘密”不是一个好秘密,它只是为了测试目的)
@Service
class JwtProvider
val secret: String = "secret"
// TODO: IMPORTANT -> the keystore is selfsigned and needs to be changed as soon as we get
// to the first production version
private lateinit var keyStore: KeyStore
@PostConstruct
fun init()
try
keyStore = KeyStore.getInstance("JKS")
// very helpful for resources: https://***.com/questions/4301329/java-class-getresource-returns-null
val resourceAsStream: InputStream? = javaClass.getResourceAsStream("/key.jks")
keyStore.load(resourceAsStream, secret.toCharArray())
catch (ex: Exception)
throw CustomException("problem while loading keystore")
fun generateToken(authentication: Authentication): String
val pricipal: User = authentication.principal as User
return Jwts.builder()
.setSubject(pricipal.username)
.signWith(getPrivateKey())
.compact()
private fun getPrivateKey(): PrivateKey
return try
keyStore.getKey("key", secret.toCharArray()) as PrivateKey
catch (ex: Exception)
logger.error(ex.message)
throw CustomException("problem while retreiving the private key for jwt signing")
private fun getPublicKey(): PublicKey
return try
keyStore.getCertificate("key").publicKey
catch (ex: KeyStoreException)
throw CustomException("problem while retreiving public key")
fun validateToken(jwt: String): Boolean
// using parseClaimsJws() because the jwt is signed
Jwts.parserBuilder().setSigningKey(getPublicKey()).build().parseClaimsJws(jwt)
return true
fun getEmailFromJwt(jwt: String): String
return Jwts.parserBuilder().setSigningKey(getPublicKey()).build().parseClaimsJws(jwt).body.subject
JwtAuthenticationFilter.kt
@Component
class JwtAuthenticationFilter(
private val jwtProvider: JwtProvider,
private val userDetailsService: UserDetailsService
) : OncePerRequestFilter()
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
)
val jwt: String = getJwtFromRequest(request)
if (StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt))
val email: String = jwtProvider.getEmailFromJwt(jwt)
val userDetails: UserDetails = userDetailsService.loadUserByUsername(email)
val authentication = UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities)
authentication.details = (WebAuthenticationDetailsSource().buildDetails(request))
SecurityContextHolder.getContext().authentication = authentication
filterChain.doFilter(request, response)
fun getJwtFromRequest(request: HttpServletRequest): String
val bearerToken: String = request.getHeader("Authorization") ?: ""
// hasText() is needed because there are api without auth and this string could be null
if(StringUtils.hasText(bearerToken))
return bearerToken.substringAfter(" ")
return bearerToken
所以,当我现在尝试不使用 jwt 作为不记名令牌的 GET http://localhost:8080/api/v1/users
或 GET http://localhost:8080/api/v1/users/<uuid>
时,我得到的是 200 而不是 403。
我已经尝试了好几个小时,但完全被卡住了。
我对 kotlin 和 spring boot 也很陌生,如果有任何关于以更优雅的方式编写我提供的代码的提示,我将不胜感激。
【问题讨论】:
【参考方案1】:如果jwt
在您的JwtAuthenticationFilter
中为空,您可以简单地抛出异常:
...
val jwt: String = getJwtFromRequest(request)
if (jwt.isEmpty())
throw new AccessDeniedException("Missing JWT Token")
...
或者,您可以执行以下操作:
...
val jwt: String = getJwtFromRequest(request)
if (jwt.isEmpty())
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
...
【讨论】:
感谢您的回答,但这不是我想要实现的行为。我希望未经身份验证的用户可以访问登录和 email_verification API,并且只有用户 API 需要身份验证。我认为antMatchers()
结合SecurityConfig.kt
中的hasAuthority()
应该可以解决问题,但事实并非如此。你知道为什么吗?以上是关于Spring Security jwt 身份验证的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Spring Security 中为所有请求添加 jwt 身份验证标头?
如何在 Spring Security 中实现基于 JWT 的身份验证和授权
在 Spring Security UsernamePasswordAuthenticationFilter JWT 身份验证中设置自定义登录 url
我可以有两个 Spring Security 配置类:一个使用基本身份验证保护一些 API,另一个使用 JWT 令牌保护 API?
Spring Security JWT - 通过授权标头的邮递员身份验证有效,但从我的 Angular 前端不起作用
前后端分离的Web应用程序中使用Spring Security+Mybatis+JWT非对称加密+动态权限管理:身份验证过滤器