Add login api sample for oauth resource server
This commit is contained in:
parent
2a83907be1
commit
a1ace59338
@ -1,11 +1,5 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
//buildscript {
|
||||
// repositories {
|
||||
// mavenCentral()
|
||||
// }
|
||||
//}
|
||||
|
||||
buildscript {
|
||||
val springBootVersion = "2.4.2"
|
||||
|
||||
|
@ -8,6 +8,11 @@ plugins {
|
||||
dependencies {
|
||||
api(project(":lib"))
|
||||
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("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
|
@ -6,8 +6,13 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.CommandLineRunner
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
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(
|
||||
//customerRepository: CustomerRepository,
|
||||
) : CommandLineRunner {
|
||||
@ -23,3 +28,14 @@ class DemoApplication @Autowired constructor(
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<DemoApplication>(*args)
|
||||
}
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/oauth")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
class OAuthController {
|
||||
@GetMapping
|
||||
fun getMe(authentication: Authentication) : Any? {
|
||||
return authentication
|
||||
}
|
||||
}
|
27
demo/src/main/kotlin/com/example/demo/SecurityConfig.kt
Normal file
27
demo/src/main/kotlin/com/example/demo/SecurityConfig.kt
Normal 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")
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
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
37
login-api/.gitignore
vendored
Normal 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/
|
35
login-api/build.gradle.kts
Normal file
35
login-api/build.gradle.kts
Normal 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
|
||||
}
|
BIN
login-api/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
login-api/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
login-api/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
login-api/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
19
login-api/src/main/resources/application.yml
Normal file
19
login-api/src/main/resources/application.yml
Normal 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-----
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
rootProject.name = "sample-modules"
|
||||
|
||||
include("demo", "lib", "customer-api")
|
||||
include("demo", "lib", "customer-api", "login-api")
|
Loading…
Reference in New Issue
Block a user