자제 로그인처리가 아닌 외부연동을 통한 로그인 선처리가 필요한 경우가 있다.
spring security를 사용하여 인증 처리를 하던 프로젝트의 경우 어떤 부분을 건들여야 할지 애매모호한 부분이 있다.
해당 기능 처리를 위해서 본인은 filter를 이용하여 BasicAuthenticationFilter가 실행되기 전에 전처리로 해당 세션에 authentication 정보를 주입하는 방식으로 처리를 하였다.
선처리 filter 코드 일부
1.해당 context에 인증 정보가 있는지 체크 (현재 인증된 정보가 있으면 스킵)
2.인증 정보를 연동 및 습득
3.해당 정보 기반으로 현재 context 인증 정보를 입력(인증은 security의 Authentication interface 기반으로 필요한 구현체로 선택해서 결정)
if(!SecurityContextHolder.getContext().getAuthentication().isAuthenticated()){
Map<String,Object> loginInfo = (Map<String,Object>)request.getSession().getAttribute("loginInfo");
if(loginInfo !=null){
LoginOperator loginOperator = loginService.searchLoginOperator((String)loginInfo.get("oprtrId"));
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
Collection<? extends GrantedAuthority> authorities = getAuthorities();
if(loginOperator !=null){
Authentication authentication = new UsernamePasswordAuthenticationToken(new LoginUser(
String.valueOf(loginOperator.getLoginId()),
loginOperator.getLoginPassword(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(),
new ArrayList<String>(),
loginOperator.getOperatorNumber(),
loginOperator.getLoginId(),
loginOperator.getOperatorName()
), loginOperator.getLoginPassword(), authorities);
//
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
2017년 8월 2일 수요일
2017년 8월 1일 화요일
Consul에 대해서
Consul에 대해서 대략적으로 설명하고자 한다.
HashiCorp에서 만든 OpenSource로 기존의 Zookeeper의 역활을 하는 Application으로 보면 된다.
아래는 해당 consul 홈페이지에 기제된 내용으로 기본적으로 4가지으로 항목으로 구분하여 설명하고 있다.
Service Discovery/Failure Detection/Multi Datacenter/KV Storage
Service Discovery
DNS 또는 HTTP 인터페이스를 통해 서비스를 스스로 등록하고 다른 서비스를 발견 할 수 있도록합니다. SaaS(Software as a Service) 제공되는 외부 서비스에 또한 등록할 수 있다
Failure Detection
각 노드에 대해서 health check를 통하여 서비스 정상 여부를 체크하고 Service Discovery를 하거나 서비스가 가능한 노드로 routing을 제공한다.
Multi Datacenter
여러개의 Datacenter를 구축하고 Datacenter간 WAN GOSSIP 프로토콜을 이용하여 정보를 조회 할 수 있도록 기능 제공한다.
KV Storage
Key/Value를 저장할 수 있는 저장소를 제공하여 서비스 별 Config 정보나 특정 데이타를 저장하여 instance간 config 정보 수정이나 데이타 공유를 할 수 있도록 지원한다.
아래의 이미지는 Stackshare에서 consul vs Zookeeper vs Eureka를 비교한 화면이다.
선호도나 사용 횟수면에서 Consul이 우월 하다는 것이 보여집니다.
저도 stackshare의 데이타를 신용하여 해당 Consul을 이용하여 Monitoring 시스템을 구현하는 것으로 결정하였습니다.
공식 사이트 : https://www.consul.io
consul 공식 사이트에 등록된 Architecture 이미지이다.
Consul의 Check 기능을 이용하여 해당 서버의 Resource 현황과 health 체크를 할 수 있도록 Consul 서버 및 클라이언트를 설치하고 설정을 진행 하도록 하겠습니다.
Consul의 Check 기능을 이용하여 해당 서버의 Resource 현황과 health 체크를 할 수 있도록 Consul 서버 및 클라이언트를 설치하고 설정을 진행 하도록 하겠습니다.
Consul Agent 서버 노드 설치
- Master 서버에 consul과
web-ui를 같이 설치
압축 해제
/data/consul
/data/consul-ui (디렉토리 이름 주의)
두개의 디렉토리를 생성 후 web_ui관련
파일을 다운받아 압축을 푼다.
cd /data/consul-ui
unzip consul_0.8.5_web_ui.zip
consul keygen
해시값을 생성성
이 해시값으로 모든 서버의 환경설정(encrypt) 값을 설정해야하니 꼭 저장해둘것!
vim /etc/consul.d/server/config.json
config 파일 생성
config 파일 생성
config.json
내용은 아래와 같이 설정한다.
{
"bootstrap": true,
"server": true,
"datacenter": "test",
"ui_dir": "/data/consul-ui",
// <- 디렉토리 경로 확인
"data_dir": "/data/consul",
"encrypt": "위에서얻은해시값",
"addresses": {
"http":
"외부망IP"
},
"bind_addr": "내부망 IP",
"advertise_addr": "외부망 IP",
"bind_addr": "내부망 IP",
"advertise_addr": "외부망 IP",
}
consul 서버 시작
nohup consul agent --server -config-dir=/etc/consul.d/server -bind=서버IP &
config-dir 옵션으로 config 파일 위치 설정
- 위와같이 설정하고 위의 명령어로 실행한뒤에
localhost:8500으로 접속하면 404에러가 남.
이때 sudo
reboot를하면 위에서 만들어둔 /data/consul-ui 디렉토리가 삭제되어 있는데,
디렉토리를 다시 만들고
1번 명령어로 실행하면 정상 작동함
Consul Agent 설치 방법
압축 해제한 consul을 아래의 두 경로로 복사/이동 시킨다.
cp ./consul
/usr/local/bin
mv ./consul
/usr/bin
설치 확인을 위해
consul version 명령 실행
- 아래의 디렉토리를 만든다.
/data/consul
- 아래의 경로에 config.json 파일을 만든다.
vim
/etc/consul.d/agent/config.json
config.json 내용은 아래와 같이 설정한다.
{
"server": false,
"datacenter": "test",
"data_dir": "/data/consul",
"encrypt": "7mASsjRU1X0ORmQyymo9Hw==",
"start_join": ["192.168.1.155"]
// <- Array형식으로 입력해야됨
}
-
Agent 실행
(1) consul agent -config-dir=/etc/consul.d/agent
(2) nohup consul agent -config-dir=/etc/consul.d/agent -bind=AgentIP
&
정상적으로 실행되었다면 서버쪽 웹화면의 [node]탭에 Agent가 추가된다.
#
consul을 서버(개발) 모드로 실행하는 방법
consul agent -dev
# 터미널을 하나 떠 띄워서 아래와 같은 명령어를 치면 Cluster Members를 볼 수 있다.
consul members [-detailed]
#
consul에서 사용하는 포트 번호
SERVER RPC : 8300/tcp
Serf LAN : 8301/tcp&udp
Serf WAN : 8302/tcp&udp
CLI RPC : 8400/tcp
HTTP API : 8500/tcp (UI접속용)
DNS Interface : 8600/tcp&udp
ufw
allow 8300/tcp
ufw
allow 8301/tcp
ufw
allow 8500/tcp
ufw
allow 8600/tcp
# #
서비스 정의 (Defining a Service)
서비스는 json을
통해 정의하는 방법과 HTTP API를 이용해 정의하는 두가지 방법이 있다.
-
/etc/consul.d/server 폴더를 만든다.
- 다음의 명령으로 [80번 포트를 사용하고, rails라는 태그를 가진 "web"이라는 서비스]를 만든다.
echo '{"service": {"name": "web",
"tags": ["rails"], "port": 80}}' | sudo tee
/etc/consul.d/server/web.json
- 서비스를 등록하기 위해 다음의 명령어로
Consul을 재시작한다.
consul agent -dev -config-dir=/etc/consul.d/server
"Synced
service 'web'"라는 출력을 통해 웹
서비스를 "동기화"한 것을 알 수 있다.
이는 에이전트가 위에서 만든 json 파일에서 서비스 정의를 로드했으며,
서비스 카탈로그에 성공적으로 등록했음을 의미한다.
추가 서비스 역시 위와같은 방식으로 만들 수 있다.
-
agent가 시작되고 서비스가 동기화되면 DNS나 HTTP API를 사용해 서비스를 쿼리할 수 있다.
#
DNS API를 이용한 쿼리 방법
- dig
@127.0.0.1 -p 8600
web.service.consul
- dig
@127.0.0.1 -p 8600
web.service.consul SRV
- DNS
API를 이용해 태그로 필터링해서 서비스를 요청하는 방법
dig @127.0.0.1 -p 8600
rails.web.service.consul
#
HTTP API를 이용한 쿼리 방법
주어진 서비스를 호스팅하는 모든 노드를 출력한다.
[
{
"ID": "15e1a433-8f0a-2996-cfdd-ed788d9c6a83",
"Node": "ef06bc916151",
"Address": "127.0.0.1",
"Datacenter":
"dc1",
"TaggedAddresses":
{
"lan":
"127.0.0.1",
"wan":
"127.0.0.1"
},
"NodeMeta":
{},
"ServiceID":
"web",
"ServiceName":
"web",
"ServiceTags":
[
"rails"
],
"ServiceAddress":
"",
"ServicePort":
80,
"ServiceEnableTagOverride":
false,
"CreateIndex":
6,
"ModifyIndex":
6
}
]
|
노드 중에
healthy한것만 출력 (문제없이 작동하는것만 출력?)
----------------------------------------------------------------------------------
#
AWS에서 설치하는 방법
---------------------------------------------------------------------------------
### Health HTTP Request ===================================
#
List Checks for Node
node별로 모든
Check를 요청한다.
"Client" 노드의
모든 Check를 json 형식으로 출력한다.
#
List Checks for Service
Service별로 모든
Check를 요청한다.
#
List Nodes for Service
지정한 서비스에 대한 결과 출력
#
List Checks in State
모든 Server/Agent에 대해
상태(state)가 passing인것을 출력
### SERVICE ==========================================
서비스는 아래와 같은 json 형식으로 정의할 수 있다.
agent 실행시 -config-file의 옵션으로
지정하거나 -config-dir 명령줄 옵션으로
서비스 파일의 경로를설정해서 읽어들일
수 있다.
HTTP
API를 사용해서 동적으로 등록 가능하다.
# 개별 서비스 설정 예제
{
"service": {
"name": "redis",
"tags": ["primary"],
"address": "",
"port": 8000,
"enableTagOverride": false,
"checks": [
{
"script":
"/usr/local/bin/check_redis.py",
"interval": "10s"
}
]
}
}
... or
...
{
"service":{
"name":"web",
"tags":[ "rails" ],
"port":80
}
}
# 멀티 서비스 설정 예제
{
"services": [ //
services처럼 s가
붙고, Array형식([])이다.
{
"id": "red0",
"name": "redis",
"tags": [
"primary"
],
"address": "",
"port": 6000,
"checks": [
{
"script": "/bin/check_redis -p
6000",
"interval": "5s",
"ttl": "20s"
}
]
},
{
"id": "red1",
"name": "redis",
"tags": [
"delayed",
"secondary"
],
"address": "",
"port": 7000,
"checks": [
{
"script": "/bin/check_redis -p
7000",
"interval": "30s",
"ttl": "60s"
}
]
},
...
]
}
name은 필수고, 이외의 값은 선택적으로
사용 가능하다.
id는 지정하지 않을경우 name값으로
설정되지만, 노드마다 고유값을 가져야하기 때문에
가급적 고유한 이름으로 직접 지정해주는게
좋다.
tags는 노드나 서비스에서 필터링 조건으로 사용된다.
서비스마다 IP와 port번호를 지정할 수 있는데, address/port에서 지정해주면되지만,
기본적으로 agent의 ip와 port번호가
지정되기 때문에 별도로 지정할 필요는 없다.
check/checks에 가능한 속성은 script, HTTP,
TCP, TTL이고, script, http, tcp 속성이
사용될경우 꼭 interval 속성을 사용해 시간간격을 설정해줘야한다. (10s = 10초)
ttl은 ttl만 제공하면된다.
check/checks
이름은
service:<service-id> 형식으로 자동 생성된다.
enableTagoverride는 이 서비스의 anti-entropy기능을
비활성화 시키기 위해 선택적으로 사용한다.
2016년 12월 27일 화요일
redirect와 forward의 차이
redirect와 forward의 차이
* Forward : Web Container 차원에서 페이지 이동만 있다. 실제로 웹 브라우저는 다른 페이지로 이동했음을 알 수 없다.
브라우져상에서 URL은 기존의 요청된 URL과 동일한 정보를 노출한다.
동일한 웹 컨테이너에 있는 페이지로만 이동할 수 있다.
현재 실행중인 페이지와 forwad에 의해 호출될 페이지는 request와 response 객체를 공유한다.
* Redirect : Web Container는 Redirect 명령이 들어오면 웹 브라우저에게 다른 페이지로 이동하라고 명령을 내린다. 그러면 웹 브라우저는 URL을 지시된 주소로 바꾸고 그 주소로 이동한다. 다른 웹 컨테이너에있는 주소로 이동이 가능하다.
새로운 페이지에서는 request와 response객체가 새롭게 생성된다.
즉 새로운 페이지가 로딩된 것과 동일하다.
* Forward : Web Container 차원에서 페이지 이동만 있다. 실제로 웹 브라우저는 다른 페이지로 이동했음을 알 수 없다.
브라우져상에서 URL은 기존의 요청된 URL과 동일한 정보를 노출한다.
동일한 웹 컨테이너에 있는 페이지로만 이동할 수 있다.
현재 실행중인 페이지와 forwad에 의해 호출될 페이지는 request와 response 객체를 공유한다.
* Redirect : Web Container는 Redirect 명령이 들어오면 웹 브라우저에게 다른 페이지로 이동하라고 명령을 내린다. 그러면 웹 브라우저는 URL을 지시된 주소로 바꾸고 그 주소로 이동한다. 다른 웹 컨테이너에있는 주소로 이동이 가능하다.
새로운 페이지에서는 request와 response객체가 새롭게 생성된다.
즉 새로운 페이지가 로딩된 것과 동일하다.
2016년 12월 12일 월요일
spring security를 이용한 oauth 2.0 구현
spring security를 이용한 대부분의 예제들이 spring boot를 이용하여 구현이 되어 있다.
하지만 꼭 boot로 구동을 해야 되는 건 아니니 프로젝트별 구성에 따라 맞게 구현을 하는게 좋을 것이다.
본인의 경우 boot를 지향하지 않기 때문에 tomcat 기반으로 구성을 진행했다.
기존에 spring security를 이용하여 maven 프로젝트를 구성하고 있다면 아래의 dependency만 추가해 주면 된다.
사실 oauth를 구성하는데 org.springframework.security.oauth 해당 라이브러리만 있으면 되는 것이나 관련 dendency추가해준다.
commons-lang3는 scope나 grant-types에 대해서 ,를 구분자로 리스트를 입력되어 있는 경에 대한 StringUtil이나 token발급에 필요한 util등을 사용한다.
================POM.xml===========================
<!-- spring security-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${springframework.security-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${springframework.security-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${springframework.security-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.security-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!-- oauth -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${spring-security-oauth2-version}</version>
</dependency>
<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
===================================================
실제 Oauth의 구현은 간단하다.
org.springframework.security.oauth라이브러리 내에 기능 구현이 거의 대부분이 되어 있다고해도 과언이 아니다.
customizing을 진행하기 위해서는 기능에 대한 구현체에 대한 확인이 필요하다.
하지만 그 일이 만만치가 않다. interface기반으로 실제 구현체들이 주입이 되는 방식으로 코드들이 숨겨져 있어서 일일이 찾아서 확인해야 한다.
https://projects.spring.io/spring-security-oauth/docs/oauth2.html에 설명된 아래의 3개의 configurer를 중심을 trace를 해 간다면 좀 더 쉽게 이해가 되었다.
ClientDetailsServiceConfigurer: a configurer that defines the client details service. Client details can be initialized, or you can just refer to an existing store.AuthorizationServerSecurityConfigurer: defines the security constraints on the token endpoint.AuthorizationServerEndpointsConfigurer: defines the authorization and token endpoints and the token services.
ClientDetailsServiceConfigurer
- clientDetailsService에 대한 config를 설정
- clientDetails에 대한 clientId나 grantType,scopes,TokenValiditySeconds등에 대해서 설정을 로직에 대해서 주입
- inMemory/jdbc/withClientDetails 3가지 방식을 제공하는 것으로 보아도 무방하다.
- inMemory source에서 clientDetails에 대해서 정의를 하고 말그대로 메모리에 올려서 처리하는 방식
- jdbc jdbcTemplate(spring jdbcMapper) 방식의 구현체
- withClientDetails bean로 ClientDetailsService구현체를 주입을 하던 custom ClientDetailsService의 구현체를 작성하여 주입이 가능/ 본인의 경우 해당 부분을 구현하여 처리
===================OAuth2AuthorizationServerConfig.java=============
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {// @formatter:off
// clients
// .jdbc(dataSource).withClient("sampleClientId").authorizedGrantTypes("implicit")
// .scopes("read", "write", "foo", "bar").autoApprove(false).accessTokenValiditySeconds(3600)
//
// .and().withClient("fooClientIdPassword").secret("secret")
// .authorizedGrantTypes("password", "authorization_code", "refresh_token","client_credentials").scopes("foo", "read", "write")
// .accessTokenValiditySeconds(3600) // 1 hour
// .refreshTokenValiditySeconds(2592000) // 30 days
//
// .and().withClient("barClientIdPassword").secret("secret")
// .authorizedGrantTypes("password", "authorization_code", "refresh_token").scopes("bar", "read", "write")
// .accessTokenValiditySeconds(3600) // 1 hour
// .refreshTokenValiditySeconds(2592000) // 30 days
// ;
//clients.jdbc(dataSource).;
clients.withClientDetails(clientDetailsService);
}
AuthorizationServerSecurityConfigurer
- token endpoint에 대한 대한 제약을 config
- 접근제어 및 접근 제어 방식에 대한 정의
===================OAuth2AuthorizationServerConfig.java=============
@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//oauthServer.passwordEncoder(passwordEncoder)
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
AuthorizationServerEndpointsConfigurer
-
===================OAuth2AuthorizationServerConfig.java=============
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// @formatter:off
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints.approvalStore(approvalStore).tokenStore(tokenStore).authorizationCodeServices(authorizationCodeServices())
.tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
// @formatter:on
}
여기서 중요한 포인트는 EndPoint라는 건데 결론적으로는 controller와 동일한 기능을 처리 한다.
FrameworkEndpointHandlerMapping라는 클래스가 RequestMappingHandlerMapping를 상속 받아서 @FrameworkEndpoint라는 SpringSecurity에서 생성한 인터페이스 기반으로 Endpoint를 조회해서 requestMapping을 등록하는 역활을 한다.
즉 @FrameworkEndpoint를 붙혀주면 해당 클래스는 FrameworkEndpointHandlerMapping를 통해서 controller와 같이 동작한다고 보면 된다.
기존에 라이브러리에서 제공되는 FrameworkEndpoint의 리스트 아래와 같다.
AuthorizationEndpoint
- authorize_code 발급을 위한 로직
- /oauth/authorize api에 대한 기능 구현
CheckTokenEndpoint
- accessToken에 대한 체크 로직
- /oauth/check_token api에 대한 기능 구현
TokenEndpoint
- accessToken를 발급하는 로직
- /oauth/token api에 대한 기능 구현
TokenKeyEndpoint
- JWT관련된 tokenKey 관련 기능 (사용안함)
- /oauth/token_key api에 대한 기능 구현
WhitelabelApprovalEndpoint
- authorize_code 발급시 scope에 대한 approval 를 정의하는 UI페이지에 대한 처리
- /oauth/confirm_access 페이지에 대한 기능 구현
WhitelabelErrorEndpoint
- error페이지에 대한 기능
- /oauth/error 페이지에 대한 기능 구현
아래는 필요한 api에 대하서 작성한 예제이다.
- 물론 해당 API를 controller로 구현하여도 무방하다.
- 하지만 기존 Oauth코드와 통일성을 유지하기 위하여 custom API에 대해서 동일하게 FrameworkEndpoint 형태로 개발 진행
===============ClientManageEndpoint .java=============
@FrameworkEndpoint
public class ClientManageEndpoint {
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private ClientRegistrationService clientRegistrationService;
protected final Log logger = LogFactory.getLog(getClass());
private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();
public ClientManageEndpoint(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
/**
* @param exceptionTranslator the exception translator to set
*/
public void setExceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}
@RequestMapping(value = "/oauth/clients/{clientId}", method = RequestMethod.GET)
@ResponseBody
public Object checkToken(@PathVariable("clientId") String clientId) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
if (client == null) {
throw new InvalidClientException("Client was not recognised");
}
return client;
}
...중략
Oauth를 위한 Service를 구성하는 있어서 springSecurity에서 제공하는 jdbc 방식을 이용하여 구현한다면 개발 공수는 거의 없다고 할수있다. 하지만 customizing을 하는데 있어 제약이 많은 관계로 요구사항이 많은 프로젝트라고 한다면 Service를 구현하여 customizing하는 것을 추천한다.
저는 기존에 사용하던 Mybatis를 기반으로 구현을 진행하였다.
=================clientDetailMapper.groovy
@Mapper
public interface ClientDetailsMapper {
@Results(id="ClientDetailsResult", value = [
@Result(property = "clientId", column = "client_id"),
@Result(property = "clientSecret", column = "client_secret"),
@Result(property = "resourceIds", column = "resource_ids", typeHandler=CommaDelimitedListTypeHandler.class),
@Result(property = "scope", column="scope", typeHandler=CommaDelimitedListTypeHandler.class),
@Result(property = "authorizedGrantTypes", column = "authorized_grant_types", typeHandler=CommaDelimitedListTypeHandler.class),
@Result(property = "registeredRedirectUris", column = "web_server_redirect_uri",typeHandler=CommaDelimitedListTypeHandler.class),
@Result(property = "authorities", column = "authorities", jdbcType=JdbcType.VARCHAR, typeHandler=AuthorityListTypeHandler.class),
@Result(property = "accessTokenValiditySeconds", column = "access_token_validity"),
@Result(property = "refreshTokenValiditySeconds", column = "refresh_token_validity"),
@Result(property = "additionalInformation", column = "additional_information", typeHandler=JsonMappedTypeHandler.class),
@Result(property = "autoApproveScopes", column = "autoapprove",typeHandler=CommaDelimitedListTypeHandler.class)
])
@Select("""<script>
SELECT client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove
FROM oauth_client_details
WHERE client_id = #{clientId}
</script>""")
public BaseClientDetails selectClientDetails(String clientId);
@Insert("""<script>
INSERT INTO oauthdb.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
) VALUES (
#{clientId}
,#{clientSecret}
,#{resourceIds,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
,#{scope,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
,#{authorizedGrantTypes,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
,#{registeredRedirectUri,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
,#{authorities,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.AuthorityListTypeHandler}
,#{accessTokenValiditySeconds}
,#{refreshTokenValiditySeconds}
,#{additionalInformation,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.JsonMappedTypeHandler}
,#{autoApproveScopes,jdbcType=VARCHAR, typeHandler=com.oneplat.oap.oauth.common.mybatis.handler.CommaDelimitedListTypeHandler}
)
</script>""")
public int insertClientDetails(ClientDetails clientDetails);
@ResultMap("ClientDetailsResult")
@Select("""<script>
SELECT client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove
FROM oauth_client_details
</script>""")
public List<ClientDetails> selectClientDetailsList();
}
================ClientDetailServiceImpl.java===========
@Service("ClientDetailServiceImpl")
public class ClientDetailServiceImpl implements ClientDetailsService, ClientRegistrationService {
@Autowired
ClientDetailsMapper clientDetailsMapper;
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
// TODO Auto-generated method stub
return clientDetailsMapper.selectClientDetails(clientId);
}
@Override
public void addClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException {
// TODO Auto-generated method stub
clientDetailsMapper.insertClientDetails(clientDetails);
}
@Override
public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException {
// TODO Auto-generated method stub
}
@Override
public void updateClientSecret(String clientId, String secret) throws NoSuchClientException {
// TODO Auto-generated method stub
}
@Override
public void removeClientDetails(String clientId) throws NoSuchClientException {
// TODO Auto-generated method stub
}
@Override
public List<ClientDetails> listClientDetails() {
// TODO Auto-generated method stub
return clientDetailsMapper.selectClientDetailsList();
}
}
=====================OAuth2AuthorizationServerConfig.java
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private Environment env;
@Autowired
DataSource dataSource;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Value("classpath:schema.sql")
private Resource schemaScript;
@Autowired PasswordEncoder passwordEncoder;
@Autowired
ApprovalStore approvalStore;
@Autowired
TokenStore tokenStore;
@Autowired
@Qualifier("ClientDetailServiceImpl")
ClientDetailsService clientDetailsService;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//oauthServer.passwordEncoder(passwordEncoder)
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {// @formatter:off
// clients
// .jdbc(dataSource).withClient("sampleClientId").authorizedGrantTypes("implicit")
// .scopes("read", "write", "foo", "bar").autoApprove(false).accessTokenValiditySeconds(3600)
//
// .and().withClient("fooClientIdPassword").secret("secret")
// .authorizedGrantTypes("password", "authorization_code", "refresh_token","client_credentials").scopes("foo", "read", "write")
// .accessTokenValiditySeconds(3600) // 1 hour
// .refreshTokenValiditySeconds(2592000) // 30 days
//
// .and().withClient("barClientIdPassword").secret("secret")
// .authorizedGrantTypes("password", "authorization_code", "refresh_token").scopes("bar", "read", "write")
// .accessTokenValiditySeconds(3600) // 1 hour
// .refreshTokenValiditySeconds(2592000) // 30 days
// ;
//clients.jdbc(dataSource).;
clients.withClientDetails(clientDetailsService);
} // @formatter:on
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// @formatter:off
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints.approvalStore(approvalStore).tokenStore(tokenStore).authorizationCodeServices(authorizationCodeServices())
.tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
// @formatter:on
}
// @Bean
// public ClientDetailsService clientDetailsService() {
// return new JdbcClientDetailsService(dataSource);
// }
// @Bean
// public ApprovalStore approvalStore() {
// return new JdbcApprovalStore(dataSource);
// }
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore);
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
// @Bean
// public TokenStore tokenStore() {
// return new JdbcTokenStore(dataSource);
// }
//신규 EndPoint 등록
@Bean
public ClientManageEndpoint clientManageEndpoint() {
return new ClientManageEndpoint(clientDetailsService);
}
}
피드 구독하기:
글 (Atom)
