OAuth2 ve JWT REST Koruması için Spring Boot'u Kullanma

Yayınlanan: 2022-03-11

Bu makale, Spring Boot ve Maven kullanılarak JSON Web Token (JWT) - OAuth2 yetkilendirme çerçevesinin sunucu tarafı uygulamasının nasıl kurulacağına ilişkin bir kılavuzdur.

OAuth2 hakkında ilk bilgi edinilmesi önerilir ve yukarıda bağlantısı verilen taslağı okuyarak veya web'de şu veya bu gibi yararlı bilgiler aranarak edinilebilir.

OAuth2, 2006'da oluşturulan ilk OAuth sürümünün yerini alan bir yetkilendirme çerçevesidir. Korunan kaynaklara erişim elde etmek için istemciler ve bir veya daha fazla HTTP hizmeti arasındaki yetkilendirme akışlarını tanımlar.

OAuth2, aşağıdaki sunucu tarafı rollerini tanımlar:

  • Kaynak Sahibi: Kaynakların erişimini kontrol etmekten sorumlu hizmet
  • Kaynak Sunucusu: Kaynakları fiilen sağlayan hizmet
  • Yetkilendirme Sunucusu: İstemci ve kaynak sahibi arasında aracı görevi gören hizmet işleme yetkilendirme süreci

JSON Web Token veya JWT, iki taraf arasında aktarılacak taleplerin temsili için bir belirtimdir. Talepler, şifreli bir yapının yükü olarak kullanılan bir JSON nesnesi olarak kodlanır ve taleplerin dijital olarak imzalanmasını veya şifrelenmesini sağlar.

İçeren yapı, JSON Web İmzası (JWS) veya JSON Web Şifrelemesi (JWE) olabilir.

JWT, OAuth2 protokolünde kullanılan erişim ve yenileme belirteçleri için format olarak seçilebilir.

OAuth2 ve JWT, aşağıdaki özellikler nedeniyle son yıllarda büyük bir popülerlik kazandı:

  • Durumsuz REST protokolü için durumsuz bir yetkilendirme sistemi sağlar
  • Birden çok kaynak sunucusunun tek bir yetkilendirme sunucusunu paylaşabileceği bir mikro hizmet mimarisine iyi uyum sağlar
  • JSON formatı sayesinde istemci tarafında yönetimi kolay belirteç içeriği

Ancak, proje için aşağıdaki hususların önemli olması durumunda, OAuth2 ve JWT her zaman en iyi seçim değildir:

  • Durum bilgisi olmayan bir protokol, sunucu tarafında erişim iptaline izin vermez
  • Belirteç için sabit yaşam süresi, güvenlikten ödün vermeden uzun süreli oturumları yönetmek için ek karmaşıklık sağlar (örneğin, belirteci yenileme)
  • İstemci tarafında bir belirteç için güvenli bir mağaza gereksinimi

Beklenen Protokol Akışı

OAuth2'nin temel özelliklerinden biri, yetkilendirme sürecini kaynak sahiplerinden ayırmak için bir yetkilendirme katmanının eklenmesi olsa da, basitlik adına makalenin sonucu, tüm kaynak sahibini , yetkilendirme sunucusunu ve yetkilendirme sunucusunu taklit eden tek bir uygulamanın oluşturulmasıdır. kaynak sunucusu rolleri. Bu nedenle, iletişim yalnızca iki varlık, sunucu ve istemci arasında akacaktır.

Bu sadeleştirme, makalenin amacına, yani Spring Boot ortamında böyle bir sistemin kurulumuna odaklanmaya yardımcı olmalıdır.

Basitleştirilmiş akış aşağıda açıklanmıştır:

  1. Yetkilendirme talebi, parola yetkilendirmesi kullanılarak istemciden sunucuya (kaynak sahibi olarak hareket ederek) gönderilir.
  2. Erişim belirteci istemciye döndürülür (yenileme belirteci ile birlikte)
  3. Erişim belirteci daha sonra korumalı kaynak erişimi için her istekte istemciden sunucuya (kaynak sunucusu görevi görür) gönderilir.
  4. Sunucu, gerekli korumalı kaynaklarla yanıt verir

Kimlik doğrulama akış şeması

Spring Security ve Spring Boot

Her şeyden önce, bu proje için seçilen teknoloji yığınına kısa bir giriş.

