Menggunakan JWT dengan Spring Security OAuth

1. Ikhtisar

Dalam tutorial ini, kita akan membahas cara mendapatkan implementasi Spring Security OAuth2 untuk menggunakan JSON Web Token.

Kami juga terus mengembangkan artikel Spring REST API + OAuth2 + Angular dalam seri OAuth ini.

2. Server Otorisasi OAuth2

Sebelumnya, tumpukan OAuth Keamanan Musim Semi menawarkan kemungkinan untuk menyiapkan Server Otorisasi sebagai Aplikasi Musim Semi. Kami kemudian harus mengkonfigurasinya untuk menggunakan JwtTokenStore sehingga kami dapat menggunakan token JWT.

Namun, tumpukan OAuth sudah tidak digunakan lagi oleh Spring dan sekarang kami akan menggunakan Keycloak sebagai Server Otorisasi kami.

Jadi kali ini, kami akan menyiapkan Server Otorisasi kami sebagai server Keycloak tertanam di aplikasi Spring Boot . Ini mengeluarkan token JWT secara default, jadi tidak perlu konfigurasi lain dalam hal ini.

3. Server Sumber Daya

Sekarang, mari kita lihat cara mengkonfigurasi Server Sumber Daya kita untuk menggunakan JWT.

Kami akan melakukan ini dalam file application.yml :

server: port: 8081 servlet: context-path: /resource-server spring: security: oauth2: resourceserver: jwt: issuer-uri: //localhost:8083/auth/realms/baeldung jwk-set-uri: //localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs

JWT menyertakan semua informasi di dalam Token. Jadi Resource Server perlu memverifikasi tanda tangan Token untuk memastikan datanya belum dimodifikasi. The JWK-set-uri properti berisi kunci publik bahwa server dapat digunakan untuk tujuan ini .

Properti penerbit-uri menunjuk ke URI Server Otorisasi dasar, yang juga dapat digunakan untuk memverifikasi klaim iss , sebagai tindakan keamanan tambahan.

Selain itu, jika properti jwk-set-uri tidak disetel, Server Sumber Daya akan mencoba menggunakan penerbit-ui untuk menentukan lokasi kunci ini, dari titik akhir metadata Server Otorisasi.

Yang penting, menambahkan properti penerbit-uri mengamanatkan bahwa kita harus menjalankan Server Otorisasi sebelum kita dapat memulai aplikasi Server Sumber Daya .

Sekarang mari kita lihat bagaimana kita dapat mengkonfigurasi dukungan JWT menggunakan konfigurasi Java:

@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors() .and() .authorizeRequests() .antMatchers(HttpMethod.GET, "/user/info", "/api/foos/**") .hasAuthority("SCOPE_read") .antMatchers(HttpMethod.POST, "/api/foos") .hasAuthority("SCOPE_write") .anyRequest() .authenticated() .and() .oauth2ResourceServer() .jwt(); } }

Di sini, kami mengganti konfigurasi Keamanan Http default. Jadi kita perlu menentukan secara eksplisit bahwa kita ingin ini berperilaku sebagai Resource Server dan bahwa kita akan menggunakan Access Token berformat JWT menggunakan metode oauth2ResourceServer () dan jwt () masing-masing.

Konfigurasi JWT di atas adalah apa yang disediakan oleh instance Spring Boot default. Ini juga dapat disesuaikan seperti yang akan kita lihat sebentar lagi.

4. Klaim Kustom di Token

Sekarang mari kita siapkan beberapa infrastruktur untuk dapat menambahkan beberapa klaim khusus di Access Token yang dikembalikan oleh Server Otorisasi . Klaim standar yang disediakan oleh kerangka kerja semuanya baik dan bagus, tetapi sebagian besar waktu kami memerlukan beberapa informasi tambahan dalam token untuk digunakan di sisi Klien.

Mari kita ambil contoh klaim khusus, organisasi , yang akan berisi nama organisasi pengguna tertentu.

4.1. Konfigurasi Server Otorisasi

Untuk ini, kita perlu menambahkan beberapa konfigurasi ke file definisi realm kita, baeldung-realm.json :

  • Tambahkan organisasi atribut ke pengguna kami [dilindungi email] :
    "attributes" : { "organization" : "baeldung" },
  • Tambahkan protocolMapper yang disebut organisasi ke konfigurasi jwtClient :
    "protocolMappers": [{ "id": "06e5fc8f-3553-4c75-aef4-5a4d7bb6c0d1", "name": "organization", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", "user.attribute": "organization", "id.token.claim": "true", "access.token.claim": "true", "claim.name": "organization", "jsonType.label": "String" } }],

Untuk penyiapan Keycloak mandiri, ini juga dapat dilakukan menggunakan konsol Admin.

Selain itu, penting untuk diingat bahwa konfigurasi JSON di atas khusus untuk Keycloak, dan dapat berbeda untuk server OAuth lainnya .

Dengan konfigurasi baru ini dan berjalan, kita akan mendapatkan atribut ekstra organization = baeldung , dalam token payload untuk [email protected] :

{ jti: "989ce5b7-50b9-4cc6-bc71-8f04a639461e" exp: 1585242462 nbf: 0 iat: 1585242162 iss: "//localhost:8083/auth/realms/baeldung" sub: "a5461470-33eb-4b2d-82d4-b0484e96ad7f" typ: "Bearer" azp: "jwtClient" auth_time: 1585242162 session_state: "384ca5cc-8342-429a-879c-c15329820006" acr: "1" scope: "profile write read" organization: "baeldung" preferred_username: "[email protected]" }

4.2. Gunakan Token Akses di Klien Angular

Selanjutnya, kami ingin menggunakan informasi Token di aplikasi Klien Angular kami. Kami akan menggunakan perpustakaan angular2-jwt untuk itu.

Kami akan menggunakan klaim organisasi di AppService kami , dan menambahkan fungsi getOrganization :

getOrganization(){ var token = Cookie.get("access_token"); var payload = this.jwtHelper.decodeToken(token); this.organization = payload.organization; return this.organization; }

Fungsi ini memanfaatkan JwtHelperService dari pustaka angular2-jwt untuk mendekode Token Akses dan mendapatkan klaim khusus kami. Sekarang yang perlu kita lakukan adalah menampilkannya di AppComponent kita :

@Component({ selector: 'app-root', template: ` Spring Security Oauth - Authorization Code 

{{organization}}

` }) export class AppComponent implements OnInit { public organization = ""; constructor(private service: AppService) { } ngOnInit() { this.organization = this.service.getOrganization(); } }

5. Akses Klaim Ekstra di Resource Server

Tapi, bagaimana kita bisa mengakses informasi itu dari sisi Resource Server?

5.1. Akses Klaim Server Otentikasi

Itu benar-benar sederhana: kita hanya perlu ekstrak dari org.springframework.security.oauth2.jwt.Jwt 's AuthenticationPrincipal , seperti yang kita akan lakukan untuk setiap atribut lainnya di UserInfoController :

@GetMapping("/user/info") public Map getUserInfo(@AuthenticationPrincipal Jwt principal) { Map map = new Hashtable(); map.put("user_name", principal.getClaimAsString("preferred_username")); map.put("organization", principal.getClaimAsString("organization")); return Collections.unmodifiableMap(map); } 

5.2. Konfigurasi untuk Menambah / Menghapus / Mengganti Nama Klaim

Sekarang, bagaimana jika kita ingin menambahkan lebih banyak klaim di sisi Resource Server? Atau hapus atau ubah nama beberapa?

Katakanlah kita ingin mengubah klaim organisasi yang masuk dari Server Otentikasi untuk mendapatkan nilai dalam huruf besar. Selain itu, jika klaim tidak ada pada pengguna, kita perlu menyetel nilainya sebagai tidak diketahui .

Untuk mencapai ini, pertama, kita harus menambahkan kelas yang mengimplementasikan antarmuka Konverter dan menggunakan MappedJwtClaimSetConverter untuk mengonversi klaim :

public class OrganizationSubClaimAdapter implements Converter
    
      { private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap()); public Map convert(Map claims) { Map convertedClaims = this.delegate.convert(claims); String organization = convertedClaims.get("organization") != null ? (String) convertedClaims.get("organization") : "unknown"; convertedClaims.put("organization", organization.toUpperCase()); return convertedClaims; } }
    

Kedua, di kelas SecurityConfig kita, kita perlu menambahkan instance JwtDecoder kita sendiri untuk menimpa yang disediakan oleh Spring Boot dan menyetel OrganizationSubClaimAdapter sebagai konverter klaimnya :

@Bean public JwtDecoder customDecoder(OAuth2ResourceServerProperties properties) { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri( properties.getJwt().getJwkSetUri()).build(); jwtDecoder.setClaimSetConverter(new OrganizationSubClaimAdapter()); return jwtDecoder; } 

Sekarang ketika kita menekan / user / info API kita untuk pengguna [email dilindungi] , kita akan mendapatkan organisasi sebagai TIDAK DIKETAHUI .

Perhatikan bahwa menimpa kacang JwtDecoder default yang dikonfigurasi oleh Spring Boot harus dilakukan dengan hati-hati untuk memastikan semua konfigurasi yang diperlukan masih disertakan.

6. Memuat Kunci Dari Java Keystore

Dalam konfigurasi kami sebelumnya, kami menggunakan kunci publik default Server Otorisasi untuk memverifikasi integritas token kami.

Kita juga bisa menggunakan keypair dan sertifikat yang disimpan dalam file Java Keystore untuk melakukan proses penandatanganan.

6.1. Hasilkan JKS Java KeyStore File

Let's first generate the keys – and more specifically a .jks file – using the command line tool keytool:

keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass

The command will generate a file called mytest.jks which contains our keys – the Public and Private keys.

Also make sure keypass and storepass are the same.

6.2. Export Public Key

Next, we need to export our Public key from generated JKS, we can use the following command to do so:

keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey

A sample response will look like this:

-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp OsMquZMyOyteO2RsVeMLF/hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2 /5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo/KHk4nz+Fa6P3L8+L90E/3qwf6j3 DKWnAgJFRY8AbSYXt1d5ELiIG1/gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR xxCLXT+tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43+a9Qn5icSRnDfTAesQ3Cr lAWJKl2kcWU1HwJqw+dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK eQIDAQAB -----END PUBLIC KEY----- -----BEGIN CERTIFICATE----- MIIDCzCCAfOgAwIBAgIEGtZIUzANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJ1 czELMAkGA1UECBMCY2ExCzAJBgNVBAcTAmxhMQ0wCwYDVQQDEwR0ZXN0MB4XDTE2 MDMxNTA4MTAzMFoXDTE2MDYxMzA4MTAzMFowNjELMAkGA1UEBhMCdXMxCzAJBgNV BAgTAmNhMQswCQYDVQQHEwJsYTENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAICCtlreMdhLQ5eNQu736TrDKrmTMjsrXjtkbFXj Cxf4VyHmL4nCq9EkM1ZKHRxAQjIhl0A8+aa4o06t0Rz8tv+ViQQmKu8h4Ey77KTM urIr1zezXWBOyOaV6Pyh5OJ8/hWuj9y/Pi/dBP96sH+o9wylpwICRUWPAG0mF7dX eRC4iBtf4BKswtH2ZjYYX6wbccFl65aVA09Cn739EFZj0ccQi10/rRHtbHlhhKnj iy+b10S6ps2XAXtUWfZEEJuN/mvUJ+YnEkZw30wHrENwq5QFiSpdpHFlNR8CasPn WUUmdV+JBFzTMsz3TwWxplOjB3YacsCO0imU+5l+AQ51CnkCAwEAAaMhMB8wHQYD VR0OBBYEFOGefUBGquEX9Ujak34PyRskHk+WMA0GCSqGSIb3DQEBCwUAA4IBAQB3 1eLfNeq45yO1cXNl0C1IQLknP2WXg89AHEbKkUOA1ZKTOizNYJIHW5MYJU/zScu0 yBobhTDe5hDTsATMa9sN5CPOaLJwzpWV/ZC6WyhAWTfljzZC6d2rL3QYrSIRxmsp /J1Vq9WkesQdShnEGy7GgRgJn4A8CKecHSzqyzXulQ7Zah6GoEUD+vjb+BheP4aN hiYY1OuXD+HsdKeQqS+7eM5U7WW6dz2Q8mtFJ5qAxjY75T0pPrHwZMlJUhUZ+Q2V FfweJEaoNB9w9McPe1cAiE+oeejZ0jq0el3/dJsx3rlVqZN+lMhRJJeVHFyeb3XF lLFCUGhA7hxn2xf3x1JW -----END CERTIFICATE-----

6.3. Maven Configuration

Next, we don't want the JKS file to be picked up by the maven filtering process – so we'll make sure to exclude it in the pom.xml:

   src/main/resources true  *.jks    

If we're using Spring Boot, we need to make sure that our JKS file is added to application classpath via the Spring Boot Maven Plugin – addResources:

   org.springframework.boot spring-boot-maven-plugin  true    

6.4. Authorization Server

Now, we will configure Keycloak to use our Keypair from mytest.jks, by adding it to the realm definition JSON file's KeyProvider section as follows:

{ "id": "59412b8d-aad8-4ab8-84ec-e546900fc124", "name": "java-keystore", "providerId": "java-keystore", "subComponents": {}, "config": { "keystorePassword": [ "mypass" ], "keyAlias": [ "mytest" ], "keyPassword": [ "mypass" ], "active": [ "true" ], "keystore": [ "src/main/resources/mytest.jks" ], "priority": [ "101" ], "enabled": [ "true" ], "algorithm": [ "RS256" ] } },

Here we have set the priority to 101, greater than any other Keypair for our Authorization Server, and set active to true. This is done to ensure that our Resource Server would pick this particular Keypair from the jwk-set-uri property we specified earlier.

Sekali lagi, konfigurasi ini khusus untuk Keycloak dan mungkin berbeda untuk implementasi Server OAuth lainnya.

7. Kesimpulan

Dalam artikel singkat ini kami berfokus pada penyiapan proyek Spring Security OAuth2 kami untuk menggunakan Token Web JSON.

Implementasi lengkap dari tutorial ini dapat ditemukan di lebih dari GitHub.