1. 개요
Security은 Spring 생태계에서 일류 시민입니다. 따라서 OAuth2가 거의 구성없이 Spring Web MVC와 함께 작동 할 수 있다는 것은 놀라운 일이 아닙니다.
그러나 네이티브 Spring 솔루션이 프레젠테이션 레이어를 구현하는 유일한 방법은 아닙니다. JAX-RS 호환 구현 인 Jersey 는 Spring OAuth2와 함께 작동 할 수도 있습니다.
이 예제에서는 OAuth2 표준을 사용하여 구현 된 Spring Social Login으로 Jersey 애플리케이션을 보호하는 방법을 알아 봅니다 .
2. Maven 의존성
Jersey를 Spring Boot 애플리케이션에 통합하기 위해 spring-boot-starter-jersey 아티팩트를 추가해 보겠습니다 .
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
Security OAuth2를 구성하려면 spring-boot-starter-security 및 spring-security-oauth2-client가 필요합니다 .
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
Spring Boot Starter Parent 버전 2를 사용하여 이러한 모든 의존성을 관리 할 것 입니다.
3. 저지 프레젠테이션 레이어
Jersey를 프레젠테이션 계층으로 사용하려면 두 개의 엔드 포인트가있는 리소스 클래스가 필요합니다.
3.1. 리소스 클래스
다음은 엔드 포인트 정의를 포함하는 클래스입니다.
@Path("/")
public class JerseyResource {
// endpoint definitions
}
클래스 자체는 매우 간단합니다 . @Path 어노테이션 만 있습니다. 이 어노테이션의 값은 클래스 본문의 모든 끝점에 대한 기본 경로를 식별합니다.
이 리소스 클래스는 컴포넌트 스캐닝을위한 스테레오 타입 어노테이션을 가지고 있지 않다는 것을 언급 할 가치가 있습니다. 사실 Spring Bean 일 필요도 없습니다. 그 이유는 요청 매핑을 처리하기 위해 Spring에 의존하지 않기 때문입니다.
3.2. 로그인 페이지
로그인 요청을 처리하는 방법은 다음과 같습니다.
@GET
@Path("login")
@Produces(MediaType.TEXT_HTML)
public String login() {
return "Log in with <a href=\"/oauth2/authorization/github\">GitHub</a>";
}
이 메서드는 / login 엔드 포인트 를 대상으로하는 GET 요청에 대한 문자열을 반환 합니다. text / html과의 콘텐츠 형식은 클릭 가능한 링크 응답을 표시하는 사용자의 브라우저에 지시합니다.
GitHub를 OAuth2 공급자로 사용하므로 / oauth2 / authorization / github 링크가 사용 됩니다. 이 링크는 GitHub 승인 페이지로의 리디렉션을 트리거합니다.
3.3. 홈 페이지
루트 경로에 대한 요청을 처리하는 다른 방법을 정의 해 보겠습니다.
@GET
@Produces(MediaType.TEXT_PLAIN)
public String home(@Context SecurityContext securityContext) {
OAuth2AuthenticationToken authenticationToken = (OAuth2AuthenticationToken) securityContext.getUserPrincipal();
OAuth2AuthenticatedPrincipal authenticatedPrincipal = authenticationToken.getPrincipal();
String userName = authenticatedPrincipal.getAttribute("login");
return "Hello " + userName;
}
이 메소드는 로그인 한 사용자 이름을 포함하는 문자열 인 홈페이지를 반환합니다. 이 경우 로그인 속성 에서 사용자 이름을 추출했습니다 . 하지만 다른 OAuth2 공급자는 사용자 이름에 대해 다른 속성을 사용할 수 있습니다.
분명히 위의 방법은 인증 된 요청에 대해서만 작동합니다. 요청이 인증되지 않은 경우 로그인 엔드 포인트 로 리디렉션됩니다 . 섹션 4에서이 리디렉션을 구성하는 방법을 살펴 보겠습니다.
3.4. Spring 컨테이너에 Jersey 등록
Jersey 서비스를 활성화하기 위해 서블릿 컨테이너에 리소스 클래스를 등록 해 보겠습니다. 다행히도 매우 간단합니다.
@Component
public class RestConfig extends ResourceConfig {
public RestConfig() {
register(JerseyResource.class);
}
}
등록함으로써 JerseyResource을 A의 ResourceConfig의 서브 클래스, 우리는 자원 클래스의 모든 엔드 포인트의 서블릿 컨테이너를 알렸다.
마지막 단계는 Spring 컨테이너에 ResourceConfig 하위 클래스 ( 이 경우 RestConfig )를 등록하는 것 입니다. @Component 어노테이션 으로이 등록을 구현했습니다 .
4. 스프링 Security 설정
일반적인 Spring 애플리케이션과 마찬가지로 Jersey에 대한 Security을 구성 할 수 있습니다.
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.loginPage("/login");
}
}
주어진 체인에서 가장 중요한 방법은 oauth2Login 입니다. 이 방법 은 OAuth 2.0 공급자를 사용하여 인증 지원을 구성합니다. 이 사용방법(예제)에서 공급자는 GitHub입니다.
또 다른 눈에 띄는 구성은 로그인 페이지입니다. loginPage 메소드에 문자열 “/ login” 을 제공함으로써 Spring에 인증되지 않은 요청을 / login 엔드 포인트 로 리디렉션하도록 지시 합니다.
기본 Security 구성은 / login 에서 자동 생성 된 페이Map 제공합니다 . 따라서 로그인 페이지를 구성하지 않은 경우에도 인증되지 않은 요청은 해당 엔드 포인트로 리디렉션됩니다.
기본 구성과 명시 적 설정의 차이점은 기본 경우 응용 프로그램이 사용자 지정 문자열이 아닌 생성 된 페이지를 반환한다는 것입니다.
5. 애플리케이션 구성
OAuth2로 보호되는 애플리케이션을 사용하려면 OAuth2 공급자에 클라이언트를 등록해야합니다. 그런 다음 클라이언트의 자격 증명을 애플리케이션에 추가합니다.
5.1. OAuth2 클라이언트 등록
GitHub 앱 을 등록 하여 등록 프로세스를 시작하겠습니다 . GitHub 개발자 페이지를 방문한 후 새 OAuth 앱 버튼을 눌러 새 OAuth 신청 양식 을 엽니 다 .
다음으로 표시된 양식에 적절한 값을 입력하십시오. 애플리케이션 이름으로 앱을 인식 할 수있는 문자열을 입력합니다. 홈페이지 URL은 http : // localhost : 8083이고 인증 콜백 URL은 http : // localhost : 8083 / login / oauth2 / code / github 입니다.
콜백 URL은 사용자가 GitHub로 인증하고 애플리케이션에 대한 액세스 권한을 부여한 후 브라우저가 리디렉션하는 경로입니다.
등록 양식은 다음과 같습니다.
이제 애플리케이션 등록 버튼을 클릭합니다. 그런 다음 브라우저는 GitHub 앱의 홈페이지로 리디렉션되어 클라이언트 ID와 클라이언트 암호가 표시됩니다.
5.2. Spring Boot 애플리케이션 구성
jersey-application.properties 라는 속성 파일 을 클래스 경로에 추가해 보겠습니다 .
server.port=8083
spring.security.oauth2.client.registration.github.client-id=<your-client-id>
spring.security.oauth2.client.registration.github.client-secret=<your-client-secret>
자리 표시 자 <your-client-id> 및 <your-client-secret> 을 자체 GitHub 애플리케이션의 값 으로 바꾸는 것을 잊지 마십시오 .
마지막으로이 파일을 Spring Boot 애플리케이션에 속성 소스로 추가합니다.
@SpringBootApplication
@PropertySource("classpath:jersey-application.properties")
public class JerseyApplication {
public static void main(String[] args) {
SpringApplication.run(JerseyApplication.class, args);
}
}
6. 작동중인 인증
GitHub에 등록한 후 애플리케이션에 로그인하는 방법을 살펴 보겠습니다.
6.1. 응용 프로그램에 액세스
애플리케이션을 시작한 다음 localhost : 8083 주소로 홈페이지에 액세스합니다 . 요청이 인증되지 않았으므로 로그인 페이지 로 리디렉션됩니다 .
이제 GitHub 링크를 누르면 브라우저가 GitHub 승인 페이지로 리디렉션됩니다.
URL을 보면 리디렉션 된 요청이 response_type , client_id , scope 같은 많은 쿼리 매개 변수를 전달했음을 알 수 있습니다 .
https://github.com/login/oauth/authorize?response_type=code&client_id=c30a16c45a9640771af5&scope=read:user&state=dpTme3pB87wA7AZ--XfVRWSkuHD3WIc9Pvn17yeqw38%3D&redirect_uri=http://localhost:8083/login/oauth2/code/github
response_type 의 값 은 code 이며 OAuth2 부여 유형이 권한 코드임을 의미합니다. 한편 client_id 매개 변수는 애플리케이션을 식별하는 데 도움이됩니다. 모든 매개 변수의 의미 는 GitHub 개발자 페이지를 참조하세요 .
승인 페이지가 표시되면 계속하려면 애플리케이션을 승인해야합니다. 승인이 성공하면 브라우저는 몇 가지 쿼리 매개 변수와 함께 애플리케이션의 미리 정의 된 엔드 포인트로 리디렉션됩니다.
http://localhost:8083/login/oauth2/code/github?code=561d99681feeb5d2edd7&state=dpTme3pB87wA7AZ--XfVRWSkuHD3WIc9Pvn17yeqw38%3D
이면에서 애플리케이션은 인증 코드를 액세스 토큰으로 교환합니다. 이후이 토큰을 사용하여 로그인 한 사용자에 대한 정보를 얻습니다.
localhost : 8083 / login / oauth2 / code / github에 대한 요청이 반환 된 후 브라우저는 홈페이지로 돌아갑니다. 이번에 는 사용자 이름이 포함 된 인사말 메시지가 표시됩니다 .
6.2. 사용자 이름을 얻는 방법?
인사말 메시지의 사용자 이름이 GitHub 사용자 이름이라는 것이 분명합니다. 이 시점에서 질문이 발생할 수 있습니다. 인증 된 사용자로부터 사용자 이름과 기타 정보를 어떻게 얻을 수 있습니까?
이 예에서는 로그인 속성 에서 사용자 이름을 추출했습니다 . 그러나 이것은 모든 OAuth2 공급자에서 동일하지 않습니다. 즉, 공급자는 자체 재량에 따라 특정 속성의 데이터를 제공 할 수 있습니다. 따라서 우리는 이와 관련하여 단순히 표준이 없다고 말할 수 있습니다.
GitHub의 경우 참조 문서 에서 필요한 속성을 찾을 수 있습니다 . 마찬가지로 다른 OAuth2 공급자는 자체 참조를 제공합니다.
또 다른 해결책은 디버그 모드에서 애플리케이션을 시작하고 OAuth2AuthenticatedPrincipal 개체가 생성 된 후 중단 점을 설정할 수 있다는 것입니다. 이 개체의 모든 속성을 살펴보면 사용자 정보에 대한 통찰력을 갖게됩니다.
7. 테스트
애플리케이션의 동작을 확인하기 위해 몇 가지 테스트를 작성해 보겠습니다.
7.1. 환경 설정
테스트 메서드를 보유 할 클래스는 다음과 같습니다.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@TestPropertySource(properties = "spring.security.oauth2.client.registration.github.client-id:test-id")
public class JerseyResourceUnitTest {
@Autowired
private TestRestTemplate restTemplate;
@LocalServerPort
private int port;
private String basePath;
@Before
public void setup() {
basePath = "http://localhost:" + port + "/";
}
// test methods
}
실제 GitHub 클라이언트 ID를 사용하는 대신 OAuth2 클라이언트에 대한 테스트 ID를 정의했습니다. 이 ID는 spring.security.oauth2.client.registration.github.client-id 속성으로 설정됩니다 .
이 테스트 클래스의 모든 어노테이션은 Spring Boot 테스트에서 공통적이므로이 예제에서는 다루지 않습니다. 이러한 어노테이션 중 하나라도 명확하지 않은 경우에는 Testing in Spring Boot , Integration Testing in Spring 또는 Exploring the Spring Boot TestRestTemplate로 이동하십시오 .
7.2. 홈 페이지
인증되지 않은 사용자가 홈 페이지에 액세스하려고하면 인증 을 위해 로그인 페이지로 리디렉션된다는 것을 증명합니다.
@Test
public void whenUserIsUnauthenticated_thenTheyAreRedirectedToLoginPage() {
ResponseEntity<Object> response = restTemplate.getForEntity(basePath, Object.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(response.getBody()).isNull();
URI redirectLocation = response.getHeaders().getLocation();
assertThat(redirectLocation).isNotNull();
assertThat(redirectLocation.toString()).isEqualTo(basePath + "login");
}
7.3. 로그인 페이지
로그인 페이지에 액세스하면 인증 경로가 반환 되는지 확인하겠습니다 .
@Test
public void whenUserAttemptsToLogin_thenAuthorizationPathIsReturned() {
ResponseEntity response = restTemplate.getForEntity(basePath + "login", String.class);
assertThat(response.getHeaders().getContentType()).isEqualTo(TEXT_HTML);
assertThat(response.getBody()).isEqualTo("Log in with GitHub");
}
7.4. 승인 끝점
마지막으로 인증 엔드 포인트에 요청을 보낼 때 브라우저는 적절한 매개 변수를 사용하여 OAuth2 공급자의 인증 페이지로 리디렉션합니다.
@Test
public void whenUserAccessesAuthorizationEndpoint_thenTheyAresRedirectedToProvider() {
ResponseEntity response = restTemplate.getForEntity(basePath + "oauth2/authorization/github", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(response.getBody()).isNull();
URI redirectLocation = response.getHeaders().getLocation();
assertThat(redirectLocation).isNotNull();
assertThat(redirectLocation.getHost()).isEqualTo("github.com");
assertThat(redirectLocation.getPath()).isEqualTo("/login/oauth/authorize");
String redirectionQuery = redirectLocation.getQuery();
assertThat(redirectionQuery.contains("response_type=code"));
assertThat(redirectionQuery.contains("client_id=test-id"));
assertThat(redirectionQuery.contains("scope=read:user"));
}
8. 결론
이 예제에서는 Jersey 애플리케이션으로 Spring Social Login을 설정했습니다. 이 사용방법(예제)에는 GitHub OAuth2 공급자에 애플리케이션을 등록하는 단계도 포함되어 있습니다.
전체 소스 코드는 GitHub 에서 찾을 수 있습니다 .
- https://docs.spring.io/spring-framework/docs/current/reference/html
- https://www.baeldung.com/spring-security-social-login-jersey
'Java' 카테고리의 다른 글
Big O 표기법의 실용적인 Java 예제 (0) | 2021.03.20 |
---|---|
자바의 합성 구조 (0) | 2021.03.20 |
Java에서 일정 시간 후에 실행을 중지하는 방법 (0) | 2021.03.19 |
Open Liberty 소개 (0) | 2021.03.19 |
JBehave로 REST API 테스트 (0) | 2021.03.19 |