Tercih edilen proje yönetim aracı Maven'dir, ancak projenin basitliği nedeniyle Gradle gibi diğer araçlara geçmek zor olmamalıdır.

Makalenin devamında, yalnızca Spring Security yönlerine odaklanıyoruz, ancak tüm kod alıntıları, kaynak kodunun REST kaynaklarını tüketen bir istemciyle birlikte halka açık bir havuzda bulunan tamamen çalışan bir sunucu tarafı uygulamasından alınmıştır.

Spring Security, Spring tabanlı uygulamalar için neredeyse bildirimsel güvenlik hizmetleri sağlayan bir çerçevedir. Kökleri Baharın ilk başlangıcından gelmektedir ve kapsanan çok sayıda farklı güvenlik teknolojisi nedeniyle bir dizi modül olarak düzenlenmiştir.

Spring Security mimarisine hızlıca bir göz atalım (daha ayrıntılı bir kılavuz burada bulunabilir).

Güvenlik çoğunlukla kimlik doğrulama , yani kimliğin doğrulanması ve yetkilendirme , kaynaklara erişim haklarının verilmesi ile ilgilidir.

Spring security, üçüncü taraflarca sağlanan veya yerel olarak uygulanan çok çeşitli kimlik doğrulama modellerini destekler. Bir liste burada bulunabilir.

Yetkilendirme ile ilgili olarak, üç ana alan tanımlanmıştır:

  1. Web istekleri yetkilendirme
  2. Yöntem düzeyinde yetkilendirme
  3. Etki alanı nesne örnekleri yetkilendirmesine erişim

kimlik doğrulama

Temel arayüz, bir kimlik doğrulama yöntemi sağlamaktan sorumlu olan AuthenticationManager . UserDetailsService , standart JDBC veya LDAP yöntemleri durumunda doğrudan uygulanabilen veya dahili olarak kullanılabilen, kullanıcının bilgi toplamasıyla ilgili arabirimdir.

yetki

Ana arayüz AccessDecisionManager ; yukarıda listelenen tüm üç alan için hangi uygulamalar bir AccessDecisionVoter zincirine delege eder. İkinci arabirimin her bir örneği, bir Authentication (bir kullanıcı kimliği, sorumlu olarak adlandırılmış), bir kaynak ve bir ConfigAttribute koleksiyonu arasındaki ilişkiyi temsil eder; kaynak sahibinin kaynağın kendisine, belki de kullanıcı rollerinin kullanımı.

Bir web uygulamasının güvenliği, bir sunucu uygulaması filtreleri zincirinde yukarıda açıklanan temel öğeler kullanılarak uygulanır ve WebSecurityConfigurerAdapter sınıfı, kaynağın erişim kurallarını ifade etmenin bildirimsel bir yolu olarak sunulur.

Yöntem güvenliği, önce @EnableGlobalMethodSecurity(securedEnabled = true) ek açıklamasının varlığıyla ve ardından @Secured , @PreAuthorize ve @PostAuthorize gibi korunacak her yönteme uygulanacak bir dizi özel ek açıklamanın kullanılmasıyla etkinleştirilir.

Spring Boot, yüksek kalite standardını korurken geliştirmeyi kolaylaştırmak için tüm bunlara, üzerinde düşünülmüş uygulama yapılandırmaları ve üçüncü taraf kitaplıklardan oluşan bir koleksiyon ekler.

Yaylı Önyüklemeli JWT OAuth2

Şimdi, Spring Boot ile OAuth2 ve JWT'yi uygulayan bir uygulama kurmak için asıl probleme geçelim.

Java dünyasında birden çok sunucu tarafı OAuth2 kitaplığı mevcut olsa da (bir liste burada bulunabilir), Spring Security mimarisine iyi bir şekilde entegre olmasını ve bu nedenle çok fazla işlem yapma gereğini ortadan kaldırmayı umduğumuz için yay tabanlı uygulama doğal seçimdir. kullanımı için düşük seviyeli ayrıntılardan.

Güvenlikle ilgili tüm kitaplık bağımlılıkları, Maven'in yapılandırma dosyası pom.xml içinde açık bir sürüm gerektiren tek bileşen olan Spring Boot yardımıyla Maven tarafından işlenir (yani kitaplık sürümleri, Maven tarafından en güncel olanı seçerek otomatik olarak çıkarılır. eklenen Spring Boot sürümüyle uyumlu sürüm).

Spring Boot güvenliğiyle ilgili bağımlılıkları içeren maven yapılandırma dosyası pom.xml'den alıntıyı aşağıda bulabilirsiniz:

 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.1.0.RELEASE</version> </dependency>

Uygulama, hem OAuth2 yetkilendirme sunucusu/kaynak sahibi hem de kaynak sunucusu olarak hareket eder.

Korunan kaynaklar (kaynak sunucusu olarak) /api/ yolu altında yayınlanırken, kimlik doğrulama yolu (kaynak sahibi/yetkilendirme sunucusu olarak) önerilen varsayılanın ardından /oauth/token ile eşlenir.

Uygulamanın yapısı:

  • security yapılandırmasını içeren güvenlik paketi
  • errors işlemeyi içeren hata paketi
  • users , model, havuz ve denetleyici dahil REST kaynakları için glee paketleri

Sonraki paragraflar, yukarıda bahsedilen üç OAuth2 rolünden her biri için yapılandırmayı kapsar. İlgili sınıflar security paketinin içindedir:

  • OAuthConfiguration , AuthorizationServerConfigurerAdapter genişletiyor
  • ResourceServerConfiguration , ResourceServerConfigurerAdapter genişletme
  • ServerSecurityConfig , WebSecurityConfigurerAdapter genişletiyor
  • UserService , UserDetailsService uygulaması

Kaynak Sahibi ve Yetkilendirme Sunucusu Kurulumu

Yetkilendirme sunucusu davranışı, @EnableAuthorizationServer ek açıklamasının varlığıyla etkinleştirilir. Yapılandırması, kaynak sahibi davranışıyla ilgili olanla birleştirilir ve her ikisi de AuthorizationServerConfigurerAdapter sınıfında bulunur.

Burada uygulanan konfigürasyonlar şunlarla ilgilidir:

  • İstemci erişimi ( ClientDetailsServiceConfigurer kullanarak)
    • inMemory veya jdbc yöntemleriyle istemci ayrıntıları için bellek içi veya JDBC tabanlı depolama kullanımı seçimi
    • clientId ve clientSecret (seçilen PasswordEncoder bean ile kodlanmış) özniteliklerini kullanan istemcinin temel kimlik doğrulaması
    • refreshTokenValiditySeconds accessTokenValiditySeconds kullanarak erişim ve yenileme belirteçleri için geçerlilik süresi
    • YetkiliGrantTypes özniteliği kullanılarak authorizedGrantTypes verilen hibe türleri
    • scopes yöntemiyle erişim kapsamlarını tanımlar
    • Müşterinin erişilebilir kaynaklarını tanımlayın
  • Yetkilendirme sunucusu uç noktası ( AuthorizationServerEndpointsConfigurer kullanarak)
    • accessTokenConverter ile bir JWT belirtecinin kullanımını tanımlayın
    • Kimlik doğrulaması gerçekleştirmek için bir UserDetailsService ve AuthenticationManager arabirimlerinin kullanımını tanımlayın (kaynak sahibi olarak)
 package net.reliqs.gleeometer.security; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; 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.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; @Configuration @EnableAuthorizationServer public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter { private final AuthenticationManager authenticationManager; private final PasswordEncoder passwordEncoder; private final UserDetailsService userService; @Value("${jwt.clientId:glee-o-meter}") private String clientId; @Value("${jwt.client-secret:secret}") private String clientSecret; @Value("${jwt.signing-key:123}") private String jwtSigningKey; @Value("${jwt.accessTokenValidititySeconds:43200}") // 12 hours private int accessTokenValiditySeconds; @Value("${jwt.authorizedGrantTypes:password,authorization_code,refresh_token}") private String[] authorizedGrantTypes; @Value("${jwt.refreshTokenValiditySeconds:2592000}") // 30 days private int refreshTokenValiditySeconds; public OAuthConfiguration(AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder, UserDetailsService userService) { this.authenticationManager = authenticationManager; this.passwordEncoder = passwordEncoder; this.userService = userService; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient(clientId) .secret(passwordEncoder.encode(clientSecret)) .accessTokenValiditySeconds(accessTokenValiditySeconds) .refreshTokenValiditySeconds(refreshTokenValiditySeconds) .authorizedGrantTypes(authorizedGrantTypes) .scopes("read", "write") .resourceIds("api"); } @Override public void configure(final AuthorizationServerEndpointsConfigurer endpoints) { endpoints .accessTokenConverter(accessTokenConverter()) .userDetailsService(userService) .authenticationManager(authenticationManager); } @Bean JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); return converter; } }

Sonraki bölüm, kaynak sunucusuna uygulanacak yapılandırmayı açıklar.

Kaynak Sunucusu Kurulumu

Kaynak sunucu davranışı, @EnableResourceServer ek açıklaması kullanılarak etkinleştirilir ve yapılandırması ResourceServerConfiguration sınıfında bulunur.

Burada gerekli olan tek yapılandırma, önceki sınıfta tanımlanan istemci erişimiyle eşleşmesi için kaynak tanımlamasının tanımıdır.

 package net.reliqs.gleeometer.security; import org.springframework.context.annotation.Configuration; 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.ResourceServerSecurityConfigurer; @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId("api"); } }

Son yapılandırma öğesi, web uygulaması güvenliğinin tanımıyla ilgilidir.

Web Güvenliği Kurulumu

Spring web güvenlik yapılandırması, @EnableWebSecurity ek açıklaması kullanılarak etkinleştirilen ServerSecurityConfig sınıfında bulunur. @EnableGlobalMethodSecurity , yöntem düzeyinde güvenlik belirtmeye izin verir. proxyTargetClass özniteliği, bunun RestController yöntemleri için çalışmasını sağlamak için ayarlanır, çünkü denetleyiciler genellikle herhangi bir arabirim uygulamayan sınıflardır.

Aşağıdakileri tanımlar:

  • Kullanılacak kimlik authenticationProvider sağlayıcısı, beanAuthenticationProvider'ı tanımlar
  • Bean passwordEncoder tanımlayan, kullanılacak parola kodlayıcı
  • Kimlik doğrulama yöneticisi çekirdeği
  • HttpSecurity kullanılarak yayınlanan yollar için güvenlik yapılandırması
  • Standart Spring REST hata işleyicisi ResponseEntityExceptionHandler dışında hata mesajlarını işlemek için özel bir AuthenticationEntryPoint kullanımı
 package net.reliqs.gleeometer.security; import net.reliqs.gleeometer.errors.CustomAccessDeniedHandler; import net.reliqs.gleeometer.errors.CustomAuthenticationEntryPoint; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) public class ServerSecurityConfig extends WebSecurityConfigurerAdapter { private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; private final UserDetailsService userDetailsService; public ServerSecurityConfig(CustomAuthenticationEntryPoint customAuthenticationEntryPoint, @Qualifier("userService") UserDetailsService userDetailsService) { this.customAuthenticationEntryPoint = customAuthenticationEntryPoint; this.userDetailsService = userDetailsService; } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setPasswordEncoder(passwordEncoder()); provider.setUserDetailsService(userDetailsService); return provider; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/signin/**").permitAll() .antMatchers("/api/glee/**").hasAnyAuthority("ADMIN", "USER") .antMatchers("/api/users/**").hasAuthority("ADMIN") .antMatchers("/api/**").authenticated() .anyRequest().authenticated() .and().exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint).accessDeniedHandler(new CustomAccessDeniedHandler()); } }

Aşağıdaki kod özü, kaynak sahibinin kimlik doğrulamasını sağlamak için UserDetailsService arabiriminin uygulanmasıyla ilgilidir.

 package net.reliqs.gleeometer.security; import net.reliqs.gleeometer.users.User; import net.reliqs.gleeometer.users.UserRepository; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service public class UserService implements UserDetailsService { private final UserRepository repository; public UserService(UserRepository repository) { this.repository = repository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = repository.findByEmail(username).orElseThrow(() -> new RuntimeException("User not found: " + username)); GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole().name()); return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), Arrays.asList(authority)); } }

Sonraki bölüm, güvenlik kısıtlamalarının nasıl eşlendiğini görmek için bir REST denetleyici uygulamasının açıklaması hakkındadır.

REST Denetleyici

REST denetleyicisinin içinde, her kaynak yöntemi için erişim denetimi uygulamanın iki yolunu bulabiliriz:

  • Spring tarafından parametre olarak geçirilen bir OAuth2Authentication örneğini kullanma
  • @PreAuthorize veya @PostAuthorize ek açıklamalarını kullanma
 package net.reliqs.gleeometer.users; import lombok.extern.slf4j.Slf4j; import net.reliqs.gleeometer.errors.EntityNotFoundException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.ConstraintViolationException; import javax.validation.Valid; import javax.validation.constraints.Size; import java.util.HashSet; @RestController @RequestMapping("/api/users") @Slf4j @Validated class UserController { private final UserRepository repository; private final PasswordEncoder passwordEncoder; UserController(UserRepository repository, PasswordEncoder passwordEncoder) { this.repository = repository; this.passwordEncoder = passwordEncoder; } @GetMapping Page<User> all(@PageableDefault(size = Integer.MAX_VALUE) Pageable pageable, OAuth2Authentication authentication) { String auth = (String) authentication.getUserAuthentication().getPrincipal(); String role = authentication.getAuthorities().iterator().next().getAuthority(); if (role.equals(User.Role.USER.name())) { return repository.findAllByEmail(auth, pageable); } return repository.findAll(pageable); } @GetMapping("/search") Page<User> search(@RequestParam String email, Pageable pageable, OAuth2Authentication authentication) { String auth = (String) authentication.getUserAuthentication().getPrincipal(); String role = authentication.getAuthorities().iterator().next().getAuthority(); if (role.equals(User.Role.USER.name())) { return repository.findAllByEmailContainsAndEmail(email, auth, pageable); } return repository.findByEmailContains(email, pageable); } @GetMapping("/findByEmail") @PreAuthorize("!hasAuthority('USER') || (authentication.principal == #email)") User findByEmail(@RequestParam String email, OAuth2Authentication authentication) { return repository.findByEmail(email).orElseThrow(() -> new EntityNotFoundException(User.class, "email", email)); } @GetMapping("/{id}") @PostAuthorize("!hasAuthority('USER') || (returnObject != null && returnObject.email == authentication.principal)") User one(@PathVariable Long id) { return repository.findById(id).orElseThrow(() -> new EntityNotFoundException(User.class, "id", id.toString())); } @PutMapping("/{id}") @PreAuthorize("!hasAuthority('USER') || (authentication.principal == @userRepository.findById(#id).orElse(new net.reliqs.gleeometer.users.User()).email)") void update(@PathVariable Long id, @Valid @RequestBody User res) { User u = repository.findById(id).orElseThrow(() -> new EntityNotFoundException(User.class, "id", id.toString())); res.setPassword(u.getPassword()); res.setGlee(u.getGlee()); repository.save(res); } @PostMapping @PreAuthorize("!hasAuthority('USER')") User create(@Valid @RequestBody User res) { return repository.save(res); } @DeleteMapping("/{id}") @PreAuthorize("!hasAuthority('USER')") void delete(@PathVariable Long id) { if (repository.existsById(id)) { repository.deleteById(id); } else { throw new EntityNotFoundException(User.class, "id", id.toString()); } } @PutMapping("/{id}/changePassword") @PreAuthorize("!hasAuthority('USER') || (#oldPassword != null && !#oldPassword.isEmpty() && authentication.principal == @userRepository.findById(#id).orElse(new net.reliqs.gleeometer.users.User()).email)") void changePassword(@PathVariable Long id, @RequestParam(required = false) String oldPassword, @Valid @Size(min = 3) @RequestParam String newPassword) { User user = repository.findById(id).orElseThrow(() -> new EntityNotFoundException(User.class, "id", id.toString())); if (oldPassword == null || oldPassword.isEmpty() || passwordEncoder.matches(oldPassword, user.getPassword())) { user.setPassword(passwordEncoder.encode(newPassword)); repository.save(user); } else { throw new ConstraintViolationException("old password doesn't match", new HashSet<>()); } } }

Çözüm

Spring Security ve Spring Boot, eksiksiz bir OAuth2 yetkilendirme/kimlik doğrulama sunucusunu neredeyse bildirimsel bir şekilde hızlı bir şekilde kurmaya izin verir. Bu öğreticide açıklandığı gibi, OAuth2 istemcisinin özelliklerini doğrudan application.properties/yml dosyasından yapılandırarak kurulum daha da kısaltılabilir.

Tüm kaynak kodları bu GitHub deposunda mevcuttur: spring-glee-o-meter. Yayınlanan kaynakları tüketen bir Angular istemcisi bu GitHub deposunda bulunabilir: glee-o-meter.