Using Spring Boot for OAuth2 and JWT REST API Security
Hallo guys, pada tutorial ini, saya akan mempraktikan secara langsung membuat aplikasi Spring Boot yang menggunakan otentikasi JWT untuk mengamankan API REST yang terbuka. Dalam contoh ini, saya akan menggunakan user hard-coded untuk otentikasi, maksudnya yaitu saat ini belum menggunakan database. Setiap user akan dapat menggunakan API ini hanya jika ia memiliki JSON Web Token (JWT) yang valid. Untuk ilustrasi nya bisa kalian lihat pada gambar berikut ini.
Dalam tutorial ini saya tidak menjelaskan secara mendetail tentang JWT anda dapat membaca dan memahaminya sebelum melakukan praktik ini. Namun saya hanya sedikit menjelaskan tentang beberapa pengertian dari Authorization server, Resource Server, OAuth2 dan Token JWT.
Authorization server
Authorization server adalah Server yang mengeluarkan token akses setelah berhasil mengotentikasi a clientcdan resource owner, dan mengesahkan sebuah permintaan , authorization juga merupakan komponen arsitektur tertinggi untuk Keamanan API Web.
Resource Server adalah Server yang menangani permintaan yang diautentikasi setelah client memperoleh access token, atau aplikasi yang menyediakan token akses ke klien untuk mengakses Endpoint HTTP.
OAuth2
OAuth2 adalah kerangka kerja authorization yang memungkinkan aplikasi Keamanan Web untuk mengakses sumber daya dari klien.
Token JWT
JWT Token adalah JSON Web Token, yang digunakan untuk mewakili pihak yang diklaim yang diamankan antara dua pihak. Anda dapat mempelajari lebih lanjut tentang token JWT di www.jwt.io/
Untuk pemahaman yang lebih baik sedikit rangkuman tentang apa saja yang akan kita lakukan di tutorial ini :
1. mendevelop aplikasi Spring Boot yang dengan REST GET API dengan perintah /hello.
2. Konfigurasikan Spring Security untuk JWT. Membuka REST POST API dengan mapping/otentikasi menggunakan Pengguna mana yang akan mendapatkan Token Web JSON yang valid. Dan kemudian, izinkan usermengakses API /hello hanya jika memiliki token yang valid.
1. mendevelop aplikasi Spring Boot yang dengan REST GET API dengan perintah /hello.
2. Konfigurasikan Spring Security untuk JWT. Membuka REST POST API dengan mapping/otentikasi menggunakan Pengguna mana yang akan mendapatkan Token Web JSON yang valid. Dan kemudian, izinkan usermengakses API /hello hanya jika memiliki token yang valid.
Baikilah langsung saja sekarang kita akan membangun aplikasi OAuth2 yang memungkinkan penggunaan Resource Server dan Authorization server dengan bantuan Token JWT.
Kemudian coba jalankan project lewat postman atau lewat browser : localhost:8080/hello
Tambahkan depedency di project maven.
Berikut struktur project yang akan kita buat.
Buat file project java spring baru di laptop kalian.
Kemudian coba buat sebuah class controller
Controller.java
package com.enigma.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
@RequestMapping({ "/hello" })
public String firstPage() {
return "Hello World";
}
}
Tambahan port server di application.properties.
server.port=8080
SPRING SECURITY DAN JWT CONFIGURATION
membuat JWT - membuka POST API dengan mapping/otentikasi . Ketika melewati nama pengguna dan kata sandi yang benar, itu akan menghasilkan JSON Web Token (JWT).
Memvalidasi JWT - Jika pengguna mencoba mengakses GET API dengan mapping /hello . Ini akan memungkinkan akses hanya jika permintaan memiliki Token Web JSON (JWT) yang valid
membuat JWT - membuka POST API dengan mapping/otentikasi . Ketika melewati nama pengguna dan kata sandi yang benar, itu akan menghasilkan JSON Web Token (JWT).
Memvalidasi JWT - Jika pengguna mencoba mengakses GET API dengan mapping /hello . Ini akan memungkinkan akses hanya jika permintaan memiliki Token Web JSON (JWT) yang valid
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
Tambahkan code ini di application.properties
jwt.secret=enigma
JwtTokenUtil
JwtTokenUtil bertanggung jawab untuk melakukan operasi JWT
seperti pembuatan dan validasi. Ini menggunakan io.jsonwebtoken.Jwts untuk
mencapai ini.
Buat package config
dan buat file JwtTokenUtil.java
package com.enigma.config;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -2550185165626007488L;
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
@Value("${jwt.secret}")
private String secret;
//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
//retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
//for retrieveing any information from token we will need the secret key
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
//check if the token has expired
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
//generate token for user
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
//while creating the token -
//1. Define claims of the token, like Issuer, Expiration, Subject, and the ID
//2. Sign the JWT using the HS512 algorithm and secret key.
//3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
// compaction of the JWT to a URL-safe string
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
//validate token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
JwtUserDetailsSevice
Jwt userdetailservice digunakan untuk mengambil data username
dan password dari database namun pada praktik kali ini masih belum menggunakan
database , untuk tutorial selanjutnya kita akan mennggunakan database untuk
mengambil data user dan password
Buat package service
dan buat file JwtUserDetailsService.java
Jngan lupa Implements UserDetailsService dari spring security
Info : username = javainuse
password = password
package com.enigma.service;
import java.util.ArrayList;
import org.springframework.security.core.userdetails.User;
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 JwtUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("javainuse".equals(username)) {
return new User("javainuse", "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6",
new ArrayList<>());
} else {
throw new UsernameNotFoundException("User not found with username: " + username);
}
}
}
JwtAuthenticationController
membuka POST API /otentikasi menggunakan
JwtAuthenticationController. API POST berfungsi mendapatkan username dan password dalam body- Menggunakan Spring Authentication Manager kami mengotentikasi username dan password. Jika credintial valid, token JWT dibuat menggunakan
JWTTokenUtil dan diberikan kepada client.
Buat package Controller dan buat file baru
JwtAuthenticationController.java
package com.enigma.controller;
import java.util.Objects;
import com.enigma.config.JwtTokenUtil;
import com.enigma.model.JwtRequest;
import com.enigma.model.JwtResponse;
import com.enigma.service.JwtUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin
public class JwtAuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private JwtUserDetailsService userDetailsService;
@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}
JwtRequest
class ini diperlukan untuk menyimpan username dan password yang diterima dari client.
Buat package model
dan buat file baru JwtRequest.java
package com.enigma.model;
import java.io.Serializable;
public class JwtRequest implements Serializable {
private static final long serialVersionUID = 5926468583005150707L;
private String username;
private String password;
//need default constructor for JSON Parsing
public JwtRequest()
{
}
public JwtRequest(String username, String password) {
this.setUsername(username);
this.setPassword(password);
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
JwtResponse
class ini diperlukan untuk membuat respons yang berisi JWT
untuk dikembalikan kepada user.
Buat file baru
JwtResponse.java di package model
package com.enigma.model;
import java.io.Serializable;
public class JwtResponse implements Serializable {
private static final long serialVersionUID = -8091879091924046844L;
private final String jwttoken;
public JwtResponse(String jwttoken) {
this.jwttoken = jwttoken;
}
public String getToken() {
return this.jwttoken;
}
}
JwtRequestFilter
Untuk setiap permintaan masuk, kelas Filter ini dijalankan.
Ia memeriksa apakah permintaan memiliki token JWT yang valid. Jika memiliki
Token JWT yang valid maka ia menetapkan Otentikasi dalam konteks, untuk
menentukan bahwa pengguna saat ini diautentikasi.
Buat package config
dan buat file baru bernama JwtRequestFilter.java
package com.enigma.config;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.enigma.service.JwtUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import io.jsonwebtoken.ExpiredJwtException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set
// authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the
// Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
JwtAuthenticationEntryPoint
Class ini akan menolak setiap permintaan yang tidak
diautentikasi dan mengirim kode kesalahan 401
Buat file
JwtAuthenticationEntryPoint.java di package config
package com.enigma.config;
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -7858869558953243875L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
WebSecurityConfig
Buat file WebSecurityConfig.java di package config
package com.enigma.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private UserDetailsService jwtUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/authenticate").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Untuk memulai project Spring Aplication :
- Generate Json WebToken
- Jalankan localhost:8080/authenticate kemudian masukan body : username dan password ,
- Maka akan return sebuah token
Kemudian validation token di header untuk mengakses
localhost:8080/hello
Selamat mencoba ^-^
0 Response to "Using Spring Boot for OAuth2 and JWT REST API Security"
Post a Comment