mysql 샘플 테이블 + 쿼리 연습 문제

http://java-school.net/jdbc/SQL-SELECT-Statement CREATE TABLE DEPT (     DEPTNO DECIMAL ( 2 ),     DNAME VARCHAR ( 14 ),     LOC VARCHAR ( 13 ),     CONSTRAINT PK_DEPT PRIMARY KEY ( DEPTNO ) ); CREATE TABLE EMP (     EMPNO DECIMAL ( 4 ),     ENAME VARCHAR ( 10 ),     JOB VARCHAR ( 9 ),     MGR DECIMAL ( 4 ),     HIREDATE DATE ,     SAL DECIMAL ( 7 , 2 ),     COMM DECIMAL ( 7 , 2 ),     DEPTNO DECIMAL ( 2 ),     CONSTRAINT PK_EMP PRIMARY KEY ( EMPNO ),     CONSTRAINT FK_DEPTNO FOREIGN KEY ( DEPTNO ) REFERENCES DEPT ( DEPTNO ) ); CREATE TABLE SALGRADE (     GRADE TINYINT ,     LOSAL SMALLINT ,     HISAL SMALLINT ); INSE...

[Oauth2.0] 스프링부트 OAuth2 jwt kotlin (client credentials)

신입 3개월차에 oauth관련해서 서브로 깔짝 개발할 일이 있어서 그때 한 번 잠깐 공부를 했었는데 (결국 이해 못하고 넘어감)

그 때 OAuth! 어렵다! 모르겠다! 는 심리적 벽이 생겨버려서 이번에도 쉽지않았다 (문서가 안읽힘) 




타사와 openAPI 를 통해 서로 데이터를 주고받기로함

연동업체에서 인증관련 가이드가 내려옴

------------------------------------------------------

API 통신을 위한 인증의 경우 아래 당사 정보보안가이드를 준수해야하므로 ...


API 에 대한 인증 방식은 OneTime Token 을 사용하거나 혹은 그에 준하는 가변값으로 인증이 필요


- OneTime Token 방식인증 API 를 제공하여 1 회성 액세스 토큰 발행

- OneTime Token 에 준하는 가변값의 인증 방식 예제 (사전 협의 필요)

------------------------------------------------------




OAuth2 인증 방식 4가지

1) Authorization Code 

    - ex) 구글, 페이스북 로그인 

2) Implicit

3) Resource Oner Password

4) Client Credentials

    - 업체끼리 연동




개발환경

spring boot 2.5.6

java 11

kotlin

MySQL


인증 구현은 처음이라 일단 샘플을 만들어본다

accessToken 발급 해보기


build.gradle.kts

dependencies {
//implementation(project(":domain"))

implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.0.1.RELEASE")
implementation("org.springframework.boot:spring-boot-autoconfigure")
implementation("org.springframework.boot:spring-boot-starter-web")
...
외에도 뭔가 이것저것 덕지덕지 붙어있는데 생략 



@Configuration
class Oauth2Config {

/**
* Oauth2 서버설정
*/
@Configuration
@EnableAuthorizationServer
class OAuth2AuthorizationServerConfiguration : AuthorizationServerConfigurerAdapter() {

override fun configure(clients: ClientDetailsServiceConfigurer) {
/* db사용으로 변경? */
clients
.inMemory()
.withClient("client")
.secret("{noop}secret")
.scopes("default")
.authorizedGrantTypes("client_credentials")
.accessTokenValiditySeconds(86400) // 24 hours
}

override fun configure(endpoints: AuthorizationServerEndpointsConfigurer) {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
}

override fun configure(oauthServer: AuthorizationServerSecurityConfigurer) {
oauthServer.allowFormAuthenticationForClients()
}

@Bean
fun accessTokenConverter(): JwtAccessTokenConverter = JwtAccessTokenConverter().apply {
setSigningKey("t0P$3cret")
}

@Bean
fun tokenStore(): TokenStore = JwtTokenStore(accessTokenConverter())

@Bean
@Primary
fun tokenServices(): DefaultTokenServices = DefaultTokenServices().apply {
setTokenStore(tokenStore())
}
}


/**
* 리소스 서버설정
*/
@Configuration
@EnableResourceServer
class OAuth2ResourceServerConfiguration(
private val tokenServices: DefaultTokenServices //default store 랑 그냥 token store 랑 뭐가 다름?
) : ResourceServerConfigurerAdapter() {

override fun configure(resources: ResourceServerSecurityConfigurer) {
resources.tokenServices(tokenServices)
}

// oauth 토큰에 의해 보호되고 있는 리소스 서버에 대한 설정
        override fun configure(http: HttpSecurity) {
        http.cors().disable()
        .csrf().disable() // csrf 보안 토큰 disable
        .httpBasic().disable() // rest api만 고려, 기본 설정 해제
        .authorizeRequests() // 요청에 대한 사용 권한 체크
        .antMatchers("/**").authenticated()
        .and()
         .exceptionHandling()
         .authenticationEntryPoint { request: HttpServletRequest?, response: HttpServletResponse, authException: AuthenticationException? -> response.sendError(
         HttpServletResponse.SC_UNAUTHORIZED) }
         .accessDeniedHandler { request: HttpServletRequest?, response: HttpServletResponse, authException: AccessDeniedException? -> response.sendError(
         HttpServletResponse.SC_UNAUTHORIZED) }

}
}
}



여기까지하고

포스트맨으로 호출 -> 토큰을 발급받아본다


request

url : .../oauth/token

method : POST

params : client_id, client_secret, grant_type


client_id, client_secret = 소스에 기술되어있음

grant_type = client_credentials 고정값



response

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJkZWZhdWx0Il0sImV4cCI6MTY1MzY0NTMzNCwianRpIjoiODg4ZjRkZGUtOGZmZC00MjBmLTg4ZDQtNTZiNjg2ZDE0Y2RlIiwiY2xpZW50X2lkIjoiY2xpZW50In0.JN76_U3X3jdLYJhl489K-xdG-MKUfM9BXKiyBenBDEE",
"token_type": "bearer",
"expires_in": 86399,
"scope": "default",
"jti": "888f4dde-8ffd-420f-88d4-56b686d14cde"
}



토큰이 나왔다




AuthorizationServer에서 클라이언트 설정부분 inmemory 방식으로 되어있던걸 DB방식으로 변경

/**
* Oauth2 서버설정
*/
@Configuration
@EnableAuthorizationServer
class OAuth2AuthorizationServerConfiguration(
private val dataSource: DataSource,
private val clientDetailsService: ClientDetailsService
) : AuthorizationServerConfigurerAdapter() {

// 클라이언트에 대한 설정
override fun configure(clients: ClientDetailsServiceConfigurer) {
/* db사용으로 변경? */
/*clients
.inMemory()
.withClient("client")
.secret("{noop}secret")
.scopes("default")
.authorizedGrantTypes("client_credentials")
.accessTokenValiditySeconds(86400) // 24 hours*/

clients.withClientDetails(clientDetailsService)

}

// 인증, 토큰 엔드포인트, 토큰 서비스 정의
override fun configure(endpoints: AuthorizationServerEndpointsConfigurer) {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
}

// 토큰 엔드포인트 (/auth/token)에 대한 보안관련 설정
override fun configure(security: AuthorizationServerSecurityConfigurer) {
security.tokenKeyAccess("permitAll()").allowFormAuthenticationForClients()
}

@Bean
fun accessTokenConverter(): JwtAccessTokenConverter = JwtAccessTokenConverter().apply {
setSigningKey("t0P$3cret")
}

@Bean
fun tokenStore(): TokenStore = JwtTokenStore(accessTokenConverter())

@Bean
@Primary
fun jdbcClientDetailsService(dataSource: DataSource?): JdbcClientDetailsService? {
return JdbcClientDetailsService(dataSource)
}


@Bean
@Primary
fun tokenServices(): DefaultTokenServices = DefaultTokenServices().apply {
setTokenStore(tokenStore())
}

@Bean
fun passwordEncoder(): BCryptPasswordEncoder? {
return BCryptPasswordEncoder()
}
}



클라이언트 테이블 생성

CREATE TABLE `oauth_client_details` (

  `client_id` varchar(256) NOT NULL,

  `resource_ids` varchar(256) DEFAULT NULL,

  `client_secret` varchar(256) DEFAULT NULL,

  `scope` varchar(256) DEFAULT NULL,

  `authorized_grant_types` varchar(256) DEFAULT NULL,

  `web_server_redirect_uri` varchar(256) DEFAULT NULL,

  `authorities` varchar(256) DEFAULT NULL,

  `access_token_validity` int DEFAULT NULL,

  `refresh_token_validity` int DEFAULT NULL,

  `additional_information` varchar(4096) DEFAULT NULL,

  `autoapprove` varchar(256) DEFAULT NULL,

  PRIMARY KEY (`client_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;






샘플 컨트롤러 만들어서 발급받은 토큰으로 포스트맨에서 호출해보기


컨트롤러

@PostMapping("/test")
fun test(@RequestBody test: ForTest): ResponseEntity<ForTest> {

println("testController@@@")

return ResponseEntity.ok(test)
}


Headers
Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cC....(/oauth/token 에서 발급받은토큰)

RequestBody
{
"id":3334,
"name":"secretddd"
}


Error Response : 토큰 안맞을경우
{
"error": "invalid_token",
"error_description": "Cannot convert access token to JSON"
}

Error Response : Authorization 안보낼경우
{
"timestamp": "2022-05-31T07:41:26.867+00:00",
"status": 401,
"error": "Unauthorized",
"message": "No message available",
"path": "/test"
}




이제 액세스토큰을 발행해서 해당토큰으로 리소스 서버 api를 호출하는 것까지는 되었다


더 할일

1. jwt 사인키 방식 변경

2. oneTime AccessToken 으로 변경 (api 호출할때마다 새 토큰 발급받아야 접근가능하도록)

3. 인증서버, 리소스서버 나누기

4. 그 외 더 필요한 인증 및 보안관련 사항있나 확인하기





자바쟁이였는데 갑자기 코틀린으로 하려니까 재봉틀쓰다가 바느질하는기분




작성중...








참고 사이트

https://co-de.tistory.com/36?category=884527

https://brunch.co.kr/@sbcoba/15

https://github.com/AliakseiYaustratsyeu/spring-boot-oauth-client-credentials

https://docs.spring.io/spring-security/reference/5.6.0-RC1/servlet/oauth2/oauth2-client.html

https://velog.io/@skysoo/%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A1%9C%EB%93%9C%EB%A7%B5-%EB%94%B0%EB%9D%BC%EA%B0%80%EA%B8%B0-10.-OAuth2.0

https://autumnly.tistory.com/65?category=813931

https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow

https://github.com/nononsensecode/client-credential-flow-with-signed-jwt

https://blog.naver.com/mds_datasecurity/222182943542

스택오버플로우