0% found this document useful (0 votes)
252 views119 pages

Spring Security Intro

Uploaded by

Teddy Koesendra
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
Download as docx, pdf, or txt
0% found this document useful (0 votes)
252 views119 pages

Spring Security Intro

Uploaded by

Teddy Koesendra
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1/ 119

Daftar ISI

Daftar ISI..............................................................................................................2
1. Spring Boot Security......................................................................................6
2. Apa perbedaan utama antara otentikasi JWT dan OAuth?.............................8
3. JWT (JSON Web Token)...............................................................................9
4. Cara kerja JWT.............................................................................................11
a. Spring Security JWT in Spring Boot 2..............................................................11
5. Contoh dan Implementasi.............................................................................14
a. Pom.XML.........................................................................................................14
b. Entitas...............................................................................................................15
c. Repository.........................................................................................................19
d. Service..............................................................................................................20
e. Configurasi Security.........................................................................................24
f. Run and Testing with Postman..........................................................................26
g. Gitlab Project....................................................................................................28
6. Register.........................................................................................................28
a. Register Manual................................................................................................28
b. Register By OTP...............................................................................................31
c. Konfirmasi OTP Token User: guna untuk mengupdate user agar dapat login...37
7. Forget Password...........................................................................................38
a. Forget Password OTP Email............................................................................38
b. Test Postman.....................................................................................................43
8. Filter And Sorting.........................................................................................44
9. File Handling................................................................................................47
a. Upload File.......................................................................................................47
b. Service api.........................................................................................................48
c. Revisi Kode Program di class FileController....................................................54
d. Tambahkan pada class main..............................................................................55
e. Testing..............................................................................................................56
Upload 1 file.....................................................................................................................56

2
Upload many file..............................................................................................................57
Download file...................................................................................................................58
f. Download file tampa download / show file only...............................................58
10. Deploy Project ke Heroku.........................................................................60
a. Buat akun Heroku.............................................................................................60
b. Buat Akun Github.............................................................................................60
c. Masuk Ke akun Heroku, untuk deploy aplikasi.................................................60
d. Membuat database di heroku.............................................................................64
e. Membuat env dan staging di spring boot...........................................................66
f. Tambahkan pada pom.xml................................................................................67
g. Aotomatic deploy heroku..................................................................................67
11. Register OTP by URI Tymeleaf...................................................................70
a. Step 1-Controller...............................................................................................70
b. Step 2- Apllication.properties...........................................................................71
c. Step 3- Controller Tymeleaf..............................................................................72
d. Step 4- UI Tymeleaf..........................................................................................73
e. Test Run Postman.............................................................................................74
12. CronJob Scheduler....................................................................................75
a. Step 1- Application Properties..........................................................................75
b. Config...............................................................................................................76
c. Main Application..............................................................................................77
d. Cronjob Example..............................................................................................78
13. Download image dari gambar kemudian simpan ke local........................78
14. Docker spring boot....................................................................................78
a. Step 1................................................................................................................79
b. Step 2 : .env......................................................................................................79
c. Step 3 : docker-compose.yml............................................................................79
d. Step 4: run and noted........................................................................................82
a. docker-compose up -d : menajalankan semua yang semuar servise.................82
b. docker-compose up spring : jalankan image spring saja...................................82
c. docker-compose images / docker-compose ps : show all image yg aktif.........82
d. docker-compose down -v : mematikan semua service yang ada.......................82
3
e. docker-compose ps : melihart image yang sedang aktif....................................82
f. remote images : https://stackoverflow.com/questions/37971961/docker-error-
bind-address-already-in-use pass: 12345................................................................82
g. docker-compose logs : melihat logs..................................................................82
h. docker-compose logs -f postgres : lebih detail , melihat logs khusu container
service spring...........................................................................................................82
i. jalankan postgresdi docker secara manual https://towardsdatascience.com/local-
development-set-up-of-postgresql-with-docker-c022632f13ea................................82
j. docker-compose run -d \--name dev-postgres \-e
POSTGRES_PASSWORD=Pass2020! \-v
${HOME}/postgres-data/:/var/lib/postgresql/data \-p 5432:5432 binar...................82
15. Send File To Email....................................................................................83
a. Step 1 – strukture..............................................................................................83
b. Step 2 :code.......................................................................................................83
c. Step 3 : pom.xml and application.properties.....................................................86
d. Step 4: resouces.................................................................................................86
e. Step 5 : Testing By Junit...................................................................................88
16. Login with username and password only..................................................89
a. Step 1- pom.xml................................................................................................89
b. Step 2- buatlah class userservice.......................................................................90
c. Step 3- user impl...............................................................................................90
d. Model – request.................................................................................................92
e. Config-allow uri................................................................................................93
f. Controller..........................................................................................................93
g. Testing..............................................................................................................94
17. Refresh TOKEN........................................................................................95
a. Step 1- lakukan login........................................................................................95
b. Step 2- to do refresh token................................................................................96
18. Upload dan simpan barang satu enpoind...................................................96
c. Output...............................................................................................................98
19. Validation Password saat register.............................................................98
a. Step 1- Pom.xml................................................................................................98
b. Step 2-application properties.............................................................................99
4
c. Step 3- struktur kode.......................................................................................100
d. Step 3 a – setting Character Password dimana?..............................................106
e. Step 4 – cara menggunakan method validation password ?.............................106
f. Tester..............................................................................................................107
20. Menampilkan eror validation hibernite...................................................108
a. Strukture..........................................................................................................108
b. Controller........................................................................................................110
c. Entity...............................................................................................................110
d. Testing............................................................................................................111
21. Valid email manual.................................................................................111
22. Anotasi hibernite.....................................................................................111
a. @Max @Min :Batasin Request maksimal 5 huruf..........................................111
b. @Size Batasin Request maksimal 5 huruf.......................................................112
Daftar Pustaka...................................................................................................113

5
a. Spring Boot Security
Spring security merupakan fitur dari framework spring. Spring security
memungkinkan developer untuk mengintegrasikan fitur keamanan pada
aplikasi Java Web dengan cara melakukan hijacking pada HTTP request
menggunakan filter yang melakukan pengecekan keamanan.

Perhatikan gambar berikut :

6
Gambar Proses Spring security

Berikut Penjelasan gambar diatas Penjelasan Spring Security


 WebSecurityConfigurerAdapter merupakan bagian utama dari
implementasi Spring security. Dia menyediakan
konfigurasi HttpSecurity  untuk mengatur cors, csrf, session
management, rules untuk dapat melindungi berbagai
sumberdaya/informasi di aplikasi kita. Kita bisa meng-extend dan
meng-customize konfigurasi bawaannya yang terdiri dari element-
element dibawah ini.
 UserDetailsService merupakan sebuah interface yang memiliki
sebuah method untuk mengambil User berdasarkan username dan
mengembalikan objek UserDetails yang mana Spring Security akan
menggunaknnya untuk melakukan authentication dan validation.
 UserDetails berisi informasi yang diperukan seperti: username,
password, authorities, untuk digunakan sebagai Authentication object.
 UsernamePasswordAuthenticationToken untuk mendapatkan
username dan password dari request yang dikirim oleh form
login , AuthenticationManager akan selanjutnya menggunakan ini
untuk meng-authenticate akun yang hendak login.
 AuthenticationManager memiliki
sebuah DaoAuthenticationProvider (dibantu

7
oleh  UserDetailsService & PasswordEncoder) untuk
memvalidasi object UsernamePasswordAuthenticationToken . Jika
baerhasil, AuthenticationManager akan mengembalikan data dari
object Authentication (termasuk didalamnya granted authorities).
 OncePerRequestFilter melakukan sebuah single execution untuk
setiap request pada API. Ini memberikan sebuah method bernama
doFilterInternal() method yang akan  mengimplementasikan proses
parsing & validasi JWT, mengambil User details
(menggunakan UserDetailsService), mengecek Authorizaion
(menggunakan UsernamePasswordAuthenticationToken).
 AuthenticationEntryPoint untuk menangkap error pada saat
authentication.
 Repository terdiri dari UserRepository & RoleRepository untuk
bekerja dengan Database, akan di import dan digunakan di Controller.
 Controller menerima & menangani request setelah request difilter
oleh OncePerRequestFilter.
 AuthController menangani requests di proses signup dan login. 
 TestController kita gunakan untuk mengecek dan menguji hak akses.

b. Apa perbedaan utama antara otentikasi JWT dan OAuth?


OAuth 2.0 mendefinisikan protokol, yaitu menentukan bagaimana token
ditransfer, JWT mendefinisikan format token.
OAuth 2.0 dan "otentikasi JWT" memiliki penampilan yang mirip ketika
sampai pada tahap (ke-2) di mana Klien menyajikan token ke Server
Sumber Daya: token diteruskan dalam header. 
Tetapi "otentikasi JWT" bukan standar dan tidak
menentukan bagaimana Klien mendapatkan token di tempat pertama
(tahap 1). Di situlah kompleksitas yang dirasakan dari OAuth berasal: itu
juga mendefinisikan berbagai cara di mana Klien
dapat memperoleh token akses dari sesuatu yang disebut Server
Otorisasi. 
Jadi perbedaan sebenarnya adalah bahwa JWT hanyalah format token,
OAuth 2.0 adalah protokol (bahwa boleh menggunakan JWT sebagai
format token).

8
c. JWT (JSON Web Token)
WT populer untuk Otentikasi dan Pertukaran Informasi. Server
mengkodekan data ke dalam JSON Web Token dan mengirimkannya ke
Klien. Klien menyimpan JWT, kemudian setiap Permintaan dari Klien ke
rute atau sumber daya yang dilindungi harus dilampirkan JWT itu
(biasanya di header). Server akan memvalidasi JWT itu dan
mengembalikan Response.

The Client typically attact JWT in Authorization header with Bearer


prefix:
Authorization: Bearer [header].[payload].[signature]

Pada gambar di atas, ketika pengguna masuk ke situs web, Server akan


menghasilkan Sessionuntuk pengguna itu dan menyimpannya (di
Memori atau Basis Data). Server juga mengembalikan
a SessionIduntuk Klien untuk menyimpannya di Cookie Browser .

9
Sesi di Server memiliki waktu kedaluwarsa. Setelah waktu itu, Sesi ini
telah kedaluwarsa dan pengguna harus masuk kembali untuk membuat
Sesi lain.
Jika pengguna telah masuk dan Sesi belum kedaluwarsa, Cookie
(termasuk SessionId) selalu berjalan dengan semua Permintaan HTTP ke
Server. Server akan membandingkan ini SessionIddengan yang
disimpan Sessionuntuk mengotentikasi dan mengembalikan Respons
yang sesuai.
Tidak apa-apa. Tetapi mengapa kita membutuhkan Otentikasi Berbasis
Token ?
Jawabannya adalah kami tidak hanya memiliki situs web, ada banyak
platform di sana.
Asumsikan bahwa kita memiliki situs web yang bekerja dengan baik
dengan Session. Suatu hari, kami ingin menerapkan sistem untuk Seluler
(Aplikasi Asli) dan menggunakan Database yang sama dengan aplikasi
Web saat ini. Apa yang harus kita lakukan? Kami tidak dapat
mengautentikasi pengguna yang menggunakan Aplikasi Asli
menggunakan Otentikasi Berbasis Sesi karena jenis ini tidak memiliki
Cookie.
Haruskah kita membangun proyek backend lain yang mendukung
Aplikasi Asli?
Atau haruskah kita menulis modul Otentikasi untuk pengguna Aplikasi
Asli?
Itu sebabnya Otentikasi berbasis Token lahir.
Dengan metode ini, status login pengguna dikodekan ke dalam JSON
Web Token (JWT) oleh Server dan dikirim ke Klien. Saat ini banyak
RESTful API yang menggunakannya. Mari kita pergi ke bagian
berikutnya, kita akan tahu cara kerjanya.

10
d. Cara kerja JWT

Anda dapat melihat bahwa itu sederhana untuk dipahami. Alih-alih


membuat a Session, Server menghasilkan JWTdari data login pengguna
dan mengirimkannya ke Klien . Klien menyimpan JWTdan mulai
sekarang, setiap Permintaan dari Klien harus dilampirkan JWT(biasanya
di header ). Server akan memvalidasi JWT dan mengembalikan
Response.
Untuk menyimpan JWT di sisi Klien, itu tergantung pada platform yang
Anda gunakan:

e. Spring Security JWT in Spring Boot 2


This is diagram for Spring Security/JWT classes that are separated
into 3 layers:
– HTTP
– Spring Security
– REST API

11
Lihat diagram di atas, kita dapat dengan mudah mengaitkan
komponen-komponen ini dengan proses Spring Security
Authentication: menerima permintaan HTTP, filter, authenticate,
store Authentication data, generate token, get User details, authorize,
handle exception
At a glance:
– SecurityContextHolder menyediakan akses ke SecurityContext.
– SecurityContext menyimpan Otentikasi dan mungkin meminta
informasi keamanan khusus.
– Authentication mewakili prinsipal yang mencakup
GrantedAuthority yang mencerminkan izin seluruh aplikasi yang
diberikan kepada prinsipal.
– UserDetails berisi informasi yang diperlukan untuk membangun
objek Otentikasi dari DAO atau sumber data keamanan lainnya
– JwtAuthTokenFilter (extends OncePerRequestFilter) pra-proses
permintaan HTTP, dari Token, buat Otentikasi dan isi ke
SecurityContext.
– JwtProvider memvalidasi, mem-parsing token String atau
menghasilkan String token dari UserDetails.
– UsernamePasswordAuthenticationToken mendapatkan nama
pengguna/kata sandi dari Permintaan masuk dan digabungkan
menjadi turunan dari antarmuka Otentikasi.
– AuthenticationManager uses DaoAuthenticationProvider (with help

12
of UserDetailsService & PasswordEncoder) to validate instance
of UsernamePasswordAuthenticationToken, then returns a fully
populated Authentication instance on successful authentication.
– SecurityContext is established by
calling SecurityContextHolder.getContext().setAuthentication(…) wit
h returned authentication object above.
– AuthenticationEntryPoint handles AuthenticationException.
– Access to Restful API is protected by HTTPSecurity and authorized
with Method Security Expressions.

13
f. Contoh dan Implementasi
Gitlab :
https://gitlab.com/binarxgrab_mainbootcamp/backend_java/boilerplate_bej
Branch : 04032022-spring-security
Kita ingin menambahkan security dengan jwt token pada project
sebelumnya,
Berikut langkah-langkah nya.

a. Pom.XML
Silahkan Tambahkan pada pom.xml:
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<oauth2.version>2.0.14.RELEASE</oauth2.version>
<jwt.version>1.0.9.RELEASE</jwt.version>
</properties>

….
<!-- JWT-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${oauth2.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

14
b. Entitas
Silahkan tambahkan class dibawah ini :

Gambar Class Entitas

package com.binar.demo.model.oauth;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import
org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

@Entity
@Table(name = "oauth_user")
public class User implements UserDetails, Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

15
@Column(length = 100,unique=true)
private String username;

@Column(length = 100,unique=true)
private String username1;

@Column(length = 100, nullable = true)


private String fullname;

@JsonIgnore
private String password;

@JsonIgnore
private String verifyToken;

@JsonIgnore
private Date expiredVerifyToken;

@Column(length = 100, nullable = true)


private String otp;

private Date otpExpiredDate;

@JsonIgnore
private boolean enabled = true;

@JsonIgnore
@Column(name = "not_expired")
private boolean accountNonExpired = true;

@JsonIgnore
@Column(name = "not_locked")
private boolean accountNonLocked = true;

@JsonIgnore
@Column(name = "credential_not_expired")
private boolean credentialsNonExpired = true;

@ManyToMany(targetEntity = Role.class, cascade =


CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(
name = "oauth_user_role",
joinColumns = {
@JoinColumn(name = "user_id")
},
inverseJoinColumns = {
@JoinColumn(name = "role_id")
}
)
private List<Role> roles = new ArrayList<>();

public Long getId() {


return id;
}

public void setId(Long id) {


this.id = id;
}

16
public List<Role> getRoles() {
return roles;
}

public void setRoles(List<Role> roles) {


this.roles = roles;
}

@Override
public Collection<? extends GrantedAuthority>
getAuthorities() {
return this.roles;
}

@Override
public String getPassword() {
return password;
}

public void setPassword(String password) {


this.password = password;
}

public void setUsername(String username) {


this.username = username;
}

@Override
public String getUsername() {
return username;
}

@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}

public void setAccountNonExpired(boolean


accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}

@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}

public void setAccountNonLocked(boolean accountNonLocked)


{
this.accountNonLocked = accountNonLocked;
}

@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}

public void setCredentialsNonExpired(boolean

17
credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}

public void setEnabled(boolean enabled) {


this.enabled = enabled;
}

@Override
public boolean isEnabled() {
return enabled;
}

public String getUsername1() {


return username1;
}

public void setUsername1(String username1) {


this.username1 = username1;
}

public String getFullname() {


return fullname;
}

public void setFullname(String fullname) {


this.fullname = fullname;
}

public String getVerifyToken() {


return verifyToken;
}

public void setVerifyToken(String verifyToken) {


this.verifyToken = verifyToken;
}

public Date getExpiredVerifyToken() {


return expiredVerifyToken;
}

public void setExpiredVerifyToken(Date expiredVerifyToken)


{
this.expiredVerifyToken = expiredVerifyToken;
}

public String getOtp() {


return otp;
}

public void setOtp(String otp) {


this.otp = otp;
}

public Date getOtpExpiredDate() {


return otpExpiredDate;
}

public void setOtpExpiredDate(Date otpExpiredDate) {

18
this.otpExpiredDate = otpExpiredDate;
}
}

c. Repository
Silahkan tambahkan class repositori seperti gambar dibawah ini :

Gambar Class Repository


package com.binar.demo.repository.oauth;

import com.binar.demo.model.oauth.User;
import org.springframework.data.jpa.repository.Query;
import
org.springframework.data.repository.PagingAndSortingRepository;

public interface UserRepository extends


PagingAndSortingRepository<User, Long> {
@Query("FROM User u WHERE LOWER(u.username) = LOWER(?1)")
User findOneByUsername(String username);

@Query("FROM User u WHERE u.otp = ?1")


User findOneByOTP(String otp);

@Query("FROM User u WHERE LOWER(u.username) =


LOWER(:username)")
User checkExistingEmail(String username);
}

19
d. Service
Tambahkan class service seperti pada gambar dibawah ini :

Gambar Service Oauth

package com.binar.demo.view.oauth;

import com.binar.demo.model.oauth.Client;
import com.binar.demo.model.oauth.Role;
import com.binar.demo.model.oauth.RolePath;
import com.binar.demo.model.oauth.User;
import com.binar.demo.repository.oauth.ClientRepository;
import com.binar.demo.repository.oauth.RolePathRepository;
import com.binar.demo.repository.oauth.RoleRepository;
import com.binar.demo.repository.oauth.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import
org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;

@Component
@Service
public class DatabaseSeeder implements ApplicationRunner {

private static final String TAG = "DatabaseSeeder {}";

20
private Logger logger =
LoggerFactory.getLogger(DatabaseSeeder.class);

@Autowired
private PasswordEncoder encoder;

@Autowired
private RoleRepository roleRepository;

@Autowired
private ClientRepository clientRepository;

@Autowired
private UserRepository userRepository;

@Autowired
private RolePathRepository rolePathRepository;

private String defaultPassword = "password";

private String[] users = new String[]{


"admin@mail.com:ROLE_SUPERUSER ROLE_USER ROLE_ADMIN",
"user@mail.com:ROLE_USER"
};

private String[] clients = new String[]{


"my-client-apps:ROLE_READ ROLE_WRITE", // mobile
"my-client-web:ROLE_READ ROLE_WRITE" // web
};

private String[] roles = new String[] {


"ROLE_SUPERUSER:user_role:^/.*:GET|PUT|POST|PATCH|
DELETE|OPTIONS",
"ROLE_ADMIN:user_role:^/.*:GET|PUT|POST|PATCH|DELETE|
OPTIONS",
"ROLE_USER:user_role:^/.*:GET|PUT|POST|PATCH|DELETE|
OPTIONS",
"ROLE_READ:oauth_role:^/.*:GET|PUT|POST|PATCH|DELETE|
OPTIONS",
"ROLE_WRITE:oauth_role:^/.*:GET|PUT|POST|PATCH|
DELETE|OPTIONS"
};

@Override
@Transactional
public void run(ApplicationArguments applicationArguments) {
String password = encoder.encode(defaultPassword);

this.insertRoles();
this.insertClients(password);
this.insertUser(password);
}

@Transactional
public void insertRoles() {
for (String role: roles) {
String[] str = role.split(":");
String name = str[0];

21
String type = str[1];
String pattern = str[2];
String[] methods = str[3].split("\\|");
Role oldRole = roleRepository.findOneByName(name);
if (null == oldRole) {
oldRole = new Role();
oldRole.setName(name);
oldRole.setType(type);
oldRole.setRolePaths(new ArrayList<>());
for (String m: methods) {
String rolePathName = name.toLowerCase()
+"_"+m.toLowerCase();
RolePath rolePath =
rolePathRepository.findOneByName(rolePathName);
if (null == rolePath) {
rolePath = new RolePath();
rolePath.setName(rolePathName);
rolePath.setMethod(m.toUpperCase());
rolePath.setPattern(pattern);
rolePath.setRole(oldRole);
rolePathRepository.save(rolePath);
oldRole.getRolePaths().add(rolePath);
}
}
}

roleRepository.save(oldRole);
}
}

@Transactional
public void insertClients(String password) {
for (String c: clients) {
String[] s = c.split(":");
String clientName = s[0];
String[] clientRoles = s[1].split("\\s");
Client oldClient =
clientRepository.findOneByClientId(clientName);
if (null == oldClient) {
oldClient = new Client();
oldClient.setClientId(clientName);

oldClient.setAccessTokenValiditySeconds(28800);//1 jam
3600 :token valid : seharian kerja : normal 1 jam

oldClient.setRefreshTokenValiditySeconds(7257600);// refresh
oldClient.setGrantTypes("password refresh_token
authorization_code");
oldClient.setClientSecret(password);
oldClient.setApproved(true);
oldClient.setRedirectUris("");
oldClient.setScopes("read write");
List<Role> rls =
roleRepository.findByNameIn(clientRoles);

if (rls.size() > 0) {
oldClient.getAuthorities().addAll(rls);
}
}

22
clientRepository.save(oldClient);
}
}

@Transactional
public void insertUser(String password) {
for (String userNames: users) {
String[] str = userNames.split(":");
String username = str[0];
String[] roleNames = str[1].split("\\s");

User oldUser =
userRepository.findOneByUsername(username);
if (null == oldUser) {
oldUser = new User();
oldUser.setUsername(username);
oldUser.setPassword(password);
List<Role> r =
roleRepository.findByNameIn(roleNames);
oldUser.setRoles(r);
}

userRepository.save(oldUser);
}
}
}

23
e. Configurasi Security
Tambahkan configurasi security seperti pada gambar dibawah ini :

Gambar Security Konfigurasi Spring Boot


package com.binar.demo.config;

import com.binar.demo.view.oauth.Oauth2UserDetailsService;
import com.binar.demo.view.oauth.Oauth2ClientDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import
org.springframework.security.authentication.AuthenticationManager
;
import
org.springframework.security.crypto.password.PasswordEncoder;
import
org.springframework.security.oauth2.config.annotation.configurers
.ClientDetailsServiceConfigurer;
import
org.springframework.security.oauth2.config.annotation.web.configu
ration.AuthorizationServerConfigurerAdapter;
import
org.springframework.security.oauth2.config.annotation.web.configu
ration.EnableAuthorizationServer;
import
org.springframework.security.oauth2.config.annotation.web.configu
rers.AuthorizationServerEndpointsConfigurer;
import
org.springframework.security.oauth2.config.annotation.web.configu
rers.AuthorizationServerSecurityConfigurer;
import
org.springframework.security.oauth2.provider.token.AccessTokenCon
verter;
import
org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration

24
@EnableAuthorizationServer
public class Oauth2AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {

@Autowired
private Oauth2ClientDetailsService clientDetailsService;

@Autowired
private Oauth2UserDetailsService userDetailsService;

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private PasswordEncoder passwordEncoder;

@Autowired
private AccessTokenConverter accessTokenConverter;

@Autowired
private TokenStore tokenStore;

/**
* Change server config, password encoder etc.
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer
server) throws Exception {
server.allowFormAuthenticationForClients()
// .tokenKeyAccess("permitAll()")
// .checkTokenAccess("isAuthenticated()")
.passwordEncoder(passwordEncoder)
;
}

/**
* Change client details etc.
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.withClientDetails(clientDetailsService);
}

/**
* Change user details etc.
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer
endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore)
.accessTokenConverter(accessTokenConverter)
.userDetailsService(userDetailsService)
;
}
}

25
f. Run and Testing with Postman

Testing by postman hit login and get token jwt

Cara mengunakan token jwt

26
27
g. Gitlab Project
Silahkan pull dan download project pada url berikut : Gitlab :
https://gitlab.com/binarxgrab_mainbootcamp/backend_java/boilerplate_bej
Branch : 04032022-spring-security

h. Register

a. Register Manual
https://gitlab.com/binarxgrab_mainbootcamp/backend_java/boilerplate_bej
Branch : 04032022-register

Service
import java.util.Map;

public interface UserService {


Map registerManual(RegisterModel objModel) ;
}

Service Impl
package com.binar.grab.service.impl;

import com.binar.grab.config.Config;
import com.binar.grab.dao.request.RegisterModel;
import com.binar.grab.model.oauth.Role;
import com.binar.grab.model.oauth.User;
import com.binar.grab.repository.oauth.RoleRepository;
import com.binar.grab.repository.oauth.UserRepository;
import com.binar.grab.service.UserService;
import com.binar.grab.util.TemplateResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService {

Config config = new Config();


private static final Logger logger =

28
LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
RoleRepository repoRole;

@Autowired
UserRepository repoUser;

@Autowired
private PasswordEncoder encoder;

@Autowired
public TemplateResponse templateResponse;
@Override
public Map registerManual(RegisterModel objModel) {
Map map = new HashMap();
try {
String[] roleNames = {"ROLE_USER", "ROLE_READ",
"ROLE_WRITE"}; // admin
User user = new User();
user.setUsername(objModel.getEmail().toLowerCase());
user.setFullname(objModel.getFullname());

//step 1 :
// user.setEnabled(false); // matikan user

String password =
encoder.encode(objModel.getPassword().replaceAll("\\s+", ""));
List<Role> r = repoRole.findByNameIn(roleNames);

user.setRoles(r);
user.setPassword(password);
User obj = repoUser.save(user);

return templateResponse.templateSukses(obj);

} catch (Exception e) {
logger.error("Eror registerManual=", e);
return templateResponse.templateEror("eror:"+e);
}

}
}

Controller
package com.binar.grab.controller;

import com.binar.grab.config.Config;
import com.binar.grab.dao.request.RegisterModel;
import com.binar.grab.model.oauth.User;
import com.binar.grab.repository.oauth.UserRepository;
import com.binar.grab.service.UserService;
import com.binar.grab.util.TemplateResponse;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

29
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/user-register/")
public class Register {
@Autowired
private UserRepository userRepository;

Config config = new Config();

@Autowired
public UserService serviceReq;

@Autowired
public TemplateResponse templateCRUD;
@PostMapping("/register")
public ResponseEntity<Map> saveRegisterManual(@RequestBody
RegisterModel objModel) throws RuntimeException {
Map map = new HashMap();

User user =
userRepository.checkExistingEmail(objModel.getEmail());
if (null != user) {
return new
ResponseEntity<Map>(templateCRUD.notFound("Username sudah ada"),
HttpStatus.OK);

}
map = serviceReq.registerManual(objModel);

return new ResponseEntity<Map>(map, HttpStatus.OK);


}
}

Allow URI:

Run postman :

30
b. Register By OTP

https://gitlab.com/binarxgrab_mainbootcamp/backend_java/boilerplate_bej
Branch : 04032022-register-otp

Step 1 : daftarkan email untuk mendapatkan passsword pada url berikut :


https://support.google.com/accounts/answer/185833?
p=InvalidSecondFactor&visit_id=637690832060530868-1439835364&rd=1

Step 2 : Pom.xml
<!-- email -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>

Step 3 :application.properties
31
expired.token.password.minute=1200

#email follow : https://support.google.com/accounts/answer/185833?


p=InvalidSecondFactor&visit_id=637690832060530868-1439835364&rd=1
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=rikialdipari@gmail.com
spring.mail.password=lblsdoexriohzmxqtj
spring.mail.sender.name=admin
spring.mail.sender.mail=no-reply-@test.com
# Other properties
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.writetimeout=5000
# TLS , port 587
spring.mail.properties.mail.smtp.starttls.enable=true

Step 4 : UserServiceImpl.registerManual
Uncoment baris
user.setEnabled(false); //

Step 5 : controler register


@Autowired
public EmailTemplate emailTemplate;

@Autowired
public EmailSender emailSender;

// Step 2: sendp OTP berupa URL: guna updeta enable agar bisa login:
@PostMapping("/send-otp")//send OTP
public Map sendEmailegister(@RequestBody RegisterModel user) {
String message = "Thanks, please check your email for
activation.";

if (user.getEmail() == null) return templateCRUD.templateEror("No


email provided");
User found = userRepository.findOneByUsername(user.getEmail());
if (found == null) return templateCRUD.notFound("Email not
found"); //throw new BadRequest("Email not found");

String template = emailTemplate.getRegisterTemplate();


if (StringUtils.isEmpty(found.getOtp())) {
User search;
String otp;
do {
otp = SimpleStringUtils.randomString(6, true);
search = userRepository.findOneByOTP(otp);
} while (search != null);
Date dateNow = new Date();
Calendar calendar = Calendar.getInstance();

32
calendar.setTime(dateNow);
calendar.add(Calendar.MINUTE, expiredToken);
Date expirationDate = calendar.getTime();

found.setOtp(otp);
found.setOtpExpiredDate(expirationDate);
template = template.replaceAll("\\{\\{USERNAME}}",
(found.getFullname() == null ? found.getUsername1() :
found.getFullname()));
template = template.replaceAll("\\{\\{VERIFY_TOKEN}}", otp);
userRepository.save(found);
} else {
template = template.replaceAll("\\{\\{USERNAME}}",
(found.getFullname() == null ? found.getUsername1() :
found.getFullname()));
template = template.replaceAll("\\{\\{VERIFY_TOKEN}}",
found.getOtp());
}
emailSender.sendAsync(found.getUsername(), "Register", template);
return templateCRUD.templateSukses(message);
}
}

Step 6 : utils
package com.binar.grab.util;

import org.springframework.stereotype.Component;

@Component("emailTemplate")
public class EmailTemplate {
public String getRegisterTemplate() {
return "<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n" +
"<style>\n" +
"\t.email-container {\n" +
"\t\tpadding-top: 10px;\n" +
"\t}\n" +
"\tp {\n" +
"\t\ttext-align: left;\n" +
"\t}\n" +
"\n" +
"\ta.btn {\n" +
"\t\tdisplay: block;\n" +
"\t\tmargin: 30px auto;\n" +
"\t\tbackground-color: #01c853;\n" +
"\t\tpadding: 10px 20px;\n" +
"\t\tcolor: #fff;\n" +
"\t\ttext-decoration: none;\n" +
"\t\twidth: 30%;\n" +
"\t\ttext-align: center;\n" +
"\t\tborder: 1px solid #01c853;\n" +
"\t\ttext-transform: uppercase;\n" +
"\t}\n" +
"\ta.btn:hover,\n" +
"\ta.btn:focus {\n" +

33
"\t\tcolor: #01c853;\n" +
"\t\tbackground-color: #fff;\n" +
"\t\tborder: 1px solid #01c853;\n" +
"\t}\n" +
"\t.user-name {\n" +
"\t\ttext-transform: uppercase;\n" +
"\t}\n" +
"\t.manual-link,\n" +
"\t.manual-link:hover,\n" +
"\t.manual-link:focus {\n" +
"\t\tdisplay: block;\n" +
"\t\tcolor: #396fad;\n" +
"\t\tfont-weight: bold;\n" +
"\t\tmargin-top: -15px;\n" +
"\t}\n" +
"\t.mt--15 {\n" +
"\t\tmargin-top: -15px;\n" +
"\t}\n" +
"</style>\n" +
"</head>\n" +
"<body>\n" +
"\t<div class=\"email-container\">\n" +
"\t\t<p>Halo <span class=\"user-
name\">{{USERNAME}}</span> Selamat bergabung</p>\n" +
"\t\t<p>Harap konfirmasikan email kamu dengan memasukan
kode dibawah ini</p>\n" +
"\t\t\n" +
"\t\tkode: <b>{{VERIFY_TOKEN}}</b>\n" +
"\t\t\n" +
"\t\t<p class=\"mt--15\">Jika kamu butuh bantuan atau
pertanyaan, hubungi customer care kami di .... atau kirim email
ke ....</p>\n" +
"\t\t\n" +
"\t\t<p>Semoga harimu menyenangkan!</p>\n" +
"\t\t\n" +
"\t\t<p>PT ABC,</p>\n" +
"\t\t<p class=\"mt--15\".....</p>\n" +
"\n" +
"\t\t\n" +
"\t</div>\n" +
"</body>\n" +
"</html>";
}

package com.binar.grab.util;

import java.util.Random;

public class SimpleStringUtils {


public static String randomString(int size) {
return randomString(size, false);
}

34
@SuppressWarnings("SpellCheckingInspection")
public static String randomString(int size, boolean numberOnly) {
String saltChars = "1234567890";
if (!numberOnly) {
saltChars +=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
}
StringBuilder salt = new StringBuilder();
Random rnd = new Random();
while (salt.length() < size) {
int index = (int) (rnd.nextFloat() * saltChars.length());
salt.append(saltChars.charAt(index));
}

return salt.toString();
}

package com.binar.grab.service.email;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.TaskExecutor;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.mail.internet.MimeMessage;

@SuppressWarnings({"WeakerAccess", "ConstantConditions"})
@Component("emailSender")
public class EmailSender {
private final static Logger logger =
LoggerFactory.getLogger(EmailSender.class);

@Autowired
private JavaMailSenderImpl mailSender;

@Value("${spring.mail.sender.name:}")
private String senderName;

@Value("${spring.mail.sender.mail:}")
private String senderEmail;

@Qualifier("taskExecutor")
@Autowired
private TaskExecutor taskExecutor;

public boolean send(String email, String subject, String message) {


return send(null, email, subject, message);
}

35
public boolean send(String from, String email, String subject, String
message) {
MimeMessage mime = mailSender.createMimeMessage();
if (StringUtils.isEmpty(from)) {
from = senderEmail;
}
if (StringUtils.isEmpty(from)) {
from = "admin@mail.com";
}
boolean success = false;
try {
logger.info("Sending email to: "+email);
logger.info("Sending email from: "+from);
logger.info("Sending email with subject: "+subject);

MimeMessageHelper helper = new MimeMessageHelper(mime, true);


helper.setFrom(from,senderName);
helper.setTo(email);
helper.setSubject(subject);
helper.setText(message, true);
mailSender.send(mime);
success = true;
} catch (Exception e) {
logger.error("error: "+e.getMessage());
}

return success;
}

public void sendAsync(final String to, final String subject, final


String message) {
taskExecutor.execute(new Runnable() {
@Override
public void run() {

send(to, subject, message);


}
});
}

Postman :

36
c. Konfirmasi OTP Token User: guna untuk mengupdate user agar
dapat login
Controller

@GetMapping("/register-confirm-otp/{token}")
public ResponseEntity<Map> saveRegisterManual(@PathVariable(value =
"token") String tokenOtp) throws RuntimeException {

User user = userRepository.findOneByOTP(tokenOtp);


if (null == user) {
return new ResponseEntity<Map>(templateCRUD.templateEror("OTP
tidak ditemukan"), HttpStatus.OK);
}
37
String today = config.convertDateToString(new Date());

String dateToken =
config.convertDateToString(user.getOtpExpiredDate());
if(Long.parseLong(today) > Long.parseLong(dateToken)){
return new ResponseEntity<Map>(templateCRUD.templateEror("Your
token is expired. Please Get token again."), HttpStatus.OK);
}
//update user
user.setEnabled(true);
userRepository.save(user);

return new ResponseEntity<Map>(templateCRUD.templateEror("Sukses,


Silahkan Melakukan Login"), HttpStatus.OK);
}

Config
public String convertDateToString(Date date) {

DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");


String strDate = dateFormat.format(date);
return strDate;
}

d. Forget Password
Gitlab :
https://gitlab.com/binarxgrab_mainbootcamp/backend_java/boilerplate_bej
Branch: 08032022-forget-password

Step 1 : Chek email – jika tidak ada maka return email tidak ditemukan
Step 2: Jika email ditemukan – OTP Akan dikirim ke email : Validasi OTP
Step 3: User akan memasukkan OTP untuk dapat melakukan reset Password

Send Email Chek OTP Reset Pass

a. Forget Password OTP Email


Controller
package com.binar.grab.controller;

import com.binar.grab.config.Config;
import com.binar.grab.dao.request.ResetPasswordModel;
import com.binar.grab.model.oauth.User;

38
import com.binar.grab.repository.oauth.UserRepository;
import com.binar.grab.service.UserService;
import com.binar.grab.service.email.EmailSender;
import com.binar.grab.util.EmailTemplate;
import com.binar.grab.util.SimpleStringUtils;
import com.binar.grab.util.TemplateResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/forget-password/")
public class ForgerPasswordController {

@Autowired
private UserRepository userRepository;

Config config = new Config();

@Autowired
public UserService serviceReq;

@Value("${expired.token.password.minute:}")//FILE_SHOW_RUL
private int expiredToken;

@Autowired
public TemplateResponse templateCRUD;

@Autowired
public EmailTemplate emailTemplate;

@Autowired
public EmailSender emailSender;

@Autowired
private PasswordEncoder passwordEncoder;

// Step 1 : Send OTP


@PostMapping("/forgot-password")//send OTP
public Map sendEmailPassword(@RequestBody ResetPasswordModel user) {
String message = "Thanks, please check your email";

if (StringUtils.isEmpty(user.getEmail())) return
templateCRUD.templateEror("No email provided");
User found = userRepository.findOneByUsername(user.getEmail());
if (found == null) return templateCRUD.notFound("Email not found");
//throw new BadRequest("Email not found");

String template = emailTemplate.getResetPassword();

39
if (StringUtils.isEmpty(found.getOtp())) {
User search;
String otp;
do {
otp = SimpleStringUtils.randomString(6, true);
search = userRepository.findOneByOTP(otp);
} while (search != null);
Date dateNow = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(dateNow);
calendar.add(Calendar.MINUTE, expiredToken);
Date expirationDate = calendar.getTime();

found.setOtp(otp);
found.setOtpExpiredDate(expirationDate);
template = template.replaceAll("\\{\\{PASS_TOKEN}}", otp);
template = template.replaceAll("\\{\\{USERNAME}}",
(found.getUsername1() == null ? "" +
"@UserName"
:
"@" + found.getUsername1()));

userRepository.save(found);
} else {
template = template.replaceAll("\\{\\{USERNAME}}",
(found.getUsername1() == null ? "" +
"@UserName"
:
"@" + found.getUsername1()));
template = template.replaceAll("\\{\\{PASS_TOKEN}}",
found.getOtp());
}
emailSender.sendAsync(found.getUsername(), "Chute - Forget
Password", template);

return templateCRUD.templateSukses("success");

//Step 2 : CHek TOKEN OTP EMAIL


@PostMapping("/forgot-password-chek-token")
public Map cheKTOkenValid(@RequestBody ResetPasswordModel model) {
if (model.getOtp() == null) return templateCRUD.notFound("Token " +
config.isRequired);

User user = userRepository.findOneByOTP(model.getOtp());


if (user == null) {
return templateCRUD.templateEror("Token not valid");
}

return templateCRUD.templateSukses("Success");
}

// Step 3 : lakukan reset password baru


@PostMapping("/forgot-password-reset")
public Map<String, String> resetPassword(@RequestBody
ResetPasswordModel model) {
if (model.getOtp() == null) return templateCRUD.notFound("Token " +

40
config.isRequired);
if (model.getNewPassword() == null) return
templateCRUD.notFound("New Password " + config.isRequired);
User user = userRepository.findOneByOTP(model.getOtp());
String success;
if (user == null) return templateCRUD.notFound("Token not valid");

user.setPassword(passwordEncoder.encode(model.getNewPassword().replaceAll("
\\s+", "")));
user.setOtpExpiredDate(null);
user.setOtp(null);

try {
userRepository.save(user);
success = "success";
} catch (Exception e) {
return templateCRUD.templateEror("Gagal simpan user");
}
return templateCRUD.templateSukses(success);
}

Model Request
package com.binar.grab.dao.request;

import lombok.Data;

@Data
public class ResetPasswordModel {
public String email;

public String otp;


public String newPassword;
}

Email Template
public String getResetPassword(){
return "<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n" +
"<style>\n" +
"\t.email-container {\n" +
"\t\tpadding-top: 10px;\n" +
"\t}\n" +
"\tp {\n" +
"\t\ttext-align: left;\n" +
"\t}\n" +
"\n" +

41
"\ta.btn {\n" +
"\t\tdisplay: block;\n" +
"\t\tmargin: 30px auto;\n" +
"\t\tbackground-color: #01c853;\n" +
"\t\tpadding: 10px 20px;\n" +
"\t\tcolor: #fff;\n" +
"\t\ttext-decoration: none;\n" +
"\t\twidth: 30%;\n" +
"\t\ttext-align: center;\n" +
"\t\tborder: 1px solid #01c853;\n" +
"\t\ttext-transform: uppercase;\n" +
"\t}\n" +
"\ta.btn:hover,\n" +
"\ta.btn:focus {\n" +
"\t\tcolor: #01c853;\n" +
"\t\tbackground-color: #fff;\n" +
"\t\tborder: 1px solid #01c853;\n" +
"\t}\n" +
"\t.user-name {\n" +
"\t\ttext-transform: uppercase;\n" +
"\t}\n" +
"\t.manual-link,\n" +
"\t.manual-link:hover,\n" +
"\t.manual-link:focus {\n" +
"\t\tdisplay: block;\n" +
"\t\tcolor: #396fad;\n" +
"\t\tfont-weight: bold;\n" +
"\t\tmargin-top: -15px;\n" +
"\t}\n" +
"\t.mt--15 {\n" +
"\t\tmargin-top: -15px;\n" +
"\t}\n" +
"</style>\n" +
"</head>\n" +
"<body>\n" +
"\t<div class=\"email-container\">\n" +
"\t\t<p>If you Requested a password for {{USERNAME}}, use the
confirmation code below to complete the process. If you didn't make this
request, ignore the email.</p>\n" +
"\t\t\n" +
"\t\tCode: <b>{{PASS_TOKEN}}</b>\n" +
"\t\t\n" +
"\t\t\n" +
"\t\t<p>Regards</p>\n" +
"\t\t<p>PT ABC</p>\n" +
"\t\t<p class=\"mt--15\".....</p>\n" +
"\n" +
"\t\t\n" +
"\t</div>\n" +
"</body>\n" +
"</html>";
}

Allow URI : tambahkan uri yang digunakan agar diaskes tampa token

42
b. Test Postman

Step 1

Step 2 : Chek OTP

43
c. Filter And Sorting

Controller

package com.binar.grab.controller;

import com.binar.grab.config.Config;
import com.binar.grab.model.Barang;
import com.binar.grab.model.Supplier;
import com.binar.grab.model.oauth.User;
import com.binar.grab.repository.BarangRepository;
import com.binar.grab.repository.oauth.UserRepository;
import com.binar.grab.service.SupplierService;
import com.binar.grab.service.oauth.Oauth2UserDetailsService;
import com.binar.grab.util.SimpleStringUtils;
import com.binar.grab.util.TemplateResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UserDetails;
import
org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import com.binar.grab.service.BarangService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

44
@RestController
@RequestMapping("/v1/binar/barang")
public class BarangController {

@Autowired
public BarangService barangService;

Config config = new Config();

@Autowired
private Oauth2UserDetailsService userDetailsService;

@Autowired
public BarangRepository barangRepository;

@Autowired
public TemplateResponse templateResponse;

@Autowired
public UserRepository userRepository;

SimpleStringUtils simpleStringUtils = new SimpleStringUtils();

@GetMapping("list-barang")// by seller
public ResponseEntity<Map> listNotif(
@RequestParam() Integer page,
@RequestParam(required = true) Integer size,
@RequestParam(required = false) String nama,// BUY OR SELL :
menentukan siapa yang akses
@RequestParam(required = false) Double priceMin,
@RequestParam(required = false) Double priceMax,
@RequestParam(required = false) String orderby,
@RequestParam(required = false) String ordertype,
Principal principal) {
/*
1.principal : mendapatkan username berdasarkan token user yang akses di
client
2. getShort : shoritng
*/
Pageable show_data = simpleStringUtils.getShort(orderby, ordertype,
page, size);
Page<Barang> list = null;

User idUser = getUserIdToken(principal, userDetailsService);


if (idUser == null) {
return new ResponseEntity<Map>(templateResponse.notFound("User id
notfound"), HttpStatus.NOT_FOUND);
}
if (nama != null && priceMin !=null && priceMax != null ) {
list = barangRepository.getDataByPriceAndNama(priceMin,priceMax,
"'%" + nama + "%'",show_data);
} else if ( priceMin !=null && priceMax != null ) {
list = barangRepository.getDataByPrice(priceMin,priceMax,
show_data);
} else if (nama != null ) {
list = barangRepository.findByNamaLike("'%" + nama + "%'",
show_data);

45
} else {
list = barangRepository.getAllData(show_data);
}
return new ResponseEntity<Map>(templateResponse.templateSukses(list),
new HttpHeaders(), HttpStatus.OK);
}

public User getUserIdToken(Principal principal,


Oauth2UserDetailsService userDetailsService) {
UserDetails user = null;
String username = principal.getName();
if (!StringUtils.isEmpty(username)) {
user = userDetailsService.loadUserByUsername(username);
}

if (null == user) {
throw new UsernameNotFoundException("User not found");
}
User idUser = userRepository.findOneByUsername(user.getUsername());
if (null == idUser) {
throw new UsernameNotFoundException("User name not found");
}
return idUser;

46
Tambahkan SimpleStringUtils
public Pageable getShort(String orderby, String ordertype, Integer page,
Integer size) {
Pageable show_data;
if (orderby != null) {
if (ordertype != null) {
if (ordertype.equals("desc")) {
show_data = PageRequest.of(page, size,
Sort.by(orderby).descending());
} else {
show_data = PageRequest.of(page, size,
Sort.by(orderby).ascending());
}
} else {
show_data = PageRequest.of(page, size,
Sort.by(orderby).descending());
}

} else {
show_data = PageRequest.of(page, size, Sort.by("id").descending());
}
return show_data;
}

Repository
public Page<Barang> findByNamaLike(String nama , Pageable pageable);

@Query("select c from Barang c where c.price BETWEEN :priceMin


and :priceMax")// nama class
Page<Barang> getDataByPrice( Double priceMin, Double priceMax, Pageable
pageable);

@Query("select c from Barang c where c.price BETWEEN :priceMin


and :priceMax and c.nama like :nama")// nama class
Page<Barang> getDataByPriceAndNama( Double priceMin, Double priceMax,String
nama, Pageable pageable);

d. File Handling
Branch : 11032022-file-handling

a. Upload File
REQUEST Req Service api: Response :
File 1 or Simpan File URL
Mnay FIle DOWNLO
Res

47
b. Service api
Aplication.properties
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=5MB
app.uploadto.cdn=./cdn/ atau app.uploadto.cdnD://folder/subfolder

Buat file “cdn” di project

Class

Berikut code program :


package com.ecomerce.r2d.controller.fileupload;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

48
@RestController
//@EnableCaching
public class FileController {
private static final Logger logger =
LoggerFactory.getLogger(FileController.class);

@Value("${app.uploadto.cdn}")//FILE_SHOW_RUL
private String UPLOADED_FOLDER;

@Autowired
private FileStorageService fileStorageService;

@RequestMapping(value = "/v1/upload", method = RequestMethod.POST,


consumes = {"multipart/form-data", "application/json"})
public UploadFileResponse uploadFile(@RequestParam("file")
MultipartFile file) throws IOException {

Date date = new Date();


SimpleDateFormat formatter = new SimpleDateFormat("ddMyyyyhhmmss");
String strDate = formatter.format(date);
String fileName = UPLOADED_FOLDER + strDate +
file.getOriginalFilename();
String fileNameforDOwnload = strDate + file.getOriginalFilename();
Path TO = Paths.get(fileName);

// //validasi hanya boleh PNG


// if(!file.getContentType().equals("image/png")){
// return null;// eror
// }

try {

Files.copy(file.getInputStream(), TO); // pengolahan upload


disini :

} catch (Exception e) {
e.printStackTrace();
return new UploadFileResponse(fileName, null,
file.getContentType(), file.getSize(), e.getMessage());
}

String fileDownloadUri =
ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/v1/showFile/")
.path(fileNameforDOwnload)
.toUriString();

return new UploadFileResponse(fileNameforDOwnload, fileDownloadUri,


file.getContentType(), file.getSize(), "false");
}

// public String getPAth(String type) {

49
// try {
// if (type.toLowerCase().equals("product")) {
// File theDir = new File(UPLOADED_FOLDER_PRODUCT);
// if (!theDir.exists()) {
// theDir.mkdirs();
// return UPLOADED_FOLDER_PRODUCT;
// }
// return UPLOADED_FOLDER;
// }
//
// } catch (Exception e) {
// logger.info("Eror create folder" + e);
// }
// return UPLOADED_FOLDER;
// }

@GetMapping("v1/showFile/{fileName:.+}")
public ResponseEntity<Resource> showFile(@PathVariable String fileName,
HttpServletRequest request) { // Load file as Resource : step 1 load path
lokasi name file
Resource resource =
fileStorageService.loadFileAsResource(fileName);
// Try to determine file's content type
String contentType = null;
try {
// dapatan URL download
// System.out.println("resource.getFile().getAbsolutePath" +
resource.getFile().getAbsolutePath());
contentType =
request.getServletContext().getMimeType(resource.getFile().getAbsolutePath(
));

} catch (IOException ex) {


logger.info("Could not determine file type.");
}
// Fallback to the default content type if type could not be
determined
if (contentType == null) {
contentType = "application/octet-stream";// type .json
}
// System.out.println("filename=2=" +
HttpHeaders.CONTENT_DISPOSITION);
// System.out.println("filename=3=" + resource.getFilename());
// System.out.println("filename=3=" + resource);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;
filename=\"" + resource.getFilename() + "\"")
.body(resource);
}

@PostMapping("v1/uploadMultipleFiles")
public List<UploadFileResponse>
uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) throws
IOException {
return Arrays.asList(files)
.stream()
.map(file -> {

50
try {
// step 1 : call method uplaod
return uploadFile(file);
} catch (IOException e) {
e.printStackTrace();
}
return null;
})
.collect(Collectors.toList());
}

private File multipartToFile(MultipartFile upload, String routeName) {


String base = "";

logger.info(String.format("Trying upload file: %s",


upload.getOriginalFilename()));

File file = new File(base + upload.getOriginalFilename());

try {
logger.info(String.format("Saving uploaded file to: '%s'",
file.getAbsolutePath()));
FileOutputStream fos = new FileOutputStream(file);
fos.write(upload.getBytes());
fos.close();
} catch (IOException e) {
logger.error(String.format("Error: POST|UPLOAD %s", routeName),
e);
}

return file;
}

private File multipartToFile(MultipartFile upload) {


return multipartToFile(upload, UPLOADED_FOLDER);
}

package com.binar.grab.controller.fileupload;

public class FileStorageException extends RuntimeException {


public FileStorageException(String message) {
super(message);
}

public FileStorageException(String message, Throwable cause) {


super(message, cause);
}
}

package com.binar.grab.controller.fileupload;

51
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "file")
public class FileStorageProperties {
@Value("${app.uploadto.cdn}")//FILE_SHOW_RUL
private String uploadDir;

public String getUploadDir() {


return uploadDir;
}

public void setUploadDir(String uploadDir) {


this.uploadDir = uploadDir;
}
}

package com.binar.grab.controller.fileupload;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.Date;

@Service
public class FileStorageService {

private final Path fileStorageLocation;

Date date = new Date();


SimpleDateFormat formatter = new SimpleDateFormat("ddMyyyyhhmmss");
String strDate = formatter.format(date);

@Autowired
public FileStorageService(FileStorageProperties fileStorageProperties)
{
this.fileStorageLocation =
Paths.get(fileStorageProperties.getUploadDir())
.toAbsolutePath().normalize();

try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {

52
throw new FileStorageException("Could not create the directory
where the uploaded files will be stored.", ex);
}
}

public String storeFile(MultipartFile file) {


// Normalize file name
String fileName =
StringUtils.cleanPath(file.getOriginalFilename());
String date_name = strDate+file;
System.out.println("ini==="+fileName);
try {
// Check if the file's name contains d characters
if(fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains d
path sequence " + fileName);
}

// Copy file to the target location (Replacing existing file


with the same name)
Path targetLocation =
this.fileStorageLocation.resolve(fileName);
Files.copy(file.getInputStream(), targetLocation,
StandardCopyOption.REPLACE_EXISTING);
System.out.println("ini==2="+fileName);
System.out.println("ini==3="+targetLocation);
return fileName;
} catch (IOException ex) {
throw new FileStorageException("Could not store file " +
fileName + ". Please try again!", ex);
}
}

public Resource loadFileAsResource(String fileName) {


try {
Path filePath =
this.fileStorageLocation.resolve(fileName).normalize();

Resource resource = new UrlResource(filePath.toUri());


if(resource.exists()) {
return resource;
} else {
System.out.println("ini saya= "+filePath);
System.out.println("ini saya 2= "+filePath.toUri());
System.out.println("ini saya 3=
"+filePath.toAbsolutePath());
throw new FileStorageException("File not found " +
fileName);
}
} catch (MalformedURLException ex) {
throw new FileStorageException("File not found " + fileName,
ex);
}
}
}

package com.binar.grab.controller.fileupload;

53
import lombok.Data;

@Data
public class UploadFileResponse {
private String fileName;
private String fileDownloadUri;
private String fileType;
private long size;
private String eror;

public UploadFileResponse(String fileName, String fileDownloadUri,


String fileType, long size, String eror) {
this.fileName = fileName;
this.fileDownloadUri = fileDownloadUri;
this.fileType = fileType;
this.size = size;
this.eror = eror;
}
}

c. Revisi Kode Program di class FileController


Kode program yang direvisi pada method dibawah ini
@RequestMapping(value = "/v1/upload", method = RequestMethod.POST, consumes
= {"multipart/form-data", "application/json"})
public UploadFileResponse uploadFile(@RequestParam("file")
MultipartFile file) throws IOException {

Date date = new Date();


SimpleDateFormat formatter = new SimpleDateFormat("ddMyyyyhhmmss");
String strDate = formatter.format(date);
String nameFormat=
file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf
(".") );
if(nameFormat.isEmpty()){
nameFormat = ".png";
}
String fileName = UPLOADED_FOLDER + strDate + nameFormat;

String fileNameforDOwnload = strDate + nameFormat;


Path TO = Paths.get(fileName);

// //validasi hanya boleh PNG


// if(!file.getContentType().equals("image/png")){
// return null;// eror
// }

try {

Files.copy(file.getInputStream(), TO); // pengolahan upload


disini :

} catch (Exception e) {
54
e.printStackTrace();
return new UploadFileResponse(fileNameforDOwnload, null,
file.getContentType(), file.getSize(), e.getMessage());
}

String fileDownloadUri =
ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/v1/showFile/")
.path(fileNameforDOwnload)
.toUriString();

return new UploadFileResponse(fileNameforDOwnload, fileDownloadUri,


file.getContentType(), file.getSize(), "false");
}

d. Tambahkan pada class main

@EnableConfigurationProperties({
FileStorageProperties.class
})

55
e. Testing

Upload 1 file

56
Upload many file

57
Download file

f. Download file tampa download / show file only


Add class

package com.binar.grab.controller.fileupload;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import
org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

58
//step 1
@Configuration
public class ResourceConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods()
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE",
"PATCH");
}

@Override // cara call engpoint :


localhost:9090/api/showFile/namafile.png:
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//
registry.addResourceHandler("/uploads/**").addResourceLocations("file:uploa
ds/");

registry.addResourceHandler("/showFile/**").addResourceLocations("file:cdn/
");
}
}

Testing
localhost:9090/api/showFile/namafile.png:

g. Deploy Project ke Heroku

a. Buat akun Heroku


https://id.heroku.com/login

59
b. Buat Akun Github
https://github.com/login
deploy project
step 1: git reset
step 2 : git init
step 3 : git add .
step 4 : git commit -m "first commit"
step 5 : git branch -M master
step 6 :
masuk disini untuk generate token
https://github.com/settings/tokens
git remote add origin https://github.com/rikialdi/binar_grab.git
or
git remote set-url origin git@github.com:rikialdi/binar_grab.git

git remote set-url origin


https://rikialdi:[passwordtoken]@github.com/rikialdi/binar_grab.git

step 7: git push -u origin master

1. git remote -v (for checking current repository)

c. Masuk Ke akun Heroku, untuk deploy aplikasi


https://dashboard.heroku.com/apps

Buat nama aplikasi kalian

60
Tampilan setelah membuat aplikasi, jika ingin mengubungkan ke github,
silahkan pilih github seperti gambar dibawah ini

61
Pilih tampil repo, Akan tampil semua repo pada akun github teman2

Tampilan jika sudah terhubung.

62
Paga gambar diatas, dapat dilihat pada tombol enable autometic deploy,
digunakan untuk deploy otomatis mengunakan ci-cd.
Atau degan deploy manual dengan menekan tombol deploy branch.

Berikut tampilan gamba berhasil deploy

d. Membuat database di heroku


Silahkan akses https://data.heroku.com/

63
Silahkan klik salah satu database anda

Pada gambar diatas menjelaskan limit untuk database posgre gratis di heroku.

Silahkan klik tombol dibawah ini untuk melihat credential database , tujuan agar
bisa diakses remote oleh aplikasi local kita.

64
Berikut tampilan credential

e. Membuat env dan staging di spring boot


Step 1- buatlah 3 buah env seperti gambar dibawah ini

Step 2- isi dari application.properties

Step 3- isi dari file


65
Application-dev.properties : mengarah ke db DEV
Application-prod.properties: mengarah ke db PROD heroku

Step 4- copi paste credential database di heroku ke application-prod.properties

f. Tambahkan pada pom.xml


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

g. Aotomatic deploy heroku


Step 1- Perlu di allow enable

Step 2- create file pada gambar dibawah ini

66
Step 3- isi file
stages: # List of stages for jobs, and their order of execution
- build
- test
- deploy

build-job: # This job runs in the build stage, which runs first.
image: maven:3-jdk-8
stage: build
script:
- mvn package
only:
refs:
- master

#unit-test-job: # This job runs in the test stage.


# stage: test # It only starts when the job in the build stage
completes successfully.
# script:
# - echo "Running unit tests... This will take about 60 seconds."
# - sleep 60
# - echo "Code coverage is 90%"

#lint-test-job: # This job also runs in the test stage.


# stage: test # It can run at the same time as unit-test-job (in
parallel).
# script:
# - echo "Linting code... This will take about 10 seconds."
# - sleep 10
# - echo "No lint issues found."

deploy-job: # This job runs in the deploy stage.


stage: deploy # It only runs when *both* jobs in the test stage complete
successfully.

67
script:
- echo "Deploying application..."
- echo "Application successfully deployed."

#stages:
# - build
# - deploy

#build:
# image: maven:3-jdk-8
# stage: "Build"
# script:
# - mvn package
# # when: manual
# artifacts:
# name: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME"
# paths:
# - target/*.jar
# only:
# refs:
# - master

production:
image: ruby:2.4
stage: deploy
before_script:
- gem install dpl
- wget -qO- https://cli-assets.heroku.com/install-ubuntu.sh | sh
script:
- dpl --provider=heroku --app=binar-test --api-key=f4b7b3e5-20
- export HEROKU_API_KEY=f4b7b3e5-2034-469f-
- heroku run --app ecomerced2r migrate
environment:
name: production
url: https://binar-test.herokuapp.com

Cara mendapatkan api-key


Pilih accountsetting

68
h. Register OTP by URI Tymeleaf
Branch : register-tymeleaf
https://github.com/rikialdi/binar_grab.git

a. Step 1-Controller
Masih menggunakan class controller register
@Value("${BASEURL:}")//FILE_SHOW_RUL
private String BASEURL;

@PostMapping("/register-tymeleaf")
public ResponseEntity<Map> saveRegisterManualTymeleaf(@RequestBody
RegisterModel objModel) throws RuntimeException {
Map map = new HashMap();

User user = userRepository.checkExistingEmail(objModel.getEmail());


if (null != user) {
return new ResponseEntity<Map>(templateCRUD.notFound("Username
sudah ada"), HttpStatus.OK);

}
map = serviceReq.registerManual(objModel);

Map sendOTP = sendEmailegisterTymeleaf(objModel);

return new ResponseEntity<Map>(map, HttpStatus.OK);


}

@PostMapping("/send-otp-tymeleaf")//send OTP
public Map sendEmailegisterTymeleaf(@RequestBody RegisterModel user) {
69
String message = "Thanks, please check your email for activation.";

if (user.getEmail() == null) return templateCRUD.templateEror("No email


provided");
User found = userRepository.findOneByUsername(user.getEmail());
if (found == null) return templateCRUD.notFound("Email not found");
//throw new BadRequest("Email not found");

String template = emailTemplate.getRegisterTemplate();


if (StringUtils.isEmpty(found.getOtp())) {
User search;
String otp;
do {
otp = SimpleStringUtils.randomString(6, true);
search = userRepository.findOneByOTP(otp);
} while (search != null);
Date dateNow = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(dateNow);
calendar.add(Calendar.MINUTE, expiredToken);
Date expirationDate = calendar.getTime();

found.setOtp(otp);
found.setOtpExpiredDate(expirationDate);
template = template.replaceAll("\\{\\{USERNAME}}",
(found.getFullname() == null ? found.getUsername1() :
found.getFullname()));
template = template.replaceAll("\\{\\{VERIFY_TOKEN}}", BASEURL +
"user-register/web/index/"+ otp);
userRepository.save(found);
} else {
template = template.replaceAll("\\{\\{USERNAME}}",
(found.getFullname() == null ? found.getUsername1() :
found.getFullname()));
template = template.replaceAll("\\{\\{VERIFY_TOKEN}}", BASEURL +
"user-register/web/index/"+ found.getOtp());
}
emailSender.sendAsync(found.getUsername(), "Register", template);
return templateCRUD.templateSukses(message);
}

b. Step 2- Apllication.properties
BASEURL=http://localhost:8082/api/

70
c. Step 3- Controller Tymeleaf

package com.binar.grab.controller.tymeleaf;

import com.binar.grab.config.Config;
import com.binar.grab.model.oauth.User;
import com.binar.grab.repository.oauth.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.security.Principal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

@Controller
@RequestMapping("/user-register/web/")
public class RegisterConfim {

@Autowired
public UserRepository userRepo;

Config config = new Config();

@GetMapping(value = { "/index/{tokenotp}"})
public String index(Model model,@PathVariable String tokenotp) {
User user = userRepo.findOneByOTP(tokenotp);
if (null == user) {
System.out.println("user null: tidak ditemukan");
model.addAttribute("erordesc", "User not found for code
"+tokenotp);
model.addAttribute("title", "");
return "register";
}
String today = convertDateToString(new Date());

71
String dateToken =
config.convertDateToString(user.getOtpExpiredDate());
if(Long.parseLong(today) > Long.parseLong(dateToken)){
model.addAttribute("erordesc", "Your token is expired. Please
Get token again.");
model.addAttribute("title", "");
return "register";
}
user.setEnabled(true);
userRepo.save(user);
model.addAttribute("title", "Congratulations, "+user.getUsername()
+", you have successfully registered.");
model.addAttribute("erordesc", "");
return "register";
}

@GetMapping(value = "/user1")
public String index() {
return "user";
}

@RequestMapping(value = "/user")
public Principal user(Principal principal) {

return principal;
}

public String convertDateToString(Date date) {

DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");


String strDate = dateFormat.format(date);
// System.out.println("Date: " + strDate);
return strDate;
}
}

d. Step 4- UI Tymeleaf
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title th:utext="${title}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}"/>
</head>
<body>
<h1 th:utext="${title}"/>
<h1 th:utext="${erordesc}"/>
<br/><br/>
<div>Copyright 2022</div>
</body>
</html>

72
e. Test Run Postman

Step 1-Hit api register

Step 2-Chek email

73
Step 3-klik url yang dikirimkan di email, dan akan muncul tampilan berikut :

f. CronJob Scheduler
Branch : cronjob
https://github.com/rikialdi/binar_grab/

a. Step 1- Application Properties


#cronjob
http://www.cronmaker.com/;jsessionid=node01eiipoe8dueep1oj9ktr546dq2136686.
node0?0
#https://www.freeformatter.com/cron-expression-generator-quartz.html
#jalankan setiap detik
cron.expression=* * * ? * *

74
b. Config

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class ThreadConfiguration {
// https://rizkimufrizal.github.io/belajar-spring-async/
/*
https://stackoverflow.com/questions/17659510/core-pool-size-vs-maximum-
pool-size-in-threadpoolexecutor
https://www.baeldung.com/java-threadpooltaskexecutor-core-vs-max-
poolsize
*/
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(4);
executor.setThreadNamePrefix("default_task_executor_thread");
executor.initialize();
return executor;
}
/*
Code diatas berfungsi untuk mendefinisikan bean asyncTaskExecutor,
dimana penulis membuat sebuah thread pool dengan prefix
default_task_executor_thread,
sehingga nanti nya kita dapat menggunakan prefix tersebut untuk
memanggil
thread pool nya, contoh penggunaan nya yaitu
default_task_executor_thread-test atau default_task_executor_thread-sample.

Aturan untuk ukuran ThreadPoolExecutor'skolam umumnya salah dipahami,


karena tidak bekerja seperti yang Anda pikirkan seharusnya atau dengan cara
yang Anda inginkan.

Ambil contoh ini. Ukuran kumpulan utas awal adalah 1, ukuran kumpulan inti
adalah 5, ukuran kumpulan maks adalah 10 dan antrian adalah 100.

Sun's way: saat request masuk thread akan dibuat hingga 5, kemudian tugas
akan ditambahkan ke antrian hingga mencapai 100. Ketika antrian penuh akan
dibuat thread baru hingga maxPoolSize. Setelah semua utas digunakan dan
antrean penuh, tugas akan ditolak. Saat antrian berkurang, begitu juga
jumlah utas aktif.

Cara yang diantisipasi pengguna: saat permintaan masuk dalam utas akan
dibuat hingga 10, maka tugas akan ditambahkan ke antrean hingga mencapai
100 pada titik mana mereka ditolak. Jumlah utas akan diganti namanya secara
maksimal hingga antrian kosong. Ketika antrian kosong, utas akan mati
sampai ada yang corePoolSizetersisa.

Perbedaannya adalah pengguna ingin mulai meningkatkan ukuran kolam lebih

75
awal dan ingin antrian menjadi lebih kecil, sedangkan metode Sun ingin
menjaga ukuran kolam tetap kecil dan hanya menambahnya setelah beban
menjadi banyak.

Berikut adalah aturan Sun untuk pembuatan utas secara sederhana:

Jika jumlah utas kurang dari corePoolSize, buat utas baru untuk menjalankan
tugas baru.
Jika jumlah utas sama (atau lebih besar dari) corePoolSize, masukkan tugas
ke dalam antrian.
Jika antrian penuh, dan jumlah utas kurang dari maxPoolSize, buat utas baru
untuk menjalankan tugas.
Jika antrian penuh, dan jumlah utas lebih besar dari atau sama dengan
maxPoolSize, tolak tugas. Panjang dan pendeknya adalah bahwa utas baru
hanya dibuat ketika antrian terisi, jadi jika Anda menggunakan antrian
tidak terbatas maka jumlah utas tidak akan melebihi corePoolSize.
Untuk penjelasan lebih lengkap, dapatkan dari mulut kuda:
ThreadPoolExecutordokumentasi API.

Ada posting forum yang sangat bagus yang memberi tahu Anda cara
ThreadPoolExecutorkerjanya dengan contoh kode:
http://forums.sun.com/thread.jspa?threadID=5401400&tstart=0

Info lebih lanjut: http://forums.sun.com/thread.jspa?


threadID=5224557&tstart=450
*/
}

c. Main Application

d. Cronjob Example

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
76
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Component
public class CronJobExample {

@Autowired //backround [proccses


@Qualifier(value = "taskExecutor")
private TaskExecutor taskExecutor;

@Scheduled(cron = "${cron.expression:-}")
public void sendAsync() {
taskExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("Method executed at every 2 weeks.
Current time is :: " + new Date());

}
});
}
}

e. Download image dari gambar kemudian simpan ke local


URL url = new
URL("https://binar-test.herokuapp.com/api/showFile/2432022025921Captureass.
PNG");//("http://google.com/pathtoaimage.jpg");
BufferedImage img = ImageIO.read(url);
File file = new File("./cdn/downloaded.jpg");
ImageIO.write(img, "jpg", file);

f. Docker spring boot


Branch: docker
Github : https://github.com/rikialdi/binar_grab/

a. Step 1
Tambahkan file berikut

77
b. Step 2 : .env
APP_ENV=dev
DB_USER=postgres
DB_PASS=postgres
DB_NAME=postgres
DB_HOST=postgres
DB_PORT=5432
REDIS_HOST=redis
UID=1000
GROUPS=1000
CDN_PATH=/opt/app/cache/cdn/
CDN_BASE_URL=http://localhost:8082/cdn/
CRON_EXPRESSION=0 0 */14 * * ?
CRON_EXPRESSION_USER=0 1 * * * ?

c. Step 3 : docker-compose.yml
version: '3.6'
networks:
grab: {}

services:
# nginx:
# image: nginx
# ports:
# - 8090:80
# volumes:
# - ./nginx/nginx-njs.conf:/etc/nginx/nginx.conf:ro
# - ./nginx/config/cors.conf:/etc/nginx/config/cors.conf:ro
# - ./nginx/route.conf:/etc/nginx/routes/bca.conf:ro

78
# - ./nginx/config/nginx-njs.js:/etc/nginx/javascript/nginx.js:ro
# - ./nginx/site-with-oauth2.conf:/etc/nginx/conf.d/default.conf:ro
# - ./nginx/www:/var/www/html:ro
# - ./cache/cdn:${CDN_PATH:-/opt/app/cache/cdn}:ro
# networks:
# - binar-postgres

postgres:
image: postgres:9.5
volumes:
- ./cache/postgres:/var/lib/postgresql/data
#user: ${UID:-1000}:${GROUPS:-1000}
environment:
- POSTGRES_USER=${DB_USER:-postgres}
- POSTGRES_PASSWORD=${DB_PASS:-postgres}
- POSTGRES_DB=${DB_NAME:-postgres}
- PGDATA=/var/lib/postgresql/data/data
- TZ=Asia/Jakarta
ports:
- 5436:5432
networks:
grab:
aliases:
- ${DB_HOST:-postgres}

# redis:
# image: redis
# volumes:
# - ./cache/redis:/data
## user: ${uid:-1000}:${groups:-1000}
# networks:
# grab:
# aliases:
# - ${redis_host:-redis}

spring:
image: maven:3-jdk-8
ports:
- 8090:8082
# restart: unless-stopped
working_dir: /opt/app
volumes:
- .:/opt/app
# - ./cache/maven:/var/maven/.m2
# - ./cache/cdn:${CDN_PATH:-/opt/app/cache/cdn}
#user: ${UID:-1000}:${GROUPS:-1000}
environment:
- TZ=Asia/Jakarta
- SPRING_DATASOURCE_URL=jdbc:postgresql://ec2-18-210-191-5.compute-
1.amazonaws.com:5432/dvch7rlkgrl4i
- SPRING_DATASOURCE_USERNAME=ydtyecrssfbazz
-
SPRING_DATASOURCE_PASSWORD=efe7049a7247bef02e99208410c2f2ad038e2a29e1b94c29
5d2af0aaca7d8a40
- SPRING_PROFILES_CDN_PATHACTIVE=${APP_ENV:-dev}
# - SPRING_CACHE_TYPE=redis
# - SPRING_REDIS_HOST=${REDIS_HOST:-redis}
# - SPRING_REDIS_PORT=${REDIS_PORT:-6379}
# - SPRING_CACHE_REDIS_TIMETOLIVE=1000

79
- SECURITY_JWT_ENABLED=true
- SERVER_SERVLET_CONTEXT_PATH=/api
- MAVEN_CONFIG=/var/maven/.m2
- FILE_UPLOADDIR=${CDN_PATH:-/opt/app/cache/cdn/}
- FILE_SHOW_URL=${CDN_BASE_URL:-http://172.20.2.153:8090/cdn/}
- APP_UPLOADTO_CDN=${CDN_PATH:-/opt/app/cache/cdn/}
# - "MAVEN_OPTS=-Xmx2048m -XX:MaxPermSize=500m"
# - CRON_EXPRESSION=${CRON_EXPRESSION:-0 0 1 */14 * ?}
# - CRON_EXPRESSION_USER=${CRON_EXPRESSION_USER:-0 1 * * * ?}
networks:
grab:
aliases:
- oauth2.api
- hr.api
command: [ "mvn", "-Duser.home=/var/maven", "spring-boot:run" ]
#command: [ "mvn", "-Duser.home=/var/maven", "spring-boot:run" ]

80
d. Step 4: run and noted

a. docker-compose up -d : menajalankan semua yang semuar


servise

b. docker-compose up spring : jalankan image spring saja

c. docker-compose images / docker-compose ps : show all image yg


aktif

d. docker-compose down -v : mematikan semua service yang ada

e. docker-compose ps : melihart image yang sedang aktif

f. remote images :
https://stackoverflow.com/questions/37971961/docker-error-bind-
address-already-in-use

pass: 12345

g. docker-compose logs : melihat logs

h. docker-compose logs -f postgres : lebih detail , melihat logs khusu


container service spring

i. jalankan postgresdi docker secara manual


https://towardsdatascience.com/local-development-set-up-of-
postgresql-with-docker-c022632f13ea

j. docker-compose run -d \--name dev-postgres \-e


POSTGRES_PASSWORD=Pass2020! \-v ${HOME}/postgres-
data/:/var/lib/postgresql/data \-p 5432:5432 binar

k. Send File To Email


Branch: docker

81
Github : https://github.com/rikialdi/binar_grab/

a. Step 1 – strukture

b. Step 2 :code
ApiConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import
org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean;

@Configuration
public class ApiConfig {

@Primary
@Bean
public FreeMarkerConfigurationFactoryBean factoryBean() {
FreeMarkerConfigurationFactoryBean bean=new
FreeMarkerConfigurationFactoryBean();
bean.setTemplateLoaderPath("classpath:/templates");
return bean;
}

EmailService
package com.binar.grab.service.email.email;

import com.binar.grab.config.Config;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
82
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;

import javax.activation.DataSource;
import javax.imageio.ImageIO;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;

@Service
public class EmailService {

@Autowired
private JavaMailSender sender;
// @Autowired
// private JavaMailSenderImpl sender;

Config config2 = new Config();

@Autowired
private Configuration config;

@Value("${spring.mail.username:}")//FILE_SHOW_RUL
private String emailPengirim;

public MailResponse sendEmail(MailRequest request, Map<String, Object>


model) {
MailResponse response = new MailResponse();
MimeMessage message = sender.createMimeMessage();
try {
// set mediaType
MimeMessageHelper helper = new MimeMessageHelper(message,
MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED,
StandardCharsets.UTF_8.name());
// add attachment :naro logo di di../resources
helper.addAttachment("logo.png", new
ClassPathResource("logo.png"));

// 13. Download Gimage dari gambar kemudian simpan ke local


// URL url = new
URL("https://binar-test.herokuapp.com/api/showFile/2432022025921Captureass.
PNG");//("http://google.com/pathtoaimage.jpg");
// BufferedImage img = ImageIO.read(url);
// File file = new File("./cdn/downloaded.jpg");
// ImageIO.write(img, "jpg", file);
// helper.addAttachment("image1", file);
String fileName= "./cdn/Captureass.PNG";
File file2 = new File(fileName);
String formatFile =

83
fileName.substring(fileName.lastIndexOf(".") );
helper.addAttachment(config2.convertDateToString(new Date())
+"image4"+formatFile, file2);

Template t = config.getTemplate("email-template.ftl");
String html =
FreeMarkerTemplateUtils.processTemplateIntoString(t, model);

helper.setTo(request.getTo());
// helper.setTo(emailPengirim);
helper.setText(html, true);
helper.setSubject(request.getSubject());
helper.setFrom(request.getFrom());
sender.send(message);

response.setMessage("mail send to : " + request.getTo());


response.setStatus(Boolean.TRUE);

} catch (MessagingException | IOException | TemplateException e) {


response.setMessage("Mail Sending failure : " +
e.getMessage());
response.setStatus(Boolean.FALSE);
}

return response;
}

MailRequest
package com.binar.grab.service.email.email;

import lombok.Data;

@Data
public class MailRequest {

private String name;


private String to;
private String from;
private String subject;

MailResponse
package com.binar.grab.service.email.email;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
84
@AllArgsConstructor
@NoArgsConstructor
public class MailResponse {
private String message;
private boolean status;

c. Step 3 : pom.xml and application.properties


Pom.xml
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>

Application.properties
BASEURL=http://localhost:8082/api/
SHOWFILEPATH=http://localhost:8082/api/showFile/

d. Step 4: resouces

Email-template
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Java Techie Mail</title>
</head>

<body style="font-family: 'Space Grotesk';font-size: 12px">


<table width="100%" border="0" cellspacing="0" cellpadding="0" >
<tr >
85
<td align="center" valign="top" bgcolor="#FFFFFF"
style="background-color: #edecea; "><br> <br>

<table width="50%" border="0" cellspacing="0" cellpadding="0"


>
<tr >
<td valign="top" bgcolor="#FFFFFF" align="center"
style="background-color: #FFFFFF; font-size:
12px; color: #000000; padding: 20px;">
<div style="">
<img src="${chuteicon}"
style="width:80px;height:80px;">
</div>
</td>
</tr>
<tr >
<td valign="top" bgcolor="#FFFFFF"
style="background-color: #FFFFFF; font-size:
12px; color: #000000; padding: 0px 50px 0px 50px; ">

<div style="font-size: 12px; color: #000000;">


<b>Dear,${namesaya}</b> <br>
<br> Your order confirmation from:
<table><tr>
<td width="20"><img src="${iconuser}"
alt="Avatar" style="width:30px;height:30px; border-radius: 50%"></td>
<td><b>Jl Sudirman</b></td>
</tr></table><br>

</div>
<table style="border-bottom:2px dashed ">
<tr >
<th style="border-bottom:2px dashed
">Qty</th>
<th style="border-bottom:2px dashed
">Desc</th>
<th style="border-bottom:2px dashed
">Amt</th>
</tr>
<#list datainvoice as item>
<tr>
<td>${item.nama}</td>
<td>${item.satuan}</td>
<td>${item.harga}</td>
</tr>
<tr>
<td colspan="1">1</td>
<td colspan="1">Shipping</td>
<td colspan="1">10</td>
</tr>
<br>
</#list>
<tr>
<td colspan="2" style="text-align:
right;">SUBTOTAL</td>
<td colspan="1" >10000</td>
</tr>

86
</table>
<div style="font-size: 12px; color: #000000;">
<br> <b>Will be shipped to:</b><br>
Jl Sudirman <br>
Jakarta <br>
20876 <br>
</div>
<div style="font-size: 12px; color: #000000;">
<br> <li>You ill get notification on our app
<b>@UserTes</b> when ship
your items.</li><br>

</div>

</td>
</tr>
</table>
<br> <br></td>
</tr>
</table>
</body>

</html>

e. Step 5 : Testing By Junit


@Value("${SHOWFILEPATH:}")//FILE_SHOW_RUL
private String SHOWFILEPATH;

@Autowired
public BarangRepository barangRepository;

SimpleStringUtils simpleStringUtils = new SimpleStringUtils();

@Autowired
private EmailService service;

@Test
public void sendEmail( ) {
MailRequest request = new MailRequest();
request.setName("Invoice Test name");
request.setTo("rikialdipari@gmail.com");
request.setFrom("rikialdipari@gmail.com");
request.setSubject("Invoice Test");
Map<String, Object> model = new HashMap<>();
model.put("namesaya", "riki aldi pari");

model.put("chuteicon",
"https://binar-test.herokuapp.com/api/showFile/2432022025921Captureass.PNG"
);
BigDecimal total= new BigDecimal(0);

Pageable show_data = simpleStringUtils.getShort("id", "desc", 0, 1);


Page<Barang> data = barangRepository.getAllData(show_data);

87
model.put("datainvoice", data.getContent());
model.put("iconuser","https://binar-test.herokuapp.com/api/showFile/
2432022025921Captureass.PNG") ;
MailResponse ma = service.sendEmail(request, model);
System.out.println("MailResponse 1="+ma.getMessage());

f. Login with username and password only


url : https://github.com/rikialdi/binar_grab.git
branch : login

a. Step 1- pom.xml
<!--valdation-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>

b. Step 2- buatlah class userservice

public Map login(LoginModel objLogin);

88
c. Step 3- user impl

@Value("${BASEURL}")
private String baseUrl;

@Autowired
private RestTemplateBuilder restTemplateBuilder;

@Override
public Map login(LoginModel loginModel) {
/**
* bussines logic for login here
* **/
try {
Map<String, Object> map = new HashMap<>();

User checkUser =
repoUser.findOneByUsername(loginModel.getUsername());

if ((checkUser != null) &&


(encoder.matches(loginModel.getPassword(), checkUser.getPassword()))) {
if (!checkUser.isEnabled()) {
map.put("is_enabled", checkUser.isEnabled());
return templateResponse.templateEror(map);
}
}
if (checkUser == null) {
return templateResponse.notFound("user not found");
}
if (!(encoder.matches(loginModel.getPassword(),
checkUser.getPassword()))) {
return templateResponse.templateEror("wrong password");
}
String url = baseUrl + "/oauth/token?username=" +
loginModel.getUsername() +
"&password=" + loginModel.getPassword() +
"&grant_type=password" +
"&client_id=my-client-web" +
"&client_secret=password";
ResponseEntity<Map> response =
restTemplateBuilder.build().exchange(url, HttpMethod.POST, null, new
ParameterizedTypeReference<Map>() {
});

if (response.getStatusCode() == HttpStatus.OK) {

89
User user =
repoUser.findOneByUsername(loginModel.getUsername());
List<String> roles = new ArrayList<>();

for (Role role : user.getRoles()) {


roles.add(role.getName());
}
//save token
//
checkUser.setAccessToken(response.getBody().get("access_token").toString())
;
//
checkUser.setRefreshToken(response.getBody().get("refresh_token").toString(
));
// userRepository.save(checkUser);

map.put("access_token",
response.getBody().get("access_token"));
map.put("token_type",
response.getBody().get("token_type"));
map.put("refresh_token",
response.getBody().get("refresh_token"));
map.put("expires_in",
response.getBody().get("expires_in"));
map.put("scope", response.getBody().get("scope"));
map.put("jti", response.getBody().get("jti"));

return map;
} else {
return templateResponse.notFound("user not found");
}
} catch (HttpStatusCodeException e) {
e.printStackTrace();
if (e.getStatusCode() == HttpStatus.BAD_REQUEST) {
return templateResponse.templateEror("invalid login");
}
return templateResponse.templateEror(e);
} catch (Exception e) {
e.printStackTrace();

return templateResponse.templateEror(e);
}
}

90
d. Model – request

package com.binar.grab.dao.request;

import lombok.Data;

import javax.validation.constraints.NotEmpty;

@Data
public class LoginModel {
@NotEmpty(message = "username is required.")
private String username;
@NotEmpty(message = "password is required.")
private String password;
}

e. Config-allow uri

91
f. Controller

package com.binar.grab.controller;

import com.binar.grab.config.Config;
import com.binar.grab.dao.request.LoginModel;
import com.binar.grab.dao.request.RegisterModel;
import com.binar.grab.model.oauth.User;
import com.binar.grab.repository.oauth.UserRepository;
import com.binar.grab.service.UserService;
import com.binar.grab.service.email.EmailSender;
import com.binar.grab.util.EmailTemplate;
import com.binar.grab.util.SimpleStringUtils;
import com.binar.grab.util.TemplateResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import java.util.Map;

@RestController
@RequestMapping("/user-login/")
public class LoginController {
@Autowired
private UserRepository userRepository;

92
Config config = new Config();

@Autowired
public UserService serviceReq;

@Value("${expired.token.password.minute:}")//FILE_SHOW_RUL
private int expiredToken;

@Autowired
public TemplateResponse templateCRUD;

@PostMapping("/login")
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Map> login(@Valid @RequestBody LoginModel
objModel) {
Map map = serviceReq.login(objModel);
return new ResponseEntity<Map>(map, HttpStatus.OK);
}

g. Testing

@Autowired
private TestRestTemplate restTemplate;
@Test
public void restTemplateLogin2() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "*/*");
headers.set("Content-Type", "application/json");
String bodyTesting = "{\n" +
" \"username\":\"rikialdipari@gmail.com\",\n" +
" \"password\":\"password\"\n" +
"}";
HttpEntity<String> entity = new HttpEntity<String>(bodyTesting,
headers);

93
ResponseEntity<String> exchange =
restTemplate.exchange("http://localhost:8082/api/user-login/login",
HttpMethod.POST, entity, String.class);

assertEquals(HttpStatus.OK, exchange.getStatusCode());
System.out.println("response =" + exchange.getBody());
}

Output-sukses

h. Refresh TOKEN

a. Step 1- lakukan login


Ambil atau copy paste value refresh_token

94
b. Step 2- to do refresh token

Param username dan password di happus.


Value grant_type gantu dengan refresh_token.
Tambahkan param refresh_token.

c. Upload dan simpan barang satu enpoind


Branch : uploadbarang
url : https://github.com/rikialdi/binar_grab.git
@Value("${app.uploadto.cdn}")//FILE_SHOW_RUL
private String UPLOADED_FOLDER;

95
@PostMapping(value = "/uploadsimpanbarang/{idsupplier}",consumes =
{"multipart/form-data", "application/json"})
public ResponseEntity<Map> uploadFile(
@RequestParam(value="file", required = true) MultipartFile file,
@PathVariable(value = "idsupplier") Long idsupplier,
@RequestParam(value="nama", required = true) String nama,
@RequestParam(value="stok", required = true) int stok,
@RequestParam(value="satuan", required = true) String satuan,
@RequestParam(value="harga", required = true) double harga
) {
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("ddMyyyyhhmmss");
String strDate = formatter.format(date);
String fileName = UPLOADED_FOLDER + strDate +
file.getOriginalFilename();
String fileNameforDOwnload = strDate + file.getOriginalFilename();
Path TO = Paths.get(fileName);
Map map = new HashMap();
try {
Files.copy(file.getInputStream(), TO); // pengolahan upload
disini :
// insert barang
Barang b = new Barang();
b.setNama(nama);
b.setStok(stok);
b.setSatuan(satuan);
b.setHarga(harga);
b.setFileName(fileNameforDOwnload);
map = barangService.insert(b, idsupplier);
} catch (Exception e) {
e.printStackTrace();
return new
ResponseEntity<Map>(templateResponse.templateEror("eror"), HttpStatus.OK);
}
return new ResponseEntity<Map>(templateResponse.templateSukses(map),
HttpStatus.OK);
}

96
d. Output

e. Validation Password saat register


Branch: validationPassword
url : https://github.com/rikialdi/binar_grab.git

a. Step 1- Pom.xml
<!--valdation-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!--digunakan untuk validasi password -->
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>1.6.0</version>
</dependency>

97
b. Step 2-application properties
Buatlah file baru di resources:

HISTORY_VIOLATION=Password matches one of %1$s previous passwords.


ILLEGAL_WORD=Password contains the dictionary word '%1$s'.
ILLEGAL_WORD_REVERSED=Password contains the reversed dictionary word
'%1$s'.
ILLEGAL_DIGEST_WORD=Password contains a dictionary word.
ILLEGAL_DIGEST_WORD_REVERSED=Password contains a reversed dictionary word.
ILLEGAL_MATCH=Password matches the illegal pattern '%1$s'.
ALLOWED_MATCH=Password must match pattern '%1$s'.
ILLEGAL_CHAR=Password %2$s the illegal character '%1$s'.
ALLOWED_CHAR=Password %2$s the illegal character '%1$s'.
ILLEGAL_QWERTY_SEQUENCE=Password contains the illegal QWERTY sequence
'%1$s'.
ILLEGAL_ALPHABETICAL_SEQUENCE=Password contains the illegal alphabetical
sequence '%1$s'.
ILLEGAL_NUMERICAL_SEQUENCE=Password contains the illegal numerical sequence
'%1$s'.
ILLEGAL_USERNAME=Password %2$s the user id '%1$s'.
ILLEGAL_USERNAME_REVERSED=Password %2$s the user id '%1$s' in reverse.
ILLEGAL_WHITESPACE=Password %2$s a whitespace character.
ILLEGAL_NUMBER_RANGE=Password %2$s the number '%1$s'.
ILLEGAL_REPEATED_CHARS=Password contains %3$s sequences of %1$s or more
repeated characters, but only %2$s allowed: %4$s.
INSUFFICIENT_UPPERCASE=Password must contain %1$s or more uppercase
characters.
INSUFFICIENT_LOWERCASE=Password must contain %1$s or more lowercase
characters.
INSUFFICIENT_ALPHABETICAL=Password must contain %1$s or more alphabetical
characters.
INSUFFICIENT_DIGIT=Password must contain %1$s or more digit characters.
INSUFFICIENT_SPECIAL=Password must contain %1$s or more special characters.
INSUFFICIENT_CHARACTERISTICS=Password matches %1$s of %3$s character rules,

98
but %2$s are required.
INSUFFICIENT_COMPLEXITY=Password meets %2$s complexity rules, but %3$s are
required.
INSUFFICIENT_COMPLEXITY_RULES=No rules have been configured for a password
of length %1$s.
SOURCE_VIOLATION=Password cannot be the same as your %1$s password.
TOO_LONG=Password must be no more than %2$s characters in length.
TOO_SHORT=Password must be %1$s or more characters in length.
TOO_MANY_OCCURRENCES=Password contains %2$s occurrences of the character
'%1$s', but at most %3$s are allowed.

c. Step 3- struktur kode

Class PasswordValueMatch
package com.binar.grab.controller.validationpass.annotation;

import
com.binar.grab.controller.validationpass.utils.PasswordFieldsValueMatchVali
dator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

import static java.lang.annotation.ElementType.*;


import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* <h2>PasswordValueMatch</h2>
*
* @author aek
*/
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordFieldsValueMatchValidator.class)
@Documented
public @interface PasswordValueMatch {

99
String message() default "Fields values don't match!";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };

String field();

String fieldMatch();

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface List {
PasswordValueMatch[] value();
}
}

Class ValidPassword
package com.binar.grab.controller.validationpass.annotation;

import
com.binar.grab.controller.validationpass.utils.PasswordConstraintValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;


import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* <h2>ValidPassword</h2>
*
* @author aek
*/
@Documented
@Constraint(validatedBy = PasswordConstraintValidator.class)
@Target({ TYPE, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface ValidPassword {

String message() default "Invalid Password";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};


}

Class PasswordConstraintValidator

100
package com.binar.grab.controller.validationpass.utils;

import com.binar.grab.controller.validationpass.annotation.ValidPassword;
import lombok.SneakyThrows;
import org.passay.*;
import org.passay.PropertiesMessageResolver;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

/**
* <h2>PasswordConstraintValidator</h2>
*
* @author aek
*/
public class PasswordConstraintValidator implements
ConstraintValidator<ValidPassword, String> {

@Override
public void initialize(final ValidPassword arg0) {

@SneakyThrows
@Override
public boolean isValid(String password, ConstraintValidatorContext
context) {

//customizing validation messages


Properties props = new Properties();
InputStream inputStream = getClass()
.getClassLoader().getResourceAsStream("passay.properties");
props.load(inputStream);
MessageResolver resolver = new PropertiesMessageResolver(props);

PasswordValidator validator = new PasswordValidator(resolver,


Arrays.asList(
/*
example : Aa1@231456
*/
// length between 8 and 16 characters
new LengthRule(8, 16),

// at least one upper-case character


new CharacterRule(EnglishCharacterData.UpperCase, 1),

// at least one lower-case character


new CharacterRule(EnglishCharacterData.LowerCase, 1),

// at least one digit character


new CharacterRule(EnglishCharacterData.Digit, 1),

// at least one symbol (special character)


new CharacterRule(EnglishCharacterData.Special, 1),

101
// no whitespace= tidak dengan spasi
new WhitespaceRule(),

// rejects passwords that contain a sequence of >= 5


characters alphabetical (e.g. abcdef)
//tidak boleh caracter berurut contoh abcde ,ini tidak
boleh, jika abczd : ini boleh
new IllegalSequenceRule(EnglishSequenceData.Alphabetical,
5, false),
// rejects passwords that contain a sequence of >= 5
characters numerical (e.g. 12345)
//tidak boleh numeric berurut contoh 12345 ,ini tidak
boleh, jika `12365` : ini boleh
new IllegalSequenceRule(EnglishSequenceData.Numerical, 5,
false)
));

RuleResult result = validator.validate(new PasswordData(password));

if (result.isValid()) {
return true;
}

List<String> messages = validator.getMessages(result);


String messageTemplate = String.join(",", messages);
context.buildConstraintViolationWithTemplate(messageTemplate)
.addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}

Class PasswordFieldsValueMatchValidator
package com.binar.grab.controller.validationpass.utils;

import
com.binar.grab.controller.validationpass.annotation.PasswordValueMatch;
import org.springframework.beans.BeanWrapperImpl;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
* <h2>PasswordFieldsValueMatchValidator</h2>
*
* @author aek
*/
public class PasswordFieldsValueMatchValidator implements
ConstraintValidator<PasswordValueMatch, Object> {

private String field;


private String fieldMatch;
private String message;

102
public void initialize(PasswordValueMatch constraintAnnotation) {
this.field = constraintAnnotation.field();
this.fieldMatch = constraintAnnotation.fieldMatch();
this.message = constraintAnnotation.message();
}

public boolean isValid(Object value,


ConstraintValidatorContext context) {

Object fieldValue = new BeanWrapperImpl(value)


.getPropertyValue(field);
Object fieldMatchValue = new BeanWrapperImpl(value)
.getPropertyValue(fieldMatch);

boolean isValid = false;


if (fieldValue != null) {
isValid = fieldValue.equals(fieldMatchValue);
}

if (!isValid) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate(message)
.addPropertyNode(field)
.addConstraintViolation();
context
.buildConstraintViolationWithTemplate(message)
.addPropertyNode(fieldMatch)
.addConstraintViolation();
}

return isValid;

Class Api Response


package com.binar.grab.controller.validationpass.web;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class ApiResponse {

private Object data;


private String message;
private boolean error = true;

public ApiResponse(Object data, String message){


this.data = data;
this.message = message;

103
}
}

Class BaseExceptionHandler
package com.binar.grab.controller.validationpass.web;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestControllerAdvice
public class BaseExceptionHandler {

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse
handleValidationExceptions(MethodArgumentNotValidException ex) {

Map<String, String> errors = new HashMap<>();

ex.getBindingResult().getFieldErrors().forEach(error -> {
if (errors.containsKey(error.getField())) {
errors.put(error.getField(), String.format("%s,
%s", errors.get(error.getField()), error.getDefaultMessage()));
} else {
errors.put(error.getField(),
error.getDefaultMessage());
}
}
);
return new ApiResponse(errors, "VALIDATION_FAILED");
}
}

d. Step 3 a – setting Character Password dimana?


Ada di class : PasswordConstraintValidator

104
e. Step 4 – cara menggunakan method validation password ?
Misal api yang digunakan adalah : api register, ingin validasi password

Pada controller register, tambhakn text yang di kotak merah dibawah ini :

Pada RegisterModel sebagai Requwat : tambahkan text yang di kotak


merah,seperti gmabar dibawah ini “

105
f. Tester
Failed:

Success:

106
g. Menampilkan eror validation hibernite
Branch : showErorHibernite
url : https://github.com/rikialdi/binar_grab.git

a. Strukture

107
apiResponse
package com.binar.grab.controller.validationpass.web;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class ApiResponse {

private Object data;


private String message;
private boolean error = true;

public ApiResponse(Object data, String message){


this.data = data;
this.message = message;
}
}

Handler
package com.binar.grab.controller.validationpass.web;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestControllerAdvice
public class BaseExceptionHandler {

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse
handleValidationExceptions(MethodArgumentNotValidException ex) {

Map<String, String> errors = new HashMap<>();

ex.getBindingResult().getFieldErrors().forEach(error -> {
if (errors.containsKey(error.getField())) {
errors.put(error.getField(), String.format("%s,
%s", errors.get(error.getField()), error.getDefaultMessage()));
} else {
errors.put(error.getField(),
error.getDefaultMessage());
}
}

108
);
return new ApiResponse(errors, "VALIDATION_FAILED");
}
}

b. Controller
Pada setiap controller tambahkan yang dikotak merah

c. Entity
Setiap req atau entity tambahkan yang dikotak merah dibawah ini

109
d. Testing

e. Valid email manual


blic boolean isValidEmail(String email)
{
String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\."+
"[a-zA-Z0-9_+&*-]+)*@" + "(?:[a-zA-Z0-9-]+\\.)+[a-z" +
"A-Z]{2,7}$";

Pattern pat = Pattern.compile(emailRegex);


return pat.matcher(email).matches();
}

f. Anotasi hibernite

a. @Max @Min :Batasin Request maksimal 5 huruf

import javax.validation.constraints.Max;

@Min(2)
@Max(100)
public String username;

110
b. @Size Batasin Request maksimal 5 huruf
import javax.validation.constraints.*;

@Size(
min = 5,
max = 14,
message = "The author email '${validatedValue}' must be between
{min} and {max} characters long"
)
public String email;

111
c. Mengenal Lambda Expression untuk Membuat Fungsi
Anonymous di Java
Ref: https://www.petanikode.com/java-lambda/

Istilah anonymous memang untuk menggambarkan sesuatu yang tak memiliki


nama. Nah di Java, juga ada fungsi anonymous dan class anonymous.

Apa itu fungsi anonymous?


Bagaimana cara membuatnya?
Mengapa harus menggunakan fungsi anonymous?

a. Apa itu Fungsi Anonymous?


Fungsi anonymous adalah fungsi yang tidak memiliki nama. Fungsi anonymous
di Java dikenal juga dengan lambda expression.
Fungsi anonymous biasanya dibuat hanya untuk sekali pakai.
Artinya, saat kita membuat fungsi anonymous, kita akan mengeksekusinya saat
itu juga. Tidak bisa dipanggil lagi seperti fungsi biasa.
Fungsi ini mulai ditambahkan pada JDK 8.
Jika kamu menggunakan JDK di bawah 8, maka kamu harus upgrade dulu agar
bisa menggunakan fungsi anonymous.

112
Oke, kalau begitu.. bagaimana cara membuat fungsi anonymous?

b. Cara Membuat Fungsi Anonymous di Java


Berikut ini adalah bentuk umum lambda expression atau fungsi anonymous di
Java:

Ini simbol-simbol yang perlu kamu ingat:

() -> {}

Keterangan:

 () tempat kita menaruh parameter.


 -> (operator lambda) simbol yang menandakan fungsi ini adalah
lambda/anonymous.
 {} body fungsinya.

Contoh:

(int x, int y) -> { return x + y }

Sebenarnya simbol yang perlu diingat adalah simbol lambda ( ->), karena simbol ini
yang membedakan fungsi anonymous dengan fungsi biasa.

113
Untuk tanda kurung yang ini () dengan yang ini {}, di fungsi biasa juga ada.

Mari kita lihat contohnya:

// ini fungsi biasa


int jumlahkan(int a, int b){
return a + b;
}

// ini fungsi anonymous


(int a, int b) -> { return a + b }
Bahkan tanpa kurung kurawal juga bisa:

(int a, int b) -> return a + b;

Karena cuma ada satu baris ekspresi, maka tanda kurung {} boleh tidak ada.

Oh iya, fungsi anonymous dapat kita buat di berbagai tempat seperti:

 Pada Deklarasi variabel;Contoh:

int variabel = () -> { return 0 };

 Pada pengisian variabel dan array;Contoh:

int variabel;
int arr;
variabel = () -> { return 0 };
arr = () -> { return {0,4,3,2,1} };

 Pada saat mengembalikan nilai dengan return;Contoh:

int methodName(){
return () -> { return 0 };
}

 Pada body lambda itu sendiri;Contoh:

() -> {
return () -> 5 + 2;

114
};

 Pada ekspresi kondisional (?:)Contoh:

String jawab = (int x) -> { x < 10} ? () -> return "yes": () -> retu "no";

c. Mengapa Harus Pakai Fungsi Anonymous?

Lambda expression atau fungsi anonymous sebenarnya hadir untuk


menyempurnakan class anonymous. 1

Class anonymous biasanya digunakan untuk mengimplementasikan interface


dan class abstrak.

Tapi masalahnya:

Saat interface hanya memiliki satu method saja untuk diimplementasikan. Kita harus
membuat class (anonymous) baru.

Padahal kan kita cuma butuh method-nya saja.

Di sini lah saat yang tepat untuk menggunakan fungsi anonymous atau lambda
expression.

Bahkan Netbeans juga akan menyarankan menggunakan lambda expression apabila


menemukan kasus ini.

d. Contoh Program Fungsi Anonymous

Buatlah proyek baru di Netbeans dengan nama ContohLambda.

Setelah itu, buat sebuah interface baru pada pacakage <default package> dengan


nama Clickable dan isi kodenya seperti ini:

interface Clickable {
115
void onClick();
}

Setelah itu, buatlah class Button dengan isi seperti ini:

public class Button {


private Clickable action;

void setClickAction(Clickable action){


this.action = action;
}

void doClick(){
action.onClick();
}
}

Terakhir, buatlah class Main dengan isi seperti ini:

public class Main {

public static void main(String[] args) {

Button btn = new Button();

// membuat class anonymous untuk implementasi interface


btn.setClickAction(new Clickable() {
@Override
public void onClick() {
System.out.println("Tombol sudah diklik!");
System.out.println("Yay!");
}
});

// mencoba klik tombol


btn.doClick();

}
}

116
Perhatikan baris kode yang saya tandai di atas..

Di sana kita menggunakan class anonymous untuk mengimplementasikan


interface Clickable.

Nah, kalau mau lebih sederhana.. kita bisa pakai lambda expression.

Sekarang coba ubah kodenya menjadi seperti ini:

public class Main {

public static void main(String[] args) {

Button btn = new Button();

// membuat class anonymous untuk implementasi interface


btn.setClickAction(() -> {
System.out.println("Tombol sudah diklik!");
System.out.println("Yay!");
});

// mencoba klik tombol


btn.doClick();

}
}

Lebih sederhana yang mana?

Tentu saja yang menggunakan lambda expression.

Berikut ini hasil output dari program tersebut:

e. Akses Variabel untuk Fungsi Anonymous

Pada dasarnya, fungsi anonymous adalah bentuk sederhana dari class anonymous.

117
Jadi dia akan memiliki akses variabel yang sama seperti class anonymous.

Kalau tidak percaya, coba buktikan dengan membuat variabel di luar fungsi
anonymous.

Lalu akses variabel tersebut dari dalam fungsi anonymous.

public class Main {

public static void main(String[] args) {

Button btn = new Button();


String name = "Petani Kode";

// membuat class anonymous untuk implementasi interface


btn.setClickAction(() -> {
System.out.println("Tombol sudah diklik!");
System.out.println("Yay!");
System.out.println("Hello " + name);
});

// mencoba klik tombol


btn.doClick();

}
}

Fungsi anonymous akan bisa mengakses variabel yang berada di dalam class
(variabel global), dan lokal yang ada di method tempat anonymous class digunakan.

f. Akhir Kata…

Intinya:

Fungsi anonymous hanya bisa digunakan saat ingin mengimplementasikan interface


yang memiliki satu method.

118
Jika interface itu memiliki lebih dari satu method, maka sebaiknya pakai class
anonymous.

Daftar Pustaka
https://www.it-swarm-id.com/id/authentication/apa-perbedaan-utama-
antara-otentikasi-jwt-dan-oauth/828156483/
https://undebugable.blogspot.com/2020/12/membuat-authentikasi-
berbasis-token.html
https://www.bezkoder.com/spring-boot-jwt-mysql-spring-security-
architecture/
https://www.bezkoder.com/jwt-json-web-token/

119

You might also like