From 5dd491a87047dea944f95e93aea53cdeb1cbc8ad Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 16 May 2019 18:28:10 +0900 Subject: [PATCH] authorization server setting --- ...Config.java => OAuth2SsoServerConfig.java} | 21 +++-- .../config/WebSecurityConfig.java | 17 +++- .../controller/sso/SsoController.java | 71 ++++++++++++++ .../domain/UserResponseWrapper.java | 13 +++ .../domain/client/Client.java | 4 - .../domain/oauth/accesstoken/AccessToken.java | 31 +++++++ .../accesstoken/AccessTokenRepository.java | 15 +++ .../domain/oauth/client/Client.java | 27 ++++++ .../domain/oauth/client/ClientRepository.java | 7 ++ .../domain/user/UserRepository.java | 1 + .../service/sso/SsoService.java | 12 +++ .../service/sso/SsoServiceImpl.java | 92 +++++++++++++++++++ .../src/main/resources/application.properties | 17 +++- .../src/main/resources/data.sql | 43 +++++++++ .../src/main/resources/schema.sql | 70 ++++++++++++++ 15 files changed, 426 insertions(+), 15 deletions(-) rename authorization-server/src/main/java/io/bluemoon/authorizationserver/config/{AuthServerConfig.java => OAuth2SsoServerConfig.java} (88%) create mode 100644 authorization-server/src/main/java/io/bluemoon/authorizationserver/controller/sso/SsoController.java create mode 100644 authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/UserResponseWrapper.java delete mode 100644 authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/client/Client.java create mode 100644 authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/accesstoken/AccessToken.java create mode 100644 authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/accesstoken/AccessTokenRepository.java create mode 100644 authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/client/Client.java create mode 100644 authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/client/ClientRepository.java create mode 100644 authorization-server/src/main/java/io/bluemoon/authorizationserver/service/sso/SsoService.java create mode 100644 authorization-server/src/main/java/io/bluemoon/authorizationserver/service/sso/SsoServiceImpl.java create mode 100644 authorization-server/src/main/resources/data.sql create mode 100644 authorization-server/src/main/resources/schema.sql diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/config/AuthServerConfig.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/config/OAuth2SsoServerConfig.java similarity index 88% rename from authorization-server/src/main/java/io/bluemoon/authorizationserver/config/AuthServerConfig.java rename to authorization-server/src/main/java/io/bluemoon/authorizationserver/config/OAuth2SsoServerConfig.java index 57bc680..c8982a3 100644 --- a/authorization-server/src/main/java/io/bluemoon/authorizationserver/config/AuthServerConfig.java +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/config/OAuth2SsoServerConfig.java @@ -1,6 +1,7 @@ package io.bluemoon.authorizationserver.config; import io.bluemoon.authorizationserver.service.user.CustomUserDetailsServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -23,29 +24,31 @@ import javax.sql.DataSource; @Configuration @EnableAuthorizationServer -public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { +public class OAuth2SsoServerConfig extends AuthorizationServerConfigurerAdapter { + @Autowired private AuthorizationCodeServices authorizationCodeServices; + @Autowired private ApprovalStore approvalStore; private ClientDetailsService clientDetailsService; private AuthenticationManager authenticationManager; private DataSource dataSource; private CustomUserDetailsServiceImpl customUserDetailsService; - public AuthServerConfig( + public OAuth2SsoServerConfig( +// AuthorizationCodeServices authorizationCodeServices, +// ApprovalStore approvalStore, ClientDetailsService clientDetailsService, AuthenticationManager authenticationManager, DataSource dataSource, - CustomUserDetailsServiceImpl customUserDetailsService, - AuthorizationCodeServices authorizationCodeServices, - ApprovalStore approvalStore + CustomUserDetailsServiceImpl customUserDetailsService ) { +// this.authorizationCodeServices = authorizationCodeServices; +// this.approvalStore = approvalStore; this.clientDetailsService = clientDetailsService; this.authenticationManager = authenticationManager; this.dataSource = dataSource; this.customUserDetailsService = customUserDetailsService; - this.authorizationCodeServices = authorizationCodeServices; - this.approvalStore = approvalStore; } @Override @@ -67,9 +70,9 @@ public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { // refresh token .userDetailsService(customUserDetailsService) // approval store - .approvalStore(jdbcApprovalStore(dataSource)) + .approvalStore(approvalStore) // code service - .authorizationCodeServices(jdbcAuthorizationCodeServices(dataSource)); + .authorizationCodeServices(authorizationCodeServices); } @Override diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/config/WebSecurityConfig.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/config/WebSecurityConfig.java index 111ffab..3f614d6 100644 --- a/authorization-server/src/main/java/io/bluemoon/authorizationserver/config/WebSecurityConfig.java +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/config/WebSecurityConfig.java @@ -1,6 +1,7 @@ package io.bluemoon.authorizationserver.config; import io.bluemoon.authorizationserver.service.user.CustomUserDetailsServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,8 +12,10 @@ import org.springframework.security.config.annotation.authentication.builders.Au import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; @Configuration +@EnableResourceServer @Order(SecurityProperties.BASIC_AUTH_ORDER - 6) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @@ -40,7 +43,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests().antMatchers("/oauth/**", "/test/**").permitAll(); + http.authorizeRequests().antMatchers("/**").permitAll() + .and() + .logout() + .permitAll(); + + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // + auth + .inMemoryAuthentication() + .withUser("user1").password("1234").roles("USER"); } /** diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/controller/sso/SsoController.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/controller/sso/SsoController.java new file mode 100644 index 0000000..ba8a855 --- /dev/null +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/controller/sso/SsoController.java @@ -0,0 +1,71 @@ +package io.bluemoon.authorizationserver.controller.sso; + +import io.bluemoon.authorizationserver.domain.UserResponseWrapper; +import io.bluemoon.authorizationserver.domain.oauth.accesstoken.AccessToken; +import io.bluemoon.authorizationserver.service.sso.SsoService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; + +@Controller +public class SsoController { + + private SsoService ssoService; + + public SsoController( + SsoService ssoService + ) { + this.ssoService = ssoService; + } + + @RequestMapping(value = "/userInfo", method = RequestMethod.POST) + @ResponseBody + public UserResponseWrapper userResponse( + @RequestParam(name = "token") String token, + @RequestParam(name = "clientId") String clientId + ) { + AccessToken accessToken = ssoService.getAccessToken(token, clientId); + UserResponseWrapper userResponseWrapper = new UserResponseWrapper(); + + if (accessToken == null) { + userResponseWrapper.setResult(false); + userResponseWrapper.setMessage("사용자 정보를 조회할 수 없습니다."); + } else { + userResponseWrapper.setMessage(accessToken.getUserName()); + } + return userResponseWrapper; + } + + @RequestMapping(value = "/userLogout", method = RequestMethod.GET) + public String userLogout( + @RequestParam(name = "clientId") String clientId, + HttpServletRequest request + ) { + String userName = request.getRemoteUser(); + String baseUri = ssoService.logoutAllClient(clientId, userName); + + request.getSession().invalidate(); + + return "redirect:"+baseUri; + } + + @RequestMapping(value = "/oauthCallback", method = RequestMethod.GET) + public String oauthCallback( + @RequestParam(name = "code") String code, + @RequestParam(name = "state") String state, + HttpServletRequest request, ModelMap map + ) { + System.out.println(code); + System.out.println(state); + System.out.println(request); + System.out.println(map.toString()); + + return "aa"; + } + +} diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/UserResponseWrapper.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/UserResponseWrapper.java new file mode 100644 index 0000000..1be4549 --- /dev/null +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/UserResponseWrapper.java @@ -0,0 +1,13 @@ +package io.bluemoon.authorizationserver.domain; + +import lombok.Data; + +@Data +public class UserResponseWrapper { + + private boolean result = true; + + private String message; + + private String userName; +} diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/client/Client.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/client/Client.java deleted file mode 100644 index 8a5748e..0000000 --- a/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/client/Client.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.bluemoon.authorizationserver.domain.client; - -public class Client { -} diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/accesstoken/AccessToken.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/accesstoken/AccessToken.java new file mode 100644 index 0000000..8a5bc56 --- /dev/null +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/accesstoken/AccessToken.java @@ -0,0 +1,31 @@ +package io.bluemoon.authorizationserver.domain.oauth.accesstoken; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Data +@Table(name = "oauth_access_token") +public class AccessToken { + + @Id + @Column(name = "token_id") + private String tokenId; + + private String token; + + @Column(name = "user_name") + private String userName; + + @Column(name = "authentication_id") + private String authenticationId; + + @Column(name = "client_id") + private String clientId; + + private String authentication; +} diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/accesstoken/AccessTokenRepository.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/accesstoken/AccessTokenRepository.java new file mode 100644 index 0000000..c176402 --- /dev/null +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/accesstoken/AccessTokenRepository.java @@ -0,0 +1,15 @@ +package io.bluemoon.authorizationserver.domain.oauth.accesstoken; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +public interface AccessTokenRepository extends JpaRepository { + + AccessToken findByTokenIdAndClientId(String tokenId, String clientId); + + int deleteByUserName(String userName); + + List findByUserName(String username); +} diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/client/Client.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/client/Client.java new file mode 100644 index 0000000..4867b78 --- /dev/null +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/client/Client.java @@ -0,0 +1,27 @@ +package io.bluemoon.authorizationserver.domain.oauth.client; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Data +@Table(name = "oauth_client_details") +public class Client { + + @Id + @Column(name = "client_id") + private String clientId; + + @Column(name = "web_server_redirect_uri") + private String redirectUri; + + @Column(name = "logout_uri") + private String logoutUri; + + @Column(name = "base_uri") + private String baseUri; +} diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/client/ClientRepository.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/client/ClientRepository.java new file mode 100644 index 0000000..196eef3 --- /dev/null +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/oauth/client/ClientRepository.java @@ -0,0 +1,7 @@ +package io.bluemoon.authorizationserver.domain.oauth.client; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ClientRepository extends JpaRepository { + +} diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/user/UserRepository.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/user/UserRepository.java index dc27504..25b5c5e 100644 --- a/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/user/UserRepository.java +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/domain/user/UserRepository.java @@ -1,6 +1,7 @@ package io.bluemoon.authorizationserver.domain.user; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; public interface UserRepository extends JpaRepository { User findByUserName(String userName); diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/service/sso/SsoService.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/service/sso/SsoService.java new file mode 100644 index 0000000..bb3d4a8 --- /dev/null +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/service/sso/SsoService.java @@ -0,0 +1,12 @@ +package io.bluemoon.authorizationserver.service.sso; + +import io.bluemoon.authorizationserver.domain.oauth.accesstoken.AccessToken; + + +public interface SsoService { + + AccessToken getAccessToken(String token, String clientId); + + String logoutAllClient(String clientId, String userName); + +} diff --git a/authorization-server/src/main/java/io/bluemoon/authorizationserver/service/sso/SsoServiceImpl.java b/authorization-server/src/main/java/io/bluemoon/authorizationserver/service/sso/SsoServiceImpl.java new file mode 100644 index 0000000..139d0b9 --- /dev/null +++ b/authorization-server/src/main/java/io/bluemoon/authorizationserver/service/sso/SsoServiceImpl.java @@ -0,0 +1,92 @@ +package io.bluemoon.authorizationserver.service.sso; + +import io.bluemoon.authorizationserver.domain.oauth.accesstoken.AccessToken; +import io.bluemoon.authorizationserver.domain.oauth.accesstoken.AccessTokenRepository; +import io.bluemoon.authorizationserver.domain.oauth.client.Client; +import io.bluemoon.authorizationserver.domain.oauth.client.ClientRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service +public class SsoServiceImpl implements SsoService{ + private AccessTokenRepository accessTokenRepository; + private ClientRepository clientRepository; + + public SsoServiceImpl( + AccessTokenRepository accessTokenRepository, + ClientRepository clientRepository + ) { + this.accessTokenRepository = accessTokenRepository; + this.clientRepository = clientRepository; + } + + @Override + public AccessToken getAccessToken(String token, String clientId) { + String tokenId = extractTokenId(token); + + return accessTokenRepository.findByTokenIdAndClientId(tokenId, clientId); + } + + private String extractTokenId(String value) { + if (value == null) { + return null; + } + + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + + byte[] bytes = digest.digest(value.getBytes("UTF-8")); + return String.format("%032x", new BigInteger(1, bytes)); + + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("MD5 algorithm not avilable. Fatal (should be in the JDK)."); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK)."); + } + } + + @Override + @Transactional + public String logoutAllClient(String clientId, String userName) { + requestLogoutToAllClients(userName); + removeAccessToken(userName); + + Optional client = clientRepository.findById(clientId); + return client.get().getBaseUri(); + } + + private void requestLogoutToAllClients(String userName) { + List tokenList = accessTokenRepository.findByUserName(userName); + + for (AccessToken token : tokenList) { + requestLogoutToClient(token); + } + + } + + private void requestLogoutToClient(AccessToken token) { + Optional client = clientRepository.findById(token.getClientId()); + + String logoutUri = client.get().getLogoutUri(); + String authorizationHeader = null; + + Map paramMap = new HashMap<>(); + paramMap.put("tokenId", token.getTokenId()); + paramMap.put("userName", token.getUserName()); + + // logout 요청 + } + + private int removeAccessToken(String userName) { + return accessTokenRepository.deleteByUserName(userName); + } +} diff --git a/authorization-server/src/main/resources/application.properties b/authorization-server/src/main/resources/application.properties index 7972f08..9784617 100644 --- a/authorization-server/src/main/resources/application.properties +++ b/authorization-server/src/main/resources/application.properties @@ -1,2 +1,17 @@ +server.port=8081 -security.oauth2.authorization.check-token-access=isAuthenticated() \ No newline at end of file +security.oauth2.authorization.check-token-access=isAuthenticated() + +spring.main.allow-bean-definition-overriding=true +spring.datasource.url=jdbc:mysql://127.0.0.1/oauth2?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=uneed3515 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.platform=schema + +spring.jpa.database = MYSQL +#spring.jpa.hibernate.ddl-auto=update +#spring.jpa.generate-ddl=true +spring.jpa.show-sql=true +#spring.jpa.generate-ddl=false +#spring.jpa.hibernate.ddl-auto=none \ No newline at end of file diff --git a/authorization-server/src/main/resources/data.sql b/authorization-server/src/main/resources/data.sql new file mode 100644 index 0000000..256a595 --- /dev/null +++ b/authorization-server/src/main/resources/data.sql @@ -0,0 +1,43 @@ +insert into oauth_client_details (client_id, client_secret, + resource_ids, scope, authorized_grant_types, + web_server_redirect_uri, authorities, access_token_validity, + refresh_token_validity, additional_information, + autoapprove, logout_uri, base_uri) + values ('System1_id', 'System1_secret', + null, 'read', 'authorization_code', + 'http://localhost:18010/oauthCallback', 'ROLE_YOUR_CLIENT', 36000, + 2592000, null, + 'true', 'http://localhost:8081/logout', 'http://localhost:8081/me'); + +insert into oauth_client_details (client_id, client_secret, + resource_ids, scope, authorized_grant_types, + web_server_redirect_uri, authorities, access_token_validity, + refresh_token_validity, additional_information, + autoapprove, logout_uri, base_uri) + values ('System2_id', 'System2_secret', + null, 'read', 'authorization_code', + 'http://localhost:18020/oauthCallback', 'ROLE_YOUR_CLIENT', 36000, + 2592000, null, + 'true', 'http://localhost:18020/logout', 'http://localhost:18020/me'); + +insert into oauth_client_details (client_id, client_secret, + resource_ids, scope, authorized_grant_types, + web_server_redirect_uri, authorities, access_token_validity, + refresh_token_validity, additional_information, + autoapprove, logout_uri, base_uri) + values ('System3_id', 'System3_secret', + null, 'read', 'authorization_code', + 'http://localhost:18030/oauthCallback', 'ROLE_YOUR_CLIENT', 36000, + 2592000, null, + 'true', 'http://localhost:18030/logout', 'http://localhost:18030/me'); + +insert into oauth_client_details (client_id, client_secret, + resource_ids, scope, authorized_grant_types, + web_server_redirect_uri, authorities, access_token_validity, + refresh_token_validity, additional_information, + autoapprove, logout_uri, base_uri) + values ('System4_id', 'System4_secret', + null, 'read', 'authorization_code', + 'http://localhost:18040/oauthCallback', 'ROLE_YOUR_CLIENT', 36000, + 2592000, null, + 'true', 'http://localhost:18040/logout', 'http://localhost:18040/me'); \ No newline at end of file diff --git a/authorization-server/src/main/resources/schema.sql b/authorization-server/src/main/resources/schema.sql new file mode 100644 index 0000000..b486d6d --- /dev/null +++ b/authorization-server/src/main/resources/schema.sql @@ -0,0 +1,70 @@ + +-- used in tests that use Mysql Local +create table oauth_client_details ( + client_id VARCHAR(255) PRIMARY KEY, + resource_ids VARCHAR(255), + client_secret VARCHAR(255), + scope VARCHAR(255), + authorized_grant_types VARCHAR(255), + web_server_redirect_uri VARCHAR(255), + logout_uri VARCHAR(255), + base_uri VARCHAR(255), + authorities VARCHAR(255), + access_token_validity INTEGER, + refresh_token_validity INTEGER, + additional_information VARCHAR(4096), + autoapprove VARCHAR(255) +); + +create table oauth_client_token ( + token_id VARCHAR(255), + token BLOB, + authentication_id VARCHAR(255) PRIMARY KEY, + user_name VARCHAR(255), + client_id VARCHAR(255) +); + +create table oauth_access_token ( + token_id VARCHAR(255), + token BLOB, + authentication_id VARCHAR(255) PRIMARY KEY, + user_name VARCHAR(255), + client_id VARCHAR(255), + authentication BLOB, + refresh_token VARCHAR(255) +); + +create table oauth_refresh_token ( + token_id VARCHAR(255), + token BLOB, + authentication BLOB +); + +create table oauth_code ( + code VARCHAR(255), authentication BLOB +); + +create table oauth_approvals ( + userId VARCHAR(255), + clientId VARCHAR(255), + scope VARCHAR(255), + status VARCHAR(10), + expiresAt TIMESTAMP, + lastModifiedAt TIMESTAMP +); + + +-- -- customized oauth_client_details table +-- create table ClientDetails ( +-- appId VARCHAR(255) PRIMARY KEY, +-- resourceIds VARCHAR(255), +-- appSecret VARCHAR(255), +-- scope VARCHAR(255), +-- grantTypes VARCHAR(255), +-- redirectUrl VARCHAR(255), +-- authorities VARCHAR(255), +-- access_token_validity INTEGER, +-- refresh_token_validity INTEGER, +-- additionalInformation VARCHAR(4096), +-- autoApproveScopes VARCHAR(255) +-- ); \ No newline at end of file