OAuth2 i Spring Boot

Izvor: SIS Wiki
Skoči na: orijentacija, traži

Sadržaj

Uvod

U ovom radu bit će objašnjen princip rada OAuth protokola te će ukratko biti razjašnjeno što je to Spring Boot. Bit će objašnjeno koje sve role postoje u OAuth protokolu, vrste token storea, grant typeova i slično. Na kraju će biti pokazano kako pomoću Spring Boota ostvariti OAuth2 standard, točnije i Authorization server i Resource server.

OAuth

OAuth je autorizacijski standard koji omogućava klijentskim aplikacijama pristup korisničkim podacima na HTTP servisima kao što su Facebook, Google, Twiter itd. Bez dijeljenja korisnikove lozinke. Na primjer, ako imate aplikaciju koja za korisnika može objavljivati postove na Facebooku, korisnik se prijavljuje na Facebook svojim korisničkim imenom i lozinkom te na taj način autorizira aplikaciju da u njegovo ime smije objavljivati postove. Jednom kad se korisnik prijavio aplikacija dobiva access token te se na taj način autorizira bez da koristi i zna korisničke podatke.

Nastao je krajem 2006. godine, a krajem 2012. objavljena je nova verzija nazvana Oauth 2.0 ili kraće OAuth2. Kod OAuth2 je pojednostavljen razvoj klijentskih aplikacija jer pruža posebne autorizacijske tokove za web aplikacije, desktop aplikacije i mobilne telefone.

Role

Kod OAuth standarda je ključno raspoznavati 4 role:

Oauth flow.PNG

Registracija aplikacije - Client ID i Client Secret

Da bi bilo moguće koristiti autentikaciju i autorizacijeu preko neke treće strane potrebno je prvo registrirati svoju aplikaciju na tom servisu. Kod registracije je potrebno popuniti informacije kao što su ime aplikacije, web adresa aplikacije i redirect ili callback URL. Nakon registracije bit će dodijeljeni client ID i client secret. Client id je javni string koji služi za identifikaciju aplikacije i sastavljanje autorizacijskog URL-a. Client ID služi kako bi servis autenticirao aplikaciju i naravno mora biti privatan.

U našem primjeru registracija neće biti potrebna jer će u konfiguraciji autorizacijskog servera biti postavljeni client id i secret koji će se koristiti.

Tokeni

Tokeni se dobivaju o strane autorizacijskogservera naon što su autentikacija i utorizacija prošli.

Postoje dvije vrste tokena:

Grant Types

Postoje četiri načina traženja pristupa te se koriste ovisno o tipovima koje podržava servis API te vrsti aplikacije koja traži pristup.

Tipovi traženja pristup su:

Spring Boot

Spring Boot znatno olakšava izradu aplikacija koje koriste Spring tako da na temelju startera koji se uključe u pom automatski pretpostavlja koji su sve jar-ovi potrebni te na temelju toga i anotacija slaže konfiguraciju. Npr. Ako želimo napraviti web aplikaciju u pom je potrebno uključiti “spring-boot-starter-web” te će Spring Boot uključiti apsolutno sve što je potrebno za razvoj full-stack web aplikacije pa čak i Tomcat i spring-webmvc. Također omogućava lagano mijenjanje ili podešavanje nekih postavki kroz application.properties s već unaprijed određenim ključevima i mogućim vrijednostima. Ako za nešto želimo ručno napisati konfiguraciju i to je naravno moguće uz određenu anotaciju.

Konfiguracija

Kao server bit će korišten Apache Tomcat. Biti će konfigururan da koristi ssl, tj. HTTPS, te će i Spring Boot aplikacija biti konfigurirana da prihvaća samo HTTPS zahtjeve.

Tomcat

Kako bi omogućili ssl na Tomcatu, potrebno je izgenerirati certfikat pozivanjem naredbe:

$JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA -keystore /path/to/my/keystore

Nakon toga potrebno je u server.xml datoteci odkomentirati sljedeći dio

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
              maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
              keystoreFile="" keystorePass=""
              clientAuth="false" sslProtocol="TLS" />

To će omogućiti ssl na portu 8443. Pod keystoreFile potrebno je dodati putanju do keystorea, a pod keystorePass lozinku za taj keystore.

Authorization i Resource server

Ovaj tutorial bit će orijentiran na Oauth pa neće biti prikazane klase kao što su hibernate modeli ili interface i implementacije servisnog ili DAO sloja.

Preko web stranice https://start.spring.io/ ili Eclipse dodatka moguće je izgenerirati projekt koji će automatski postaviti sve što treba za početak te je aplikaciju odmah moguće pokrenuti.


Klasa u kojoj je konfiguracija OAuth Authorization servera i Resource servera izgleda ovako:

package hr.optimit.security;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
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.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
public class OAuth2Configuration {

	@Configuration
	@EnableResourceServer
	protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

		@Autowired
		private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

		@Autowired
		private CustomLogoutSuccessHandler customLogoutSuccessHandler;

		@Override
		public void configure(HttpSecurity http) throws Exception {

			http
				.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint)
				.and()
				.logout()
				.logoutUrl("/oauth/logout")
				.logoutSuccessHandler(customLogoutSuccessHandler)
				.and()
				.csrf()
				.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
				.disable()
				.headers()
				.frameOptions().disable()
				.and()
				.sessionManagement()
				.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
				.and()
				.requiresChannel()
				.anyRequest().requiresSecure()
				.and()
				.authorizeRequests()
				.antMatchers("/mt2a/api/**").authenticated();
		}
	}

	@Configuration
	@EnableAuthorizationServer
	protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {

		private static final String ENV_OAUTH = "authentication.oauth.";

		private static final String PROP_CLIENTID = "clientid";

		private static final String PROP_SECRET = "secret";

		private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";

		private RelaxedPropertyResolver propertyResolver;

		@Autowired
		private DataSource dataSource;

		@Bean
		public TokenStore tokenStore() {
			return new JdbcTokenStore(dataSource);
		}

		@Autowired
		@Qualifier("authenticationManagerBean")
		private AuthenticationManager authenticationManager;

		@Override
		public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
			endpoints
					.tokenStore(tokenStore())
					.authenticationManager(authenticationManager);
		}

		@Override
		public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

			clients
					.inMemory()
					.withClient(propertyResolver.getProperty(PROP_CLIENTID))
					.scopes("read", "write")
					.authorizedGrantTypes("password", "refresh_token")
					.secret(propertyResolver.getProperty(PROP_SECRET))
					.accessTokenValiditySeconds(
							propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, PROP_TOKEN_VALIDITY_SECONDS));

		}

		@Override
		public void setEnvironment(Environment environment) {
			this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
		}

	}

}

Application.properties datoteka sadrži sljedeće vrijednosti

#data source properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mt2a
spring.datasource.username=postgres
spring.datasource.password=Zagy1991

# JPA properties
spring.jpa.show_sql=true
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=update
spring.data.jpa.repositories.enabled=true

spring.data.rest.base-path=/mt2a/api

#Application specific
authentication.oauth.clientid=mt2a
authentication.oauth.secret=Zagy1991
authentication.oauth.tokenValidityInSeconds=1800

security.require_ssl=true

Kod konfigracije Resource servera koištene su neke standardnespring postavke kao što su rukovanje iznimkama kod logina, logout url i success handler, zaštita od CSRF-a i slino. Najvažnije su zapravo zadnjedvije linije koje kažu da svaki zahjev koji dolazi na url /mt2/api/ mora biti autenticiran.

Kod Authorization servera je postavljn authentication manager i Jdbc Token Store. Kod Jdbc Token Storea se tokeni spremaju u bazu podataka. Postoje još i In Memory Token Store koji sprema tokene u memorijuili JWT Token store koji ih zapravo ne sprema nigdje već samo čita informacije iz tokena.

Da bi se mogao koristiti Jdbc Token store potrebno je kreirati određene tablice u bazi. Sljedeće skripte služe za kreiranje tih tablica na Postgres bazi.

create table oauth_access_token (
  token_id VARCHAR(256),
  token bytea,
  authentication_id VARCHAR(256),
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication bytea,
  refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
  token_id VARCHAR(256),
  token bytea,
  authentication bytea
);

Grant Type je postavljen na password i na refresh token. Client ID, client secret i trajanje access tokena se čitaju iz application.properties datoteke.

Testiranje

Za dobivanje access tokena poslan je sljedeći zahtjev:

curl -k -vu mt2a:Zagy1991 -d "username=hlevnjak&password=test&grant_type=password" https://localhost:8443/oauth/token

U odgovoru su dobiveni access i refresh token.

{"access_token":"2cacdb78-b7f2-4d14-a518-75c68cca8881","token_type":"bearer","refresh_token":"361a6e1b-1864-4f10-8433-744447fd8d98","expires_in":59,"scope":"read write"}

Nakon toga je iskorišten access token kako bi dohvatili podatke. Poslan je sljedeći zahtjev:

curl -i -k -H "Authorization: Bearer 2cacdb78-b7f2-4d14-a518-75c68cca8881" https://localhost:8443/mt2a/api/users
<pre>

Dobiven je sljedeći odgovor:

<pre>
{
  "_embedded" : {
    "users" : [ {
      "userUsername" : "hlevnjak",
      "userPassword" : "57900f52882ff73115d43a5cfb82b1fcf4730ff2dd5ab8746c7a663887cb605a3fa8ecab41ad22b7",
      "userStatus" : "active",
      "userRole" : "user",
      "userFirstName" : "Tomislav",
      "userLastName" : "Hlevnjak",
      "userEmail" : "hlevnjak.tomislav@gmail.com",
      "userCreationTimestamp" : "2016-02-25T17:49:38.640+0000",
      "sysModificationTimestamp" : "2016-02-25T17:49:38.640+0000",
      "sysModificationUser" : 1,
      "allowedDomains" : "*",
      "_links" : {
        "self" : {
          "href" : "https://localhost:8443/mt2a/api/users/1"
        },
        "apnaUser" : {
          "href" : "https://localhost:8443/mt2a/api/users/1"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "https://localhost:8443/mt2a/api/users"
    },
    "profile" : {
      "href" : "https://localhost:8443/mt2a/api/profile/users"
    },
    "search" : {
      "href" : "https://localhost:8443/mt2a/api/users/search"
    }
  }
}

Kada istekne access token,moguće ga je ponovno zatražiti pomoću refresh tokena.

curl -k -vu mt2a:Zagy1991 -d "username=hlevnjak&password=test&grant_type=refresh_token&refresh_token=361a6e1b-1864-4f10-8433-744447fd8d98" http://localhost:8080/oauth/token

Zaključak

OAuth je odličan i trenutno jako korišten način autentikacije i autorizacije klijentsikh aplikacija sa HTTP servisima kao što su Facebook, Twitter, Google itd. Obzirom da svi večina takvih servisa ima omogućen OAuth, nije komplicirana implementacija istog na klijentskim aplikacijama bilo da se radi o web aplikacijama, desktop aplikacijama ili mobilim aplikacijama. Uz Sprng Boot implementacija Autorizacijskog i Resurce servera je također jednostavna što je i prikazano ranije.

Liteatura

--Tomislav Hlevnjak

Osobni alati
Imenski prostori
Inačice
Radnje
Orijentacija
Traka s alatima