Single Sign-On na przykładzie GitHub

Single Sign-On na przykładzie GitHub

Artykuł ten ma na celu zaprezentowanie podejścia Single Sign-On na przykładzie logowania z użyciem serwisu GitHub. Single Sign-On to podejście które zakłada jeden centralny punkt logowania za pomocą którego użytkownik ma dostęp do wszystkich chronionych zasobów. Nie ma zatem potrzeby pamiętania kolejnych haseł oraz nie trzeba tworzyć dodatkowych mechanizmów uwierzytelniania. Zanim przejdziemy do projektowania aplikacji omówimy czym jest standard OAuth 2.0 (z ang. Open Authorization) – specyfikacja dostępna tutaj: http://tools.ietf.org/html/rfc6749. OAuth 2.0 to „protokół” który daje możliwość budowania bezpiecznych mechanizmów autoryzacyjnych z użyciem różnych platform np. portali WWW czy aplikacji mobilnych. Specyfikacja nie narzuca sztywnych reguł implementowania tego standardu dlatego określenie protokół użyte jest raczej na wyrost, jednak można spotkać się z takim określeniem. W dobie serwisów społecznościowych niemal każdy posiada konto tj. google, facebook, linkedin, twitter. Użycie zaufanego portalu do logowania do innych platform to idea standardu OAuth. Mechanizm działania (flow) jest następujący:

  • użytkownik klika w link i jest przekierowywany na stronę dostawcy np. może to być konto w serwisie google,
  • użytkownik potwierdza udzielenie uprawnień,
  • użytkownik przekierowywany jest na stronę aplikacji z unikalnym tokenem,
  • przeglądarka (klient) zapamiętuje token np. w pliku cookie i dołącza go do każdego zapytania,
  • aplikacja uzyskuje dostęp do danych użytkownika np. adresu email  i w ten sposób identyfikuje użytkowników na swojej platformie.

[źródło] http://habr.com/en/post/449182/

Pamiętajmy jednak, że autoryzacja to nie to samo co uwierzytelnianie, standard OAuth używany jest do autoryzacji czyli uzyskania dostępu, uwierzytelnianie natomiast to potwierdzenie tożsamości. Projektowanie aplikacji zaczynamy od nowego projektu Spring Boota – niezbędne zależności – plik pom.xml:

<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	<java.version>1.8</java.version>
	<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
</properties>
 
<dependencies>
 
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-oauth2</artifactId>
	</dependency>
 
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>javax.servlet-api</artifactId>
		<version>3.1.0</version>
	</dependency>
 
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-tomcat</artifactId>
		<scope>provided</scope>
	</dependency>
 
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
		<exclusions>
			<exclusion>
				<artifactId>tomcat-embed-el</artifactId>
				<groupId>org.apache.tomcat.embed</groupId>
			</exclusion>
			<exclusion>
				<artifactId>tomcat-embed-core</artifactId>
				<groupId>org.apache.tomcat.embed</groupId>
			</exclusion>
			<exclusion>
				<artifactId>tomcat-embed-websocket</artifactId>
				<groupId>org.apache.tomcat.embed</groupId>
			</exclusion>
		</exclusions>
	</dependency>
 
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>
 
<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

Aplikacja będzie działać na zewnętrznym serwerze zatem klasa startowa przedstawia się następująco:

@SpringBootApplication
public class Oauth2SsoApplication extends SpringBootServletInitializer {
 
	public static void main(String[] args) {
		SpringApplication.run(Oauth2SsoApplication.class, args);
	}
 
	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(Oauth2SsoApplication.class);
	}
}

Włączamy funkcjonalność OAuth – klasa SecurityConfig. Konfigurujemy dostęp do aplikacji, jeden zasób będzie tylko dostępny publicznie \public, inne zasoby są chronione i wymagają autentykacji:

@Configuration
@EnableOAuth2Sso
class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(httpecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login", "/public")
                    .permitAll()
                .anyRequest()
                    .authenticated();
    }
}

Plik application.properties:

security.oauth2.client.client-id                      = ***
security.oauth2.client.client-secret                  = ***
 
#access token
security.oauth2.client.access-token-uri               = http://github.com/login/oauth/access_token
 
#authorization
security.oauth2.client.user-authorization-uri         = http://github.com/login/oauth/authorize
 
#zcheme of authentication
security.oauth2.client.client-authentication-scheme   = form
 
#user info endpoint
security.oauth2.resource.user-info-uri                = http://api.github.com/user
 
#prefer user info
security.oauth2.resource.prefer-token-info            = false
 
security.basic.enabled                                = false
 
logging.level.org.springframework.security            = DEBUG

parametry security.oauth2.client.client-id  oraz security.oauth2.client.client-secret należy uzupełnić własnymi danymi które wygenerowane zostały przez GitHuba. Link gdzie opisany jest proces jak dokładnie utworzyć aplikację na GitHubie – http://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/. Po utworzeniu aplikacji jako Authorization callback URL podajemy przykładowo – należy dostosować do własnej domeny:

http://oauth.javaleader.pl/login

Tworzymy RestController:

@RestController
public class RestControllerApi {
 
    @GetMapping("/public")
    String getPublicContent(){
        return "getPublicContent";
    }
 
    @GetMapping("/private")
    String getPrivateContent(){
        return "getPrivateContent";
    }
}

Aplikacja wdrożona jest pod adresem: http://oauth.javaleader.pl

Testy:

http://oauth.javaleader.pl/public -> treść dostępna publicznie

http://oauth.javaleader.pl/private -> treść dostępna wyłącznie po zalogowaniu się z użyciem GitHubApi.

P.S.

Wdrożenie aplikacji w kontenerze servletów – Apache Tomcat wymagało dodania w pliku ./conf/server.xml wpisu:

<Host name="oauth.javaleader.pl"  appBase="javaleaderoauth"
		unpackWARs="true" autoDeploy="true">
 
 
	<!-- SingleSignOn valve, share authentication between web applications
		 Documentation at: /docs/config/valve.html -->
	<!--
	<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
	-->
 
	<!-- Access log processes all example.
		 Documentation at: /docs/config/valve.html
		 Note: The pattern used is equivalent to using pattern="common" -->
	<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
		   prefix="localhost_access_log" suffix=".txt"
		   pattern="%h %l %u %t &quot;%r&quot; %s %b" />
 
  </Host>

konfiguracja wirtualnych hostów:

<VirtualHost oauth.javaleader.pl:443>
 
    ServerName  oauth.javaleader.pl
    ServerAlias oauth.javaleader.pl
    SSLEngine on
    ...
 
    ProxyPass / ajp://localhost:8009/
    ProxyPassReverse / ajp://localhost:8009/
 
</VirtualHost>

W katalogu ./Tomcat/oauth.javaleader.pl zbudowany plik war powinien mieć nazwę ROOT. Pozwala to uniknąć doklejania zbędnej nazwy kontekstu aplikacji, w przeciwnym wypadku po wdrożeniu aplikacji na produkcje logowanie nie działa poprawnie właśnie przez doklejanie nazwy wynikowego pliku WAR do ścieżki URL aplikacji. Polecam dodatkowo wpis https://javaleader.pl/2019/06/12/konfiguracja-virtual-hosts-apache-tomcat-protokolu-ajp/ który przedstawia jak skonfigurować aplikację z życiem serwera VPS & Tomcata & protokołu AJP.

Zobacz kod na GitHubie i zapisz się na bezpłatny newsletter!

.

Leave a comment

Your email address will not be published.


*