authenticaction, registration, login
This commit is contained in:
parent
dba2228939
commit
c685dfb71c
@ -4,9 +4,10 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
plugins {
|
||||
id("org.springframework.boot") version "2.3.0.RELEASE"
|
||||
id("io.spring.dependency-management") version "1.0.9.RELEASE"
|
||||
kotlin("jvm") version "1.3.71"
|
||||
kotlin("jvm") version "1.3.72"
|
||||
kotlin("plugin.spring") version "1.3.72"
|
||||
kotlin("plugin.jpa") version "1.3.72"
|
||||
kotlin("plugin.serialization") version "1.3.70"
|
||||
}
|
||||
|
||||
group = "com.aitrainer"
|
||||
@ -22,12 +23,18 @@ dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-aop")
|
||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
implementation("org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.3.0.RELEASE")
|
||||
implementation("org.springframework.security.oauth:spring-security-oauth2:2.5.0.RELEASE")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation("org.apache.logging.log4j:log4j-core:2.13.3")
|
||||
implementation("org.apache.logging.log4j:log4j-api:2.13.3")
|
||||
implementation("org.slf4j:slf4j-api:1.7.30")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0") // JVM dependency
|
||||
implementation("io.jsonwebtoken:jjwt:0.9.1")
|
||||
|
||||
|
||||
|
||||
runtimeOnly("mysql:mysql-connector-java")
|
||||
|
1
data/db/update_0_0_3.sql
Normal file
1
data/db/update_0_0_3.sql
Normal file
@ -0,0 +1 @@
|
||||
INSERT INTO `customer` (`name`, `firstname`, `email`, `password`, `sex`, `age`, `active`, `date_add`, `date_change`, `data_policy_allowed`, `admin`) VALUES ('Dummy User', NULL, 'bosi', '$2a$10$thOc8jS750c7xe9U9Qq3GuSPs/H0Pt2Ads05yzUlyzQBIj.Rk9QCy', 'm', 40, 'N', NULL, NULL, 1, 1);
|
@ -4,11 +4,8 @@ import org.slf4j.LoggerFactory
|
||||
import org.springframework.boot.SpringApplication
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
|
||||
@SpringBootApplication
|
||||
@RestController
|
||||
class ApiApplication
|
||||
private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping()
|
||||
@RequestMapping
|
||||
class ApplicationProperties {
|
||||
|
||||
@Value("\${application.version}")
|
||||
|
@ -1,39 +1,53 @@
|
||||
package com.aitrainer.api.controller
|
||||
|
||||
import com.aitrainer.api.model.Customer
|
||||
import com.aitrainer.api.model.User
|
||||
import com.aitrainer.api.service.ServiceBeans
|
||||
import com.aitrainer.api.repository.CustomerRepository
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.annotation.Secured
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import javax.validation.Valid
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
class CustomerController ( private val customerRepository: CustomerRepository ) {
|
||||
|
||||
@Autowired
|
||||
var serviceBeans: ServiceBeans? = null
|
||||
|
||||
@Secured
|
||||
@GetMapping("/customers")
|
||||
fun getAllCustomers(): List<Customer> =
|
||||
fun getAllCustomers(@RequestHeader headers: HttpHeaders): List<Customer> =
|
||||
customerRepository.findAll()
|
||||
|
||||
@Secured
|
||||
@PostMapping("/customers")
|
||||
fun createNewCustomer(@Valid @RequestBody customer: Customer): Customer =
|
||||
fun createNewCustomer(@Valid @RequestBody customer: Customer, @RequestHeader headers: HttpHeaders): Customer =
|
||||
customerRepository.save(customer)
|
||||
|
||||
@Secured
|
||||
@GetMapping("/customers/{id}")
|
||||
fun getCustomerById(@PathVariable(value = "id") customerId: Long): ResponseEntity<Customer> {
|
||||
fun getCustomerById(@PathVariable(value = "id") customerId: Long, @RequestHeader headers: HttpHeaders): ResponseEntity<Customer> {
|
||||
return customerRepository.findById(customerId).map { customer ->
|
||||
ResponseEntity.ok(customer)
|
||||
}.orElse(ResponseEntity.notFound().build())
|
||||
}
|
||||
|
||||
|
||||
@Secured
|
||||
@GetMapping("/customers/real")
|
||||
fun getRealCustomers(active: String): List<Customer> =
|
||||
fun getRealCustomers(active: String, @RequestHeader headers: HttpHeaders): List<Customer> =
|
||||
customerRepository.findByActive(active)
|
||||
|
||||
|
||||
@Secured
|
||||
@PutMapping("/customers/{id}")
|
||||
fun updateCustomerById(@PathVariable(value = "id") customerId: Long,
|
||||
@Valid @RequestBody newCustomer: Customer): ResponseEntity<Customer> {
|
||||
@Valid @RequestBody newCustomer: Customer,
|
||||
@RequestHeader headers: HttpHeaders): ResponseEntity<Customer> {
|
||||
|
||||
return customerRepository.findById(customerId).map { existingCustomer ->
|
||||
val updatedCustomer: Customer = existingCustomer
|
||||
@ -43,6 +57,60 @@ class CustomerController ( private val customerRepository: CustomerRepository )
|
||||
age = newCustomer.age)
|
||||
ResponseEntity.ok().body(customerRepository.save(updatedCustomer))
|
||||
}.orElse(ResponseEntity.notFound().build())
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/registration")
|
||||
fun registration(@Valid @RequestBody json: String): ResponseEntity<*> {
|
||||
val customer = Customer()
|
||||
|
||||
val newUser: User = User().fromJson(json)
|
||||
with (customer) {
|
||||
email = newUser.username
|
||||
password = serviceBeans!!.passwordEncoder().encode(newUser.password)
|
||||
}
|
||||
|
||||
val returnCustomer: Customer? = customerRepository.findByEmail(newUser.username).let {
|
||||
if ( it == null) {
|
||||
customerRepository.save(customer)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return if ( returnCustomer != null ) {
|
||||
ResponseEntity.ok().body(returnCustomer)
|
||||
} else {
|
||||
ResponseEntity.badRequest().body("Customer exists")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@GetMapping("/login")
|
||||
fun login(@Valid @RequestBody json: String): ResponseEntity<*> {
|
||||
val customer = Customer()
|
||||
|
||||
val newUser: User = User().fromJson(json)
|
||||
with (customer) {
|
||||
email = newUser.username
|
||||
password = newUser.password
|
||||
}
|
||||
val returnCustomer: Customer? = customerRepository.findByEmail(newUser.username).let {
|
||||
if ( it == null) {
|
||||
null
|
||||
} else {
|
||||
if (serviceBeans!!.passwordEncoder().matches(newUser.password, it.password)) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return if ( returnCustomer != null ) {
|
||||
ResponseEntity.ok().body(returnCustomer)
|
||||
} else {
|
||||
ResponseEntity.badRequest().body("Customer does not exist or the password is wrong")
|
||||
}
|
||||
}
|
||||
}
|
@ -2,15 +2,13 @@ package com.aitrainer.api.controller
|
||||
|
||||
import com.aitrainer.api.ApiApplication
|
||||
import com.aitrainer.api.repository.ConfigurationRepository
|
||||
import org.aspectj.lang.annotation.Around
|
||||
import org.aspectj.lang.JoinPoint
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
import org.aspectj.lang.annotation.Pointcut
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
@Aspect
|
||||
@Component
|
||||
@ -22,13 +20,11 @@ class CustomerControllerAspect {
|
||||
@Autowired
|
||||
private lateinit var properties: ApplicationProperties
|
||||
|
||||
@Suppress("unused")
|
||||
@Pointcut("execution(* com.aitrainer.api.controller.CustomerController.*())")
|
||||
fun customerControllerAspect() {
|
||||
}
|
||||
|
||||
@Before("customerControllerAspect()")
|
||||
fun loggingAop() {
|
||||
@Before("execution(* com.aitrainer.api.controller.CustomerController.*(..))")
|
||||
fun customerControllerAspect(joinPoint: JoinPoint) {
|
||||
println("customer controller")
|
||||
Singleton.checkDBUpdate(configurationRepository, properties)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,7 @@ package com.aitrainer.api.controller
|
||||
import com.aitrainer.api.ApiApplication
|
||||
import com.aitrainer.api.model.Configuration
|
||||
import com.aitrainer.api.repository.ConfigurationRepository
|
||||
import org.hibernate.Hibernate.isInitialized
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.io.File
|
||||
import java.sql.Connection
|
||||
@ -73,8 +71,7 @@ object Singleton {
|
||||
|
||||
}
|
||||
|
||||
fun execSQL( sql: String ) {
|
||||
|
||||
private fun execSQL( sql: String ) {
|
||||
if (! this.initialized ) {
|
||||
this.getConnection()
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package com.aitrainer.api.controller
|
||||
|
||||
import com.aitrainer.api.ApiApplication
|
||||
import com.aitrainer.api.repository.ConfigurationRepository
|
||||
import org.aspectj.lang.annotation.Around
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
import org.aspectj.lang.annotation.Pointcut
|
||||
|
@ -5,7 +5,6 @@ import javax.persistence.GeneratedValue
|
||||
import javax.persistence.GenerationType
|
||||
import javax.persistence.Id
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.Null
|
||||
|
||||
@Entity
|
||||
data class Configuration (
|
||||
|
@ -4,12 +4,9 @@ import javax.persistence.Entity
|
||||
import javax.persistence.GeneratedValue
|
||||
import javax.persistence.GenerationType
|
||||
import javax.persistence.Id
|
||||
import javax.validation.constraints.NotBlank
|
||||
|
||||
@Entity
|
||||
data class Customer (
|
||||
@get: NotBlank
|
||||
|
||||
var name: String = "",
|
||||
var firstname: String = "",
|
||||
var email: String = "",
|
||||
@ -20,6 +17,7 @@ data class Customer (
|
||||
var dateChange: String? = null,
|
||||
var dataPolicyAllowed: Int = 0,
|
||||
var admin: Int = 0,
|
||||
var password: String = "",
|
||||
|
||||
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val customer_id: Long = 0
|
||||
|
15
src/main/kotlin/com/aitrainer/api/model/User.kt
Normal file
15
src/main/kotlin/com/aitrainer/api/model/User.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package com.aitrainer.api.model
|
||||
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.json.*
|
||||
|
||||
@Serializable
|
||||
data class User (
|
||||
var username: String = "",
|
||||
var password: String = ""
|
||||
) {
|
||||
@OptIn(UnstableDefault::class)
|
||||
fun fromJson(json: String): User {
|
||||
return Json.parse(serializer(), json)
|
||||
}
|
||||
}
|
@ -7,4 +7,6 @@ import org.springframework.stereotype.Repository
|
||||
@Repository
|
||||
interface CustomerRepository : JpaRepository<Customer, Long> {
|
||||
fun findByActive( active: String? ): List<Customer>
|
||||
|
||||
fun findByEmail(email: String?): Customer?
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.aitrainer.api.security
|
||||
|
||||
import com.aitrainer.api.ApiApplication
|
||||
import com.aitrainer.api.controller.ApplicationProperties
|
||||
import com.aitrainer.api.controller.Singleton
|
||||
import com.aitrainer.api.repository.ConfigurationRepository
|
||||
import org.aspectj.lang.JoinPoint
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Suppress("unused")
|
||||
@Aspect
|
||||
@Component
|
||||
class AuthenticationControllerAspect {
|
||||
private val logger = LoggerFactory.getLogger(ApiApplication::class.simpleName)
|
||||
|
||||
@Autowired
|
||||
private lateinit var configurationRepository: ConfigurationRepository
|
||||
@Autowired
|
||||
private lateinit var properties: ApplicationProperties
|
||||
|
||||
@Before("execution(* com.aitrainer.api.security.JwtAuthenticationController.*(..))")
|
||||
fun customerControllerAspect(joinPoint: JoinPoint) {
|
||||
println("auth controller join")
|
||||
Singleton.checkDBUpdate(configurationRepository, properties)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.aitrainer.api.security
|
||||
|
||||
import com.aitrainer.api.service.UserDetailsServiceImpl
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.ResponseEntity
|
||||
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.web.bind.annotation.*
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
|
||||
|
||||
@Component
|
||||
@RestController
|
||||
//@CrossOrigin
|
||||
@RequestMapping("/api")
|
||||
class JwtAuthenticationController {
|
||||
@Autowired
|
||||
private val authenticationManager: AuthenticationManager? = null
|
||||
|
||||
@Autowired
|
||||
private val jwtTokenUtil: JwtTokenUtil? = null
|
||||
|
||||
@Autowired
|
||||
private val jwtUserDetailsService: UserDetailsServiceImpl? = null
|
||||
|
||||
@PostMapping("/authenticate")
|
||||
fun generateAuthenticationToken(@RequestBody authenticationRequest: JwtRequest): ResponseEntity<*> {
|
||||
|
||||
authenticate(authenticationRequest.username!!, authenticationRequest.password!!)
|
||||
|
||||
val userDetails = jwtUserDetailsService
|
||||
?.loadUserByUsername(authenticationRequest.username)
|
||||
val token: String = jwtTokenUtil!!.generateToken(userDetails!!)
|
||||
return ResponseEntity.ok<Any>(JwtResponse(token))
|
||||
}
|
||||
|
||||
private fun authenticate(username: String, password: String) {
|
||||
try {
|
||||
authenticationManager!!.authenticate(UsernamePasswordAuthenticationToken(username, password))
|
||||
} catch (e: DisabledException) {
|
||||
throw Exception("USER_DISABLED", e)
|
||||
} catch (e: BadCredentialsException) {
|
||||
throw Exception("INVALID_CREDENTIALS", e)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.aitrainer.api.security
|
||||
|
||||
import org.springframework.security.web.AuthenticationEntryPoint
|
||||
import org.springframework.stereotype.Component
|
||||
import java.io.IOException
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
import java.io.Serializable
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
|
||||
|
||||
@Component
|
||||
class JwtAuthenticationEntryPoint : AuthenticationEntryPoint, Serializable {
|
||||
@Throws(IOException::class)
|
||||
override fun commence(request: HttpServletRequest?, response: HttpServletResponse,
|
||||
authException: AuthenticationException?) {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = -7858869558953243875L
|
||||
}
|
||||
}
|
19
src/main/kotlin/com/aitrainer/api/security/JwtRequest.kt
Normal file
19
src/main/kotlin/com/aitrainer/api/security/JwtRequest.kt
Normal file
@ -0,0 +1,19 @@
|
||||
package com.aitrainer.api.security
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
|
||||
class JwtRequest : Serializable {
|
||||
var username: String? = null
|
||||
var password: String? = null
|
||||
|
||||
//default constructor for JSON Parsing
|
||||
constructor(username: String?, password: String?) {
|
||||
this.username = username
|
||||
this.password = password
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = 5926468583005150707L
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.aitrainer.api.security
|
||||
|
||||
import com.aitrainer.api.service.UserDetailsServiceImpl
|
||||
import io.jsonwebtoken.ExpiredJwtException
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
import java.io.IOException
|
||||
import javax.servlet.FilterChain
|
||||
import javax.servlet.ServletException
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
|
||||
@Component
|
||||
class JwtRequestFilter : OncePerRequestFilter() {
|
||||
@Autowired
|
||||
private val jwtUserDetailsService: UserDetailsServiceImpl? = null
|
||||
|
||||
@Autowired
|
||||
private val jwtTokenUtil: JwtTokenUtil? = null
|
||||
|
||||
//@Autowired
|
||||
//private lateinit var authenticationController: JwtAuthenticationController
|
||||
|
||||
@Throws(ServletException::class, IOException::class)
|
||||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
|
||||
val requestTokenHeader = request.getHeader("Authorization")
|
||||
var username: String? = null
|
||||
var jwtToken: String? = null
|
||||
// JWT Token is in the form "Bearer token". Remove Bearer word and get only the Token
|
||||
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer")) {
|
||||
jwtToken = requestTokenHeader.substring(7)
|
||||
try {
|
||||
username = jwtTokenUtil!!.getUsernameFromToken(jwtToken)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
println("Unable to get JWT Token")
|
||||
} catch (e: ExpiredJwtException) {
|
||||
println("JWT Token has expired")
|
||||
}
|
||||
} else if (requestTokenHeader != null && requestTokenHeader.equals("1") ) {
|
||||
logger.warn("Authenticate")
|
||||
//val credentials: User = ObjectMapper().readValue(request.inputStream, User::class.java)
|
||||
|
||||
} else {
|
||||
logger.warn("JWT Token does not begin with Bearer String")
|
||||
}
|
||||
|
||||
//Once we get the token validate it.
|
||||
if (username != null && SecurityContextHolder.getContext().authentication == null) {
|
||||
val userDetails: UserDetails = jwtUserDetailsService!!.loadUserByUsername(username)
|
||||
|
||||
// if token is valid configure Spring Security to manually set authentication
|
||||
if (jwtTokenUtil!!.validateToken(jwtToken!!, userDetails)) {
|
||||
val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(
|
||||
userDetails, null, userDetails.authorities)
|
||||
usernamePasswordAuthenticationToken.details = WebAuthenticationDetailsSource().buildDetails(request)
|
||||
// After setting the Authentication in the context, we specify
|
||||
// that the current user is authenticated. So it passes the Spring Security Configurations successfully.
|
||||
SecurityContextHolder.getContext().authentication = usernamePasswordAuthenticationToken
|
||||
}
|
||||
}
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
|
||||
/*private fun readUserCredentials(request: HttpServletRequest): UserCredentials? {
|
||||
return try {
|
||||
ObjectMapper().readValue(request.inputStream, UserCredentials::class.java)
|
||||
} catch (ioe: IOException) {
|
||||
throw BadCredentialsException("Invalid request", ioe)
|
||||
}
|
||||
}*/
|
||||
}
|
12
src/main/kotlin/com/aitrainer/api/security/JwtResponse.kt
Normal file
12
src/main/kotlin/com/aitrainer/api/security/JwtResponse.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package com.aitrainer.api.security
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
|
||||
class JwtResponse(val token: String) : Serializable {
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = -8091879091924046844L
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.aitrainer.api.security
|
||||
|
||||
import com.aitrainer.api.service.ServiceBeans
|
||||
import com.aitrainer.api.service.UserDetailsServiceImpl
|
||||
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.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||
import org.springframework.security.config.http.SessionCreationPolicy
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class JwtSecurityConfig : WebSecurityConfigurerAdapter() {
|
||||
@Autowired
|
||||
private val jwtAuthenticationEntryPoint: JwtAuthenticationEntryPoint? = null
|
||||
|
||||
@Autowired
|
||||
private val jwtUserDetailsService: UserDetailsServiceImpl? = null
|
||||
|
||||
@Autowired
|
||||
private val jwtRequestFilter: JwtRequestFilter? = null
|
||||
|
||||
@Autowired
|
||||
private val serviceBeans: ServiceBeans? = null
|
||||
|
||||
override fun configure(auth: AuthenticationManagerBuilder?) {
|
||||
auth!!.userDetailsService(jwtUserDetailsService).passwordEncoder(serviceBeans!!.passwordEncoder())
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Throws(Exception::class)
|
||||
override fun authenticationManagerBean(): AuthenticationManager {
|
||||
return super.authenticationManagerBean()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun configure(httpSecurity: HttpSecurity) {
|
||||
|
||||
// We don't need CSRF for this example
|
||||
httpSecurity.
|
||||
csrf().disable().
|
||||
// dont authenticate this particular request
|
||||
authorizeRequests().antMatchers("/api/authenticate").permitAll().
|
||||
// all other requests need to be authenticated
|
||||
anyRequest().authenticated().and().
|
||||
// make sure we use stateless session; session won't be used to
|
||||
// store user's state.
|
||||
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().
|
||||
// Add a filter to validate the tokens with every request
|
||||
//addFilterAt(JwtAuthenticationFilter(authenticationManagerBean()), UsernamePasswordAuthenticationFilter::class.java).
|
||||
addFilterAfter(jwtRequestFilter, UsernamePasswordAuthenticationFilter::class.java).
|
||||
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
67
src/main/kotlin/com/aitrainer/api/security/JwtTokenUtil.kt
Normal file
67
src/main/kotlin/com/aitrainer/api/security/JwtTokenUtil.kt
Normal file
@ -0,0 +1,67 @@
|
||||
package com.aitrainer.api.security
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
import org.springframework.stereotype.Component
|
||||
import java.util.*
|
||||
import java.io.Serializable
|
||||
|
||||
import io.jsonwebtoken.Claims
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
|
||||
|
||||
@Component
|
||||
class JwtTokenUtil : Serializable {
|
||||
@Value("\${jwt.secret}")
|
||||
private val secret: String? = null
|
||||
fun getUsernameFromToken(token: String?): String {
|
||||
return getClaimFromToken(token, Claims::getSubject)
|
||||
}
|
||||
|
||||
fun getIssuedAtDateFromToken(token: String?): Date {
|
||||
return getClaimFromToken<Date>(token, Claims::getIssuedAt)
|
||||
}
|
||||
|
||||
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 claims.claimsResolver()
|
||||
}
|
||||
|
||||
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() + JWT_TOKEN_VALIDITY * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact()
|
||||
}
|
||||
|
||||
fun canTokenBeRefreshed(token: String): Boolean {
|
||||
return !isTokenExpired(token)
|
||||
}
|
||||
|
||||
fun validateToken(token: String, userDetails: UserDetails): Boolean {
|
||||
val username = getUsernameFromToken(token)
|
||||
return username == userDetails.username && !isTokenExpired(token)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = -2550185165626007488L
|
||||
const val JWT_TOKEN_VALIDITY = 5 * 60 * 60.toLong()
|
||||
}
|
||||
}
|
19
src/main/kotlin/com/aitrainer/api/service/ServiceBeans.kt
Normal file
19
src/main/kotlin/com/aitrainer/api/service/ServiceBeans.kt
Normal file
@ -0,0 +1,19 @@
|
||||
package com.aitrainer.api.service
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
/*
|
||||
Commonly used Beans
|
||||
*/
|
||||
|
||||
@Component
|
||||
class ServiceBeans {
|
||||
|
||||
@Bean
|
||||
fun passwordEncoder(): PasswordEncoder {
|
||||
return BCryptPasswordEncoder()
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.aitrainer.api.service
|
||||
|
||||
import com.aitrainer.api.model.Customer
|
||||
import com.aitrainer.api.repository.CustomerRepository
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
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.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
@Service
|
||||
class UserDetailsServiceImpl: UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private lateinit var customerRepository: CustomerRepository
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
override fun loadUserByUsername(username: String?): UserDetails {
|
||||
val customer: Customer? = customerRepository.findByEmail(username)
|
||||
|
||||
val grantedAuthorities = HashSet<GrantedAuthority>()
|
||||
grantedAuthorities.add(SimpleGrantedAuthority("user"))
|
||||
|
||||
if (customer != null) {
|
||||
return User(customer.email, customer.password, grantedAuthorities)
|
||||
} else {throw Exception("User does not exist")}
|
||||
|
||||
}
|
||||
}
|
@ -16,4 +16,6 @@ logging.config=classpath:logback-spring.xml
|
||||
logging.file=logs
|
||||
|
||||
# if the database structure has been changed, increment this version number
|
||||
application.version=0.0.2
|
||||
application.version=0.0.3
|
||||
|
||||
jwt.secret=aitrainer
|
25
src/test/kotlin/com/aitrainer/api/test/AuthenticationTest.kt
Normal file
25
src/test/kotlin/com/aitrainer/api/test/AuthenticationTest.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package com.aitrainer.api.test
|
||||
|
||||
import com.aitrainer.api.security.JwtAuthenticationController
|
||||
import com.aitrainer.api.security.JwtRequest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@SpringBootTest
|
||||
class AuthenticationTest {
|
||||
|
||||
@Autowired
|
||||
private lateinit var authController: JwtAuthenticationController
|
||||
@Test
|
||||
fun testAuthentication() {
|
||||
val response: ResponseEntity<*>
|
||||
val jwtRequest = JwtRequest("bosi", "andio2009")
|
||||
response = authController.generateAuthenticationToken(jwtRequest)
|
||||
assertEquals(response.statusCode, HttpStatus.OK)
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +1,32 @@
|
||||
package com.aitrainer.api.test
|
||||
|
||||
import com.aitrainer.api.controller.CustomerController
|
||||
import com.aitrainer.api.model.Customer
|
||||
import com.aitrainer.api.model.User
|
||||
import com.aitrainer.api.repository.CustomerRepository
|
||||
import com.aitrainer.api.service.ServiceBeans
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@SpringBootTest
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class CustomerTests {
|
||||
|
||||
@Autowired
|
||||
private var serviceBean: ServiceBeans? = null
|
||||
|
||||
@BeforeAll
|
||||
fun init() {
|
||||
if ( serviceBean == null ) { serviceBean = ServiceBeans() }
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private lateinit var customerRepository: CustomerRepository
|
||||
private var insertedId: Long? = null
|
||||
@ -51,4 +67,56 @@ class CustomerTests {
|
||||
customerRepository.delete(updatedCustomer)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRegistration() {
|
||||
val json = "{\"username\":\"bosi@example.com\",\"password\":\"94385\"}"
|
||||
val user: User = User().fromJson(json)
|
||||
assertEquals(user.username, "bosi@example.com")
|
||||
val customer = Customer()
|
||||
with(customer) {
|
||||
email = user.username
|
||||
password = user.password
|
||||
}
|
||||
val customerController = CustomerController(customerRepository)
|
||||
customerController.serviceBeans = serviceBean
|
||||
var response: ResponseEntity<*> = customerController.registration(json)
|
||||
val newCustomer: Customer? = response.body as Customer
|
||||
assertEquals(response.statusCode, HttpStatus.OK)
|
||||
|
||||
val json2 = "{\"username\":\"bosi@example.com\",\"password\":\"934345\"}"
|
||||
response = customerController.registration(json2)
|
||||
assertEquals(response.statusCode, HttpStatus.BAD_REQUEST)
|
||||
|
||||
if ( newCustomer != null) {
|
||||
customerRepository.delete(newCustomer)
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun testLogin() {
|
||||
val json = "{\"username\":\"bosi2@example.com\",\"password\":\"94333385\"}"
|
||||
val user: User = User().fromJson(json)
|
||||
val customer = Customer()
|
||||
with(customer) {
|
||||
email = user.username
|
||||
password = user.password
|
||||
}
|
||||
val customerController = CustomerController(customerRepository)
|
||||
customerController.serviceBeans = serviceBean
|
||||
var response: ResponseEntity<*> = customerController.registration(json)
|
||||
val newCustomer: Customer? = response.body as Customer
|
||||
assertEquals(response.statusCode, HttpStatus.OK)
|
||||
|
||||
response = customerController.login(json)
|
||||
val loginedCustomer: Customer? = response.body as Customer
|
||||
assertEquals(response.statusCode, HttpStatus.OK)
|
||||
if ( loginedCustomer != null ) {
|
||||
assertEquals(loginedCustomer.email, ("bosi2@example.com") )
|
||||
} else {
|
||||
assert(true)
|
||||
}
|
||||
|
||||
if ( newCustomer != null) {
|
||||
customerRepository.delete(newCustomer)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user