【问题标题】:Spring Security jwt authenticationSpring Security jwt 身份验证
【发布时间】: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)
    }

UserDetailsS​​erviceImpl.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://stackoverflow.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/usersGET http://localhost:8080/api/v1/users/<uuid> 时,我得到的是 200 而不是 403。

我已经尝试了好几个小时,但完全被卡住了。

我对 kotlin 和 spring boot 也很陌生,如果有任何关于以更优雅的方式编写我提供的代码的提示,我将不胜感激。

【问题讨论】:

    标签: java spring spring-boot kotlin jwt


    【解决方案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() 应该可以解决问题,但事实并非如此。你知道为什么吗?
    猜你喜欢
    • 2022-08-16
    • 2019-02-07
    • 2021-01-27
    • 2021-08-22
    • 2018-02-27
    • 2014-02-26
    • 2022-10-05
    • 2013-01-15
    • 2019-08-25
    相关资源
    最近更新 更多