728x90
Spring Boot with OAuth2 - 1. OAuth2 Server 에서 구현한 코드를 확장하는 방법에 대해서 설명합니다.
데이터베이스 연결(MariaDB + Hibernate)
Hibernate를 통하여 MariaDB에 접속하는 예제를 구현합니다. application.properties에 데이터베이스 연결 정보만 추가하고 일부 몇몇 코드만 쿠가하면 자동으로 데이터베이스에 연결하여 OAuth2 서비스가 가능합니다.
관련 라이브러리 설정
pom.xml에 관련 라이브러리를 추가합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
application.properties에 데이터베이스 연결정보를 설정합니다.
spring.jpa.generate-ddl= false
spring.jpa.hibernate.ddl-auto= none
#spring.jpa.show-sql: true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://host:port/database_name?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=oauth2
spring.datasource.password=password
아래 SQL문을 실행하여 데이터베이스 테이블을 생성합니다.
CREATE TABLE `oauth_access_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` mediumblob DEFAULT NULL,
`authentication_id` varchar(256) NOT NULL,
`user_name` varchar(256) DEFAULT NULL,
`client_id` varchar(256) DEFAULT NULL,
`authentication` mediumblob DEFAULT NULL,
`refresh_token` varchar(256) DEFAULT NULL,
PRIMARY KEY (`authentication_id`)
);
CREATE TABLE `oauth_approvals` (
`userId` varchar(256) DEFAULT NULL,
`clientId` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`status` varchar(10) DEFAULT NULL,
`expiresAt` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`lastModifiedAt` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
);
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(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
);
CREATE TABLE `oauth_code` (
`code` varchar(256) DEFAULT NULL,
`authentication` mediumblob DEFAULT NULL
);
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` mediumblob DEFAULT NULL,
`authentication` mediumblob DEFAULT NULL
);
CREATE TABLE `oauth_user_authorities` (
`username` varchar(256) NOT NULL,
`authority` varchar(256) NOT NULL,
PRIMARY KEY (`username`,`authority`)
);
CREATE TABLE `oauth_user_details` (
`username` varchar(256) NOT NULL,
`password` varchar(256) NOT NULL,
`enabled` tinyint(1) DEFAULT NULL,
`account_non_expired` tinyint(1) DEFAULT NULL,
`account_non_locked` tinyint(1) DEFAULT NULL,
`credentials_non_expired` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`username`)
);
기본 데이터를 아래와 같이 입력합니다.
insert into `oauth_client_details`(`client_id`,`resource_ids`,`client_secret`,`scope`,`authorized_grant_types`,`web_server_redirect_uri`,`authorities`,`access_token_validity`,`refresh_token_validity`,`additional_information`,`autoapprove`)
values ('client',NULL,'{noop}secret','read_profile','authorization_code,password,client_credentials,implicit,refresh_token','http://localhost:9000/callback',NULL,36000,2592000,NULL,'true');
insert into `oauth_user_details`(`username`,`password`,`enabled`,`account_non_expired`,`account_non_locked`,`credentials_non_expired`) values ('admin','{noop}pass',1,1,1,1);
insert into `oauth_user_details`(`username`,`password`,`enabled`,`account_non_expired`,`account_non_locked`,`credentials_non_expired`) values ('user','{noop}pass',1,1,1,1);
insert into `oauth_user_authorities`(`username`,`authority`) values ('admin','ADMIN');
insert into `oauth_user_authorities`(`username`,`authority`) values ('admin','USER');
insert into `oauth_user_authorities`(`username`,`authority`) values ('user','USER');
AuthorizationServerConfig 수정
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private ApprovalStore approvalStore;
@Autowired
private TokenStore tokenStore;
@Bean
public AuthorizationCodeServices jdbcAuthorizationCodeServices(DataSource dataSource) {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Bean
public ApprovalStore jdbcApprovalStore(DataSource dataSource) {
return new JdbcApprovalStore(dataSource);
}
@Bean
public TokenStore jdbcTokenStore(DataSource dataSource) {
return new CustomJdbcTokenStore(dataSource);
}
@Bean
@Primary
public ClientDetailsService jdbcClientDetailsService(DataSource dataSource) {
return new JdbcClientDetailsService(dataSource);
}
/*
데이터베이스를 사용하여 사용자를 관리하므로 이 코드는 삭제처리합니다.
@Autowired
@Qualifier("userDetailsService")
private UserDetailsService userDetailsService;
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices)
.tokenStore(tokenStore)
.approvalStore(approvalStore)
// .userDetailsService(userDetailsService)
;
}
/*
데이터베이스를 사용하므로 이 코드는 삭제처리합니다.
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("client")
// .secret("{bcrypt}$2a$10$goA9F/Q./Ml8lYvuO1tj6OKA5K6VVM/jmUcdIp1AMzqtXHsuo68/W") // secret
.secret("{noop}secret") // secret
.redirectUris("http://localhost:9000/callback")
.authorizedGrantTypes("authorization_code", "implicit", "password", "client_credentials", "refresh_token")
.accessTokenValiditySeconds(120)
.refreshTokenValiditySeconds(240)
.scopes("read_profile");
}
*/
}
/*
CustomJdbcTokenStore를 재구현하지 않을 경우
Failed to find access token for token null 오류가 출력됩니다.
*/
class CustomJdbcTokenStore extends JdbcTokenStore {
private static final Logger log = LoggerFactory.getLogger(CustomJdbcTokenStore.class);
public CustomJdbcTokenStore(DataSource dataSource) {
super(dataSource);
}
@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
OAuth2AccessToken accessToken = null;
try {
accessToken = new DefaultOAuth2AccessToken(tokenValue);
} catch (EmptyResultDataAccessException e) {
if (log.isInfoEnabled()) {
log.info("Failed to find access token for token " + tokenValue);
}
} catch (IllegalArgumentException e) {
log.warn("Failed to deserialize access token for " + tokenValue, e);
removeAccessToken(tokenValue);
}
return accessToken;
}
}
WebSecurityConfig 수정
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.Base64.Encoder;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import lombok.AllArgsConstructor;
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//
private static final Logger log = LoggerFactory.getLogger(WebSecurityConfig.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
makeAuthorizationRequestHeader();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
/*
데이터베이스를 사용하므로 이 코드는 삭제처리합니다.
@Bean
public UserDetailsService userDetailsService() {
PasswordEncoder encoder = passwordEncoder();
String password = encoder.encode("pass");
log.debug("PasswordEncoder password : [{}] ", password); // {bcrypt}$2a$10$q6JJMlG7Q7Gt4n/76ydvp.Vk9pWVcTfCQ4NtWyBzNtWOmefYNw/wO
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password(password).roles("USER").build());
manager.createUser(User.withUsername("admin").password("{noop}pass").roles("USER", "ADMIN").build());
return manager;
}
*/
@Autowired
DataSource dataSource;
private JdbcUserDetailsManager userDetailsManager;
// Enable jdbc authentication
@Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
this.userDetailsManager = auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from oauth_user_details where username = ?")
.authoritiesByUsernameQuery("select username, authority from oauth_user_authorities where username = ?")
// .rolePrefix("ROLE_")
.getUserDetailsService();
// 필요할 경우 아래의 코드들을 주석해제합니다.
// .userExistsSql("select username from oauth_user_details where username = ?")
// .createUserSql("insert into oauth_user_details (username, password, enabled) values (?,?,?)")
// .createAuthoritySql("insert into oauth_user_authorities (username, authority) values (?,?)")
// .updateUserSql("update oauth_user_details set password = ?, enabled = ? where username = ?")
// .deleteUserSql("delete from oauth_user_details where username = ?")
// .deleteUserAuthoritiesSql("delete from oauth_user_authorities where username = ?");
}
@Bean
public JdbcUserDetailsManager jdbcUserDetailsManager() throws Exception {
// JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager();
// jdbcUserDetailsManager.setDataSource(dataSource);
//
// this.userDetailsManager.setUsersByUsernameQuery("select username, password, enabled from oauth_user_details where username = ?");
this.userDetailsManager.setUserExistsSql("select username from oauth_user_details where username = ?");
this.userDetailsManager.setCreateUserSql("insert into oauth_user_details (username, password, enabled) values (?,?,?)");
this.userDetailsManager.setCreateAuthoritySql("insert into oauth_user_authorities (username, authority) values (?,?)");
this.userDetailsManager.setUpdateUserSql("update oauth_user_details set password = ?, enabled = ? where username = ?");
this.userDetailsManager.setDeleteUserSql("delete from oauth_user_details where username = ?");
this.userDetailsManager.setDeleteUserAuthoritiesSql("delete from oauth_user_authorities where username = ?");
//
// return jdbcUserDetailsManager;
return this.userDetailsManager;
}
private static void makeAuthorizationRequestHeader() {
String oauthClientId = "client";
String oauthClientSecret = "secret";
Encoder encoder = Base64.getEncoder();
try {
String toEncodeString = String.format("%s:%s", oauthClientId, oauthClientSecret);
String authorizationRequestHeader = "Basic " + encoder.encodeToString(toEncodeString.getBytes("UTF-8"));
log.debug("AuthorizationRequestHeader : [{}] ", authorizationRequestHeader); // Y2xpZW50OnNlY3JldA==
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
}
}
/**
* Need to configure this support password mode support password grant type
*
* @return
* @throws Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
참고 사이트
소스코드
소스코드는 여기에서 다운로드 가능합니다.
728x90
'Development > Spring Framework, Security, OAuth2' 카테고리의 다른 글
Spring Boot with OAuth2 - 6. OAuth2 Server 접근/권한 제어 (0) | 2019.11.27 |
---|---|
Spring Boot with OAuth2 - 5. OAuth2 Resource Server 분리 (0) | 2019.11.27 |
Spring Boot with OAuth2 - 4. OAuth2 Server 확장 3 - JwtTokenStore (0) | 2019.11.26 |
Spring Boot with OAuth2 - 3. OAuth2 Server 확장 2 - MyBatis + MariaDB 연결 (0) | 2019.11.21 |
Spring Boot with OAuth2 - 1. OAuth2 Server 구현 (0) | 2019.11.18 |