Add login api sample for oauth resource server

This commit is contained in:
Sambo Chea 2021-01-28 15:49:20 +07:00
parent 2a83907be1
commit a1ace59338
19 changed files with 446 additions and 9 deletions

View File

@ -1,11 +1,5 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
//buildscript {
// repositories {
// mavenCentral()
// }
//}
buildscript { buildscript {
val springBootVersion = "2.4.2" val springBootVersion = "2.4.2"

View File

@ -8,6 +8,11 @@ plugins {
dependencies { dependencies {
api(project(":lib")) api(project(":lib"))
api(project(":customer-api")) api(project(":customer-api"))
api(project(":login-api"))
implementation("org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.3.4.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("com.fasterxml.jackson.module:jackson-module-kotlin")

View File

@ -6,8 +6,13 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@SpringBootApplication (scanBasePackages = ["com.example.customerapi"]) @SpringBootApplication (scanBasePackages = ["com.example.demo", "com.example.loginapi","com.example.customerapi"])
class DemoApplication @Autowired constructor( class DemoApplication @Autowired constructor(
//customerRepository: CustomerRepository, //customerRepository: CustomerRepository,
) : CommandLineRunner { ) : CommandLineRunner {
@ -23,3 +28,14 @@ class DemoApplication @Autowired constructor(
fun main(args: Array<String>) { fun main(args: Array<String>) {
runApplication<DemoApplication>(*args) runApplication<DemoApplication>(*args)
} }
@RestController
@RequestMapping("/oauth")
@PreAuthorize("isAuthenticated()")
class OAuthController {
@GetMapping
fun getMe(authentication: Authentication) : Any? {
return authentication
}
}

View File

@ -0,0 +1,27 @@
package com.example.demo
import com.example.loginapi.OauthResourceServerSecurity
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer
/**
* @author sombochea <Sambo Chea>
* @email sombochea@cubetiqs.com
* @date 15/10/19
* @since 1.0
*/
@Configuration
@EnableResourceServer
class SecurityConfig : OauthResourceServerSecurity() {
@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.exceptionHandling()
.and()
.authorizeRequests()
.antMatchers("/api/**", "/oauth", "/customers")
.access("#oauth2.hasAnyScope('read','write')")
.antMatchers("/actuator/**")
.hasAnyRole("SUPER_ADMIN", "SYS_ADMIN","ACTUATOR")
}
}

View File

@ -1 +1,2 @@
spring.data.mongodb.uri=mongodb://192.168.0.202:27017/db-customer-api spring.data.mongodb.uri=mongodb://192.168.0.202:27017/db-customer-api
spring.main.allow-bean-definition-overriding=true

37
login-api/.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

View File

@ -0,0 +1,35 @@
plugins {
id("org.springframework.boot")
id("io.spring.dependency-management")
kotlin("jvm")
kotlin("plugin.spring")
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.3.4.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<Jar> {
enabled = true
}
tasks.withType<org.springframework.boot.gradle.tasks.bundling.BootJar> {
enabled = false
}

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -0,0 +1,20 @@
package com.example.loginapi
import org.springframework.security.oauth2.provider.OAuth2Authentication
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter
import org.springframework.stereotype.Component
/**
* @author sombochea <Sambo Chea>
* @email sombochea@cubetiqs.com
* @date 16/10/19
* @since 1.0
*/
@Component
class CubeJwtAccessTokenConverter : DefaultAccessTokenConverter() {
override fun extractAuthentication(map: Map<String?, *>?): OAuth2Authentication {
val authentication = super.extractAuthentication(map)
authentication.details = map
return authentication
}
}

View File

@ -0,0 +1,11 @@
package com.example.loginapi
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class LoginApiApplication
fun main(args: Array<String>) {
runApplication<LoginApiApplication>(*args)
}

View File

@ -0,0 +1,20 @@
package com.example.loginapi
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/login")
class LoginController {
@GetMapping
fun get(): Any? {
return RestClientUtils.getRestTemplate().getForObject("https://api-clinic.cubetiqs.com/info", Any::class.java)
}
@PostMapping
fun login(
@RequestParam(value = "username") username: String,
@RequestParam(value = "password") password: String,
): Any? {
return RestClientUtils.login(username, password)
}
}

View File

@ -0,0 +1,76 @@
package com.example.loginapi
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer
import org.springframework.security.oauth2.provider.token.DefaultTokenServices
import org.springframework.security.oauth2.provider.token.TokenStore
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore
/**
* @author sombochea
* @since 1.0
*/
@Configuration
@EnableResourceServer
open class OauthResourceServerSecurity :
ResourceServerConfigurerAdapter() {
private val jwtAccessTokenConverter: CubeJwtAccessTokenConverter = CubeJwtAccessTokenConverter()
@Value("\${spring.security.oauth2.resourceserver.jwt.public-key}")
var publicKey: String? = null
@Value("\${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
var jwkSetUri: String? = null
private var tokenStore: TokenStore? = null
override fun configure(resources: ResourceServerSecurityConfigurer) {
val resourceId = "cubetiq-clinic-dev"
println("Loaded system with resource id: $resourceId")
resources
.tokenStore(tokenStore())
.resourceId(resourceId)
.stateless(false)
}
@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.exceptionHandling()
.and()
.authorizeRequests()
.antMatchers("/api/**")
.access("#oauth2.hasAnyScope('read','write')")
.antMatchers("/actuator/**")
.hasAnyRole("SUPER_ADMIN", "SYS_ADMIN","ACTUATOR")
}
@Bean
fun tokenServices(tokenStore: TokenStore?): DefaultTokenServices {
val tokenServices = DefaultTokenServices()
tokenServices.setTokenStore(tokenStore)
return tokenServices
}
@Bean
fun tokenStore(): TokenStore? {
if (tokenStore == null) {
tokenStore = JwkTokenStore(jwkSetUri, jwtAccessTokenConverter)
}
return tokenStore
}
@Bean
fun jwtAccessTokenConverter(): JwtAccessTokenConverter {
val converter = JwtAccessTokenConverter()
converter.accessTokenConverter = jwtAccessTokenConverter
converter.setVerifierKey(publicKey)
return converter
}
}

View File

@ -0,0 +1,115 @@
@file:Suppress("unused", "unused")
package com.example.loginapi
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import org.springframework.http.*
import org.springframework.util.LinkedMultiValueMap
import org.springframework.util.MultiValueMap
import org.springframework.web.client.RestTemplate
import java.io.Serializable
import java.nio.charset.StandardCharsets
import java.util.*
/**
* @author sombochea
* @since 1.0
*/
object RestClientUtils {
private const val BEAN_NAME = "restTemplate"
private var restTemplate: RestTemplate? = null
@JvmStatic
fun setRestTemplate(restTemplate: RestTemplate?) {
RestClientUtils.restTemplate = restTemplate
}
@JvmStatic
fun getRestTemplate(): RestTemplate {
if (restTemplate == null) {
restTemplate = RestTemplate()
}
return restTemplate ?: throw Exception("rest client service load failed")
}
fun login(username: String, password: String): OAuthToken? {
val authEndpoint = "https://preprod-api-auth.staging.cubetiqs.com/api/oauth/token"
val httpHeaders = getHttpHeadersConfig()
val body: MultiValueMap<String, String> = LinkedMultiValueMap()
body.add("grant_type", "password")
body.add("username", username)
body.add("password", password)
val httpEntity = HttpEntity(body, httpHeaders)
println(httpEntity)
return getRestTemplate().postForEntity(authEndpoint, httpEntity, OAuthToken::class.java).body
}
private fun getHttpHeadersConfig(): HttpHeaders {
val httpHeaders = HttpHeaders()
val client = "cubetiq-clinic-dev"
val secret = "123456"
httpHeaders.contentType = MediaType.APPLICATION_FORM_URLENCODED
val clientDetail = "$client:$secret"
val oauthCodes = Base64.getEncoder().encode(clientDetail.toByteArray(StandardCharsets.US_ASCII))
httpHeaders["Authorization"] = "Basic " + String(oauthCodes)
httpHeaders["Tenant-ID"] = "TNA-00013067"
httpHeaders["User-Type"] = "INTERNAL"
return httpHeaders
}
@JsonIgnoreProperties(ignoreUnknown = true)
data class OAuthToken(
@JsonProperty(value = "access_token")
var accessToken: String? = null,
@JsonProperty(value = "token_type")
var tokenType: String? = null,
@JsonProperty(value = "refresh_token")
var refreshToken: String? = null,
@JsonProperty(value = "expires_in")
var expiresIn: Long? = null,
@JsonProperty(value = "scope")
var scope: String? = null,
@JsonProperty(value = "auditor")
var auditor: String? = null,
@JsonProperty(value = "tenant")
var tenant: String? = null,
@JsonProperty(value = "user_id")
var userId: String? = null,
@JsonProperty(value = "username")
var username: String? = null,
@JsonProperty(value = "jti")
var jti: String? = null,
var passcode: Boolean? = null,
var configs: Map<String, Any?>? = null,
@JsonProperty(value = "current_branch_id")
var currentBranchId: String? = null,
@JsonProperty(value = "current_branch")
var currentBranch: String? = null,
) : Serializable {
@JsonIgnore
fun addConfig(key: String, value: Any?) = apply {
if (this.configs == null) {
this.configs = mutableMapOf()
}
(this.configs as MutableMap)[key] = value
}
@JsonIgnore
fun addConfigs(configs: Map<String, Any?>?) = apply {
if (this.configs == null) {
this.configs = mutableMapOf()
}
if (configs != null) {
(this.configs as MutableMap).putAll(configs)
}
}
}
}

View File

@ -0,0 +1,17 @@
package com.example.loginapi
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api/users")
class UserController {
@GetMapping("/me")
fun getMe(
authentication: Authentication,
): Any {
return authentication
}
}

View File

@ -0,0 +1,26 @@
package com.example.loginapi
import org.springframework.context.annotation.Configuration
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.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.http.SessionCreationPolicy
/**
* @author sombochea <Sambo Chea>
* @email sombochea@cubetiqs.com
* @date 15/10/19
* @since 1.0
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfig : WebSecurityConfigurerAdapter() {
@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
}
}

View File

@ -0,0 +1,19 @@
server:
port: 8015
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: ${JWK_SET_URI:https://preprod-api-auth.staging.cubetiqs.com/.well-known/jwks.json}
public-key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhLjm/+1Maitij0pV4IVD
gpLZ7IAvlXxKyToTCRusFwsto3T5jZIr5pNFEPJN6XuO/2fHGlcIioRD6pC1xdHu
qoYwImNHjYrS2vRrVboBiMHgOqZ2/Qk2knyNC98vp6sBp8PDSAWSPkWgKPDR2RV0
sFoPVT+0TCtXPVrdOCPkHDvrg2M4H8NwRtec3bzv3KkIpf2TSuSSHwL9JENaXpJn
2POnZwjBADa2xIU4K3k9XdYrTDqqlnIfnj/irT8aUCQzyo5vfqy4n9eQjj/lSmhT
L76pnrIEvl0UjnfRfZ9prE6+bS2pF6d4cYXfATwC0lKkIgKjHPoyUnyleJ6qHDyN
CwIDAQAB
-----END PUBLIC KEY-----

View File

@ -0,0 +1,13 @@
package com.example.loginapi
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class LoginApiApplicationTests {
@Test
fun contextLoads() {
}
}

View File

@ -1,3 +1,3 @@
rootProject.name = "sample-modules" rootProject.name = "sample-modules"
include("demo", "lib", "customer-api") include("demo", "lib", "customer-api", "login-api")