|
@@ -0,0 +1,225 @@
|
|
|
|
+package inn.ocsf.bee.freigeld.serve
|
|
|
|
+
|
|
|
|
+import io.jsonwebtoken.Claims
|
|
|
|
+import io.jsonwebtoken.ExpiredJwtException
|
|
|
|
+import io.jsonwebtoken.Jwts
|
|
|
|
+import io.jsonwebtoken.security.Keys
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired
|
|
|
|
+import org.springframework.context.annotation.Bean
|
|
|
|
+import org.springframework.context.annotation.Configuration
|
|
|
|
+import org.springframework.security.authentication.AuthenticationManager
|
|
|
|
+import org.springframework.security.authentication.BadCredentialsException
|
|
|
|
+import org.springframework.security.authentication.DisabledException
|
|
|
|
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
|
|
|
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
|
|
|
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
|
|
|
|
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
|
|
|
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
|
|
|
+import org.springframework.security.config.http.SessionCreationPolicy
|
|
|
|
+import org.springframework.security.core.AuthenticationException
|
|
|
|
+import org.springframework.security.core.GrantedAuthority
|
|
|
|
+import org.springframework.security.core.authority.SimpleGrantedAuthority
|
|
|
|
+import org.springframework.security.core.context.SecurityContextHolder
|
|
|
|
+import org.springframework.security.core.userdetails.User
|
|
|
|
+import org.springframework.security.core.userdetails.UserDetails
|
|
|
|
+import org.springframework.security.core.userdetails.UserDetailsService
|
|
|
|
+import org.springframework.security.crypto.password.NoOpPasswordEncoder
|
|
|
|
+import org.springframework.security.crypto.password.PasswordEncoder
|
|
|
|
+import org.springframework.security.web.AuthenticationEntryPoint
|
|
|
|
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
|
|
|
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
|
|
|
|
+import org.springframework.stereotype.Component
|
|
|
|
+import org.springframework.web.filter.OncePerRequestFilter
|
|
|
|
+import java.io.Serializable
|
|
|
|
+import java.nio.charset.StandardCharsets
|
|
|
|
+import java.util.*
|
|
|
|
+import javax.inject.Inject
|
|
|
|
+import javax.servlet.FilterChain
|
|
|
|
+import javax.servlet.http.HttpServletRequest
|
|
|
|
+import javax.servlet.http.HttpServletResponse
|
|
|
|
+import kotlin.collections.HashMap
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@Configuration
|
|
|
|
+@EnableGlobalMethodSecurity(prePostEnabled = true)
|
|
|
|
+class Security : WebSecurityConfigurerAdapter() {
|
|
|
|
+
|
|
|
|
+ @Inject
|
|
|
|
+ private lateinit var jwtUserDetailsService: JwtUserDetailsService
|
|
|
|
+
|
|
|
|
+ @Inject
|
|
|
|
+ private lateinit var jwtAuthEntryPoint: JwtAuthEntryPoint
|
|
|
|
+
|
|
|
|
+ @Inject
|
|
|
|
+ private lateinit var jwtRequestFilter: JwtRequestFilter
|
|
|
|
+
|
|
|
|
+ @Bean
|
|
|
|
+ @Throws(Exception::class)
|
|
|
|
+ override fun authenticationManagerBean(): AuthenticationManager? {
|
|
|
|
+ return super.authenticationManagerBean()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ @Throws(Exception::class)
|
|
|
|
+ fun configureGlobal(auth: AuthenticationManagerBuilder) {
|
|
|
|
+ auth.userDetailsService<UserDetailsService>(jwtUserDetailsService).passwordEncoder(passwordEncoder())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ @Bean
|
|
|
|
+ fun passwordEncoder(): PasswordEncoder {
|
|
|
|
+ return NoOpPasswordEncoder.getInstance()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Throws(Exception::class)
|
|
|
|
+ override fun configure(httpSecurity: HttpSecurity) {
|
|
|
|
+ httpSecurity
|
|
|
|
+ .csrf()
|
|
|
|
+ .disable()
|
|
|
|
+ .authorizeRequests()
|
|
|
|
+ .antMatchers("/api/new/person/**").permitAll()
|
|
|
|
+ .anyRequest().authenticated()
|
|
|
|
+ .and().exceptionHandling()
|
|
|
|
+ .authenticationEntryPoint(jwtAuthEntryPoint)
|
|
|
|
+ .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
|
|
|
+
|
|
|
|
+ httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter::class.java)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@Component
|
|
|
|
+class JwtAuthEntryPoint : AuthenticationEntryPoint, Serializable {
|
|
|
|
+
|
|
|
|
+ override fun commence(p0: HttpServletRequest?, p1: HttpServletResponse?, p2: AuthenticationException?) {
|
|
|
|
+ p1?.sendError(HttpServletResponse.SC_UNAUTHORIZED, "unauthorized")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@Component
|
|
|
|
+class JwtTokenUtil : Serializable {
|
|
|
|
+
|
|
|
|
+ companion object {
|
|
|
|
+ val secret = Keys.hmacShaKeyFor("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vel fermentum eros, a molestie nisl. Nunc viverra ante nulla, non tincidunt leo rutrum vel. Donec pulvinar ornare eros, at bibendum augue efficitur eu. Nunc sit amet massa sit amet nisi fringilla commodo ac in mi. Morbi viverra enim vel rhoncus semper. Phasellus semper convallis eros, ac pretium nulla aliquet ut. Ut fringilla leo ut purus gravida, quis vestibulum mi commodo. Curabitur feugiat odio at dapibus aliquam. Phasellus eget eros quis enim vulputate fringilla a sit amet nibh. In eget metus sed dui maximus auctor. Nunc eu ornare ligula, bibendum luctus lectus.".toByteArray(StandardCharsets.UTF_8))
|
|
|
|
+
|
|
|
|
+ val tokenLifetime = 5 * 60 * 60L
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fun getUsernameFromToken(token: String?): String {
|
|
|
|
+ return getClaimFromToken(token, Claims::getSubject)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fun getExpirationDateFromToken(token: String?): Date {
|
|
|
|
+ return getClaimFromToken<Date>(token, Claims::getExpiration)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fun <T> getClaimFromToken(token: String?, claimsResolver: (Claims) -> T): T {
|
|
|
|
+ val claims: Claims = getAllClaimsFromToken(token)
|
|
|
|
+ return claimsResolver(claims)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun getAllClaimsFromToken(token: String?): Claims {
|
|
|
|
+ return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).body
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun isTokenExpired(token: String?): Boolean {
|
|
|
|
+ val expiration: Date = getExpirationDateFromToken(token)
|
|
|
|
+ return expiration.before(Date())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fun generateToken(userDetails: UserDetails): String {
|
|
|
|
+ val claims: Map<String, Any> = HashMap()
|
|
|
|
+ return doGenerateToken(claims, userDetails.username)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun doGenerateToken(claims: Map<String, Any>, subject: String): String {
|
|
|
|
+ return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(Date(System.currentTimeMillis()))
|
|
|
|
+ .setExpiration(Date(System.currentTimeMillis() + tokenLifetime * 1000))
|
|
|
|
+ .signWith(secret).compact()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fun validateToken(token: String?, userDetails: UserDetails): Boolean {
|
|
|
|
+ val username = getUsernameFromToken(token)
|
|
|
|
+ return username == userDetails.username && !isTokenExpired(token)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@Component
|
|
|
|
+class JwtRequestFilter : OncePerRequestFilter() {
|
|
|
|
+
|
|
|
|
+ @Inject
|
|
|
|
+ private lateinit var jwtTokenUtil: JwtTokenUtil
|
|
|
|
+
|
|
|
|
+ @Inject
|
|
|
|
+ private lateinit var jwtUserDetailsService: JwtUserDetailsService
|
|
|
|
+
|
|
|
|
+ override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, next: FilterChain) {
|
|
|
|
+ val requestTokenHeader = request.getHeader("Authorization")
|
|
|
|
+ var username: String? = null
|
|
|
|
+ var jwtToken: String? = null
|
|
|
|
+
|
|
|
|
+ if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
|
|
|
|
+ jwtToken = requestTokenHeader.substring(7)
|
|
|
|
+ try {
|
|
|
|
+ username = jwtTokenUtil.getUsernameFromToken(jwtToken)
|
|
|
|
+ } catch (e: IllegalArgumentException) {
|
|
|
|
+ System.out.println("Unable to get JWT Token")
|
|
|
|
+ } catch (e: ExpiredJwtException) {
|
|
|
|
+ System.out.println("JWT Token has expired")
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ logger.warn("JWT Token does not begin with Bearer String")
|
|
|
|
+ }
|
|
|
|
+ if (username != null && SecurityContextHolder.getContext().authentication == null) {
|
|
|
|
+ val userDetails = jwtUserDetailsService.loadUserByUsername(username)
|
|
|
|
+ if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
|
|
|
|
+ val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(
|
|
|
|
+ userDetails, null, userDetails.authorities)
|
|
|
|
+ usernamePasswordAuthenticationToken.details = WebAuthenticationDetailsSource().buildDetails(request)
|
|
|
|
+ SecurityContextHolder.getContext().authentication = usernamePasswordAuthenticationToken
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ next.doFilter(request, response)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+abstract class JwtAuthenticationController {
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ private lateinit var authenticationManager: AuthenticationManager
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ private lateinit var jwtTokenUtil: JwtTokenUtil
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ private lateinit var userDetailsService: JwtUserDetailsService
|
|
|
|
+
|
|
|
|
+ fun createAuthenticationToken(username: String): String {
|
|
|
|
+ authenticate(username)
|
|
|
|
+ val userDetails: UserDetails = userDetailsService.loadUserByUsername(username)
|
|
|
|
+ return jwtTokenUtil.generateToken(userDetails)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Throws(Exception::class)
|
|
|
|
+ private fun authenticate(username: String) {
|
|
|
|
+ try {
|
|
|
|
+ authenticationManager.authenticate(UsernamePasswordAuthenticationToken(username, ""))
|
|
|
|
+ } catch (e: DisabledException) {
|
|
|
|
+ throw Exception("USER_DISABLED", e)
|
|
|
|
+ } catch (e: BadCredentialsException) {
|
|
|
|
+ throw Exception("INVALID_CREDENTIALS", e)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@Component
|
|
|
|
+class JwtUserDetailsService : UserDetailsService {
|
|
|
|
+
|
|
|
|
+ override fun loadUserByUsername(username: String): UserDetails {
|
|
|
|
+ return JwtUser(username, "", mutableListOf(SimpleGrantedAuthority("PERSON")))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+class JwtUser(username: String, password: String, authorities: MutableCollection<out GrantedAuthority>) : User(username, password, authorities)
|