簡體   English   中英

Spring 安全 jwt 認證

[英]Spring Security jwt authentication

我正在使用 spring 引導和 jwt 創建一個后端應用程序以進行身份驗證。

我的問題是我無法讓當局按照我的意願工作。 我可以登錄我的用戶並取回 jwt。 但是,當在服務器上請求僅允許特定權限的路徑時,即使我沒有發送授權 header,我也會返回 200。

這是我的代碼:

安全配置.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://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
    }
}

So when I now try GET http://localhost:8080/api/v1/users or GET http://localhost:8080/api/v1/users/<uuid> without the jwt as a bearer token I get back a 200而不是 403。

我已經嘗試了好幾個小時了,完全被卡住了。

我對 kotlin 和 spring 啟動也相當陌生,如果我能以更優雅的方式編寫代碼,我將不勝感激。

如果jwtJwtAuthenticationFilter中為空,您可以簡單地拋出異常:

...
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;
}
...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM