diff --git a/pom.xml b/pom.xml
index 3722d8f..4b310cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,7 +14,7 @@
org.springframework.boot
spring-boot-starter-parent
- 2.0.2.RELEASE
+ 2.0.0.RELEASE
@@ -33,6 +33,16 @@
org.springframework.boot
spring-boot-starter-webflux
+
+ io.jsonwebtoken
+ jjwt
+ 0.7.0
+
+
+ org.projectlombok
+ lombok
+
+
org.springframework.boot
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/entity/Message.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/entity/Message.java
new file mode 100644
index 0000000..3a71c76
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/entity/Message.java
@@ -0,0 +1,21 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Data @NoArgsConstructor @AllArgsConstructor @ToString
+public class Message {
+
+ private String content;
+
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/entity/User.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/entity/User.java
new file mode 100644
index 0000000..b2ddf7e
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/entity/User.java
@@ -0,0 +1,103 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import id.web.ard.springbootwebfluxjjwt.security.model.Role;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@ToString @AllArgsConstructor @NoArgsConstructor
+public class User implements UserDetails {
+
+ private String username;
+
+ private String password;
+
+ private Boolean enabled;
+
+ private List roles;
+
+ public User(String username) {
+ this.username = username;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ //==============================
+ @Override
+ public boolean isAccountNonExpired() {
+ return false;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return false;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return this.roles.stream().map(authority -> new SimpleGrantedAuthority(authority.name())).collect(Collectors.toList());
+ }
+ //==============================
+
+ @JsonIgnore
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @JsonProperty
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(Boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/rest/AuthenticationREST.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/rest/AuthenticationREST.java
new file mode 100644
index 0000000..41a8b7b
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/rest/AuthenticationREST.java
@@ -0,0 +1,49 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.rest;
+
+import id.web.ard.springbootwebfluxjjwt.security.JWTUtil;
+import id.web.ard.springbootwebfluxjjwt.security.PBKDF2Encoder;
+import id.web.ard.springbootwebfluxjjwt.security.model.AuthRequest;
+import id.web.ard.springbootwebfluxjjwt.security.model.AuthResponse;
+import id.web.ard.springbootwebfluxjjwt.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@RestController
+public class AuthenticationREST {
+
+ @Autowired
+ private JWTUtil jwtTokenUtil;
+
+ @Autowired
+ private PBKDF2Encoder passwordEncoder;
+
+ @Autowired
+ private UserService userRepository;
+
+ @RequestMapping(value = "auth", method = RequestMethod.POST)
+ public Mono> auth(@RequestBody AuthRequest ar) {
+ return userRepository.findByUsername(ar.getUsername()).map((userDetails) -> {
+ if (passwordEncoder.encode(ar.getPassword()).equals(userDetails.getPassword())) {
+ return ResponseEntity.ok(new AuthResponse(jwtTokenUtil.generateToken(userDetails)));
+ } else {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/rest/ResourceREST.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/rest/ResourceREST.java
new file mode 100644
index 0000000..5f55b7f
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/rest/ResourceREST.java
@@ -0,0 +1,39 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.rest;
+
+import id.web.ard.springbootwebfluxjjwt.entity.Message;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@RestController
+public class ResourceREST {
+
+ @RequestMapping(value = "resource/user", method = RequestMethod.GET)
+ @PreAuthorize("hasRole('USER')")
+ public Mono> user() {
+ return Mono.just(ResponseEntity.ok(new Message("Content for user")));
+ }
+
+ @RequestMapping(value = "resource/admin", method = RequestMethod.GET)
+ @PreAuthorize("hasRole('ADMIN')")
+ public Mono> admin() {
+ return Mono.just(ResponseEntity.ok(new Message("Content for admin")));
+ }
+
+ @RequestMapping(value = "resource/user-or-admin", method = RequestMethod.GET)
+ @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
+ public Mono> userOrAdmin() {
+ return Mono.just(ResponseEntity.ok(new Message("Content for user or admin")));
+ }
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/AuthenticationManager.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/AuthenticationManager.java
new file mode 100644
index 0000000..14460e8
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/AuthenticationManager.java
@@ -0,0 +1,62 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.security;
+
+import id.web.ard.springbootwebfluxjjwt.security.model.Role;
+import io.jsonwebtoken.Claims;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.springframework.security.web.server.context.ServerSecurityContextRepository;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Component
+public class AuthenticationManager implements ReactiveAuthenticationManager {
+
+ @Autowired
+ private JWTUtil jwtUtil;
+
+ @Override
+ public Mono authenticate(Authentication authentication) {
+ String authToken = authentication.getCredentials().toString();
+
+ String username = null;
+ try {
+ username = jwtUtil.getUsernameFromToken(authToken);
+ } catch (Exception e) {
+ username = null;
+ }
+ if (username != null) {
+ Claims claims = jwtUtil.getAllClaimsFromToken(authToken);
+ if (jwtUtil.validateToken(authToken)) {
+ List rolesMap = claims.get("role", List.class);
+ List roles = new ArrayList<>();
+ for (String rolemap : rolesMap) {
+ roles.add(Role.valueOf(rolemap));
+ }
+ UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
+ username,
+ null,
+ roles.stream().map(authority -> new SimpleGrantedAuthority(authority.name())).collect(Collectors.toList())
+ );
+ return Mono.just(auth);
+ } else {
+ return Mono.empty();
+ }
+ } else {
+ return Mono.empty();
+ }
+ }
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/CORSFilter.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/CORSFilter.java
new file mode 100644
index 0000000..cbca046
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/CORSFilter.java
@@ -0,0 +1,35 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.security;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Component
+public class CORSFilter implements WebFilter{
+
+ @Override
+ public Mono filter(ServerWebExchange swe, WebFilterChain wfc) {
+
+ //CORS
+ swe.getResponse().getHeaders().add("Access-Control-Allow-Origin", "*");
+ if (swe.getRequest().getHeaders().get("Access-Control-Request-Method") != null && "OPTIONS".equalsIgnoreCase(swe.getRequest().getMethod().toString())) {
+ swe.getResponse().getHeaders().add("Access-Control-Allow-Headers", "Authorization");
+ swe.getResponse().getHeaders().add("Access-Control-Allow-Headers", "Content-Type");
+ swe.getResponse().getHeaders().add("Access-Control-Max-Age", "1");
+ swe.getResponse().getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
+ }
+
+ return wfc.filter(swe);
+ }
+
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/JWTUtil.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/JWTUtil.java
new file mode 100644
index 0000000..5206f77
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/JWTUtil.java
@@ -0,0 +1,75 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.security;
+
+import id.web.ard.springbootwebfluxjjwt.entity.User;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Component
+public class JWTUtil implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Value("${springbootwebfluxjjwt.jjwt.secret}")
+ private String secret;
+
+ @Value("${springbootwebfluxjjwt.jjwt.expiration}")
+ private String expirationTime;
+
+ public Claims getAllClaimsFromToken(String token) {
+ return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
+ }
+
+ public String getUsernameFromToken(String token) {
+ return getAllClaimsFromToken(token).getSubject();
+ }
+
+ public Date getExpirationDateFromToken(String token) {
+ return getAllClaimsFromToken(token).getExpiration();
+ }
+
+ private Boolean isTokenExpired(String token) {
+ final Date expiration = getExpirationDateFromToken(token);
+ return expiration.before(new Date());
+ }
+
+ public String generateToken(User user) {
+ Map claims = new HashMap<>();
+ claims.put("role", user.getRoles());
+ claims.put("enable", user.getEnabled());
+ return doGenerateToken(claims, user.getUsername());
+ }
+
+ private String doGenerateToken(Map claims, String username) {
+ Long expirationTimeLong = Long.parseLong(expirationTime); //in second
+
+ final Date createdDate = new Date();
+ final Date expirationDate = new Date(createdDate.getTime() + expirationTimeLong * 1000);
+ return Jwts.builder()
+ .setClaims(claims)
+ .setSubject(username)
+ .setIssuedAt(createdDate)
+ .setExpiration(expirationDate)
+ .signWith(SignatureAlgorithm.HS512, secret)
+ .compact();
+ }
+
+ public Boolean validateToken(String token) {
+ return !isTokenExpired(token);
+ }
+
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/PBKDF2Encoder.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/PBKDF2Encoder.java
new file mode 100644
index 0000000..60df094
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/PBKDF2Encoder.java
@@ -0,0 +1,48 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.security;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Base64;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Component
+public class PBKDF2Encoder implements PasswordEncoder{
+
+ @Value("${springbootwebfluxjjwt.password.secret}")
+ private String secret;
+
+ /**
+ * More info (https://www.owasp.org/index.php/Hashing_Java)
+ * @param cs password
+ * @return encoded password
+ */
+ @Override
+ public String encode(CharSequence cs) {
+ try {
+ byte[] result = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512")
+ .generateSecret(new PBEKeySpec(cs.toString().toCharArray(), secret.getBytes(), 33, 256))
+ .getEncoded();
+ return Base64.getEncoder().encodeToString(result);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public boolean matches(CharSequence cs, String string) {
+ return encode(cs).equals(string);
+ }
+
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/SecurityContextRepository.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/SecurityContextRepository.java
new file mode 100644
index 0000000..a2bc194
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/SecurityContextRepository.java
@@ -0,0 +1,50 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextImpl;
+import org.springframework.security.web.server.context.ServerSecurityContextRepository;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Component
+public class SecurityContextRepository implements ServerSecurityContextRepository{
+
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ @Override
+ public Mono save(ServerWebExchange swe, SecurityContext sc) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public Mono load(ServerWebExchange swe) {
+ ServerHttpRequest request = swe.getRequest();
+ String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+
+ if (authHeader != null && authHeader.startsWith("Bearer ")) {
+ String authToken = authHeader.substring(7);
+ Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
+ return this.authenticationManager.authenticate(auth).map((authentication) -> {
+ return new SecurityContextImpl(authentication);
+ });
+ } else {
+ return Mono.empty();
+ }
+ }
+
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/UserDetailsServiceImpl.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/UserDetailsServiceImpl.java
new file mode 100644
index 0000000..8e809c9
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/UserDetailsServiceImpl.java
@@ -0,0 +1,29 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.security;
+
+import id.web.ard.springbootwebfluxjjwt.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Service
+public class UserDetailsServiceImpl implements ReactiveUserDetailsService {
+
+ @Autowired
+ private UserService userRepository;
+
+ @Override
+ public Mono findByUsername(String username) {
+ return userRepository.findUserDetailsByUsername(username);
+ }
+
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/WebSecurityConfig.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/WebSecurityConfig.java
new file mode 100644
index 0000000..6051932
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/WebSecurityConfig.java
@@ -0,0 +1,39 @@
+package id.web.ard.springbootwebfluxjjwt.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
+public class WebSecurityConfig {
+
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ @Autowired
+ private SecurityContextRepository securityContextRepository;
+
+ @Bean
+ public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) {
+ return http.csrf().disable()
+ .formLogin().disable()
+ .httpBasic().disable()
+ .authenticationManager(authenticationManager)
+ .securityContextRepository(securityContextRepository)
+ //.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC)
+ .authorizeExchange()
+ .pathMatchers("/auth").permitAll()
+ .anyExchange().authenticated()
+ .and().build();
+
+ }
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/model/AuthRequest.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/model/AuthRequest.java
new file mode 100644
index 0000000..38bc5d8
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/model/AuthRequest.java
@@ -0,0 +1,23 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.security.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Data @NoArgsConstructor @AllArgsConstructor @ToString
+public class AuthRequest {
+
+ private String username;
+
+ private String password;
+
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/model/AuthResponse.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/model/AuthResponse.java
new file mode 100644
index 0000000..3314e57
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/model/AuthResponse.java
@@ -0,0 +1,21 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.security.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Data @NoArgsConstructor @AllArgsConstructor @ToString
+public class AuthResponse {
+
+ private String token;
+
+}
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/security/model/Role.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/model/Role.java
new file mode 100644
index 0000000..f5823a6
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/security/model/Role.java
@@ -0,0 +1,13 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.security.model;
+
+/**
+ *
+ * @author ardiansyah
+ */
+public enum Role {
+ ROLE_USER, ROLE_ADMIN
+}
\ No newline at end of file
diff --git a/src/main/java/id/web/ard/springbootwebfluxjjwt/service/UserService.java b/src/main/java/id/web/ard/springbootwebfluxjjwt/service/UserService.java
new file mode 100644
index 0000000..c76f049
--- /dev/null
+++ b/src/main/java/id/web/ard/springbootwebfluxjjwt/service/UserService.java
@@ -0,0 +1,54 @@
+/*
+ * Ardiansyah | http://ard.web.id
+ *
+ */
+package id.web.ard.springbootwebfluxjjwt.service;
+
+import id.web.ard.springbootwebfluxjjwt.entity.User;
+import id.web.ard.springbootwebfluxjjwt.security.model.Role;
+import java.util.Arrays;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+/**
+ *
+ * @author ardiansyah
+ */
+@Service
+public class UserService {
+
+ // this is just an example, you can load the user from the database from the repository
+
+ //username:passwowrd -> user:user
+ private final String userUsername = "user";// password: user
+ private final User user = new User(userUsername, "cBrlgyL2GI2GINuLUUwgojITuIufFycpLG4490dhGtY=", true, Arrays.asList(Role.ROLE_USER));
+
+ //username:passwowrd -> admin:admin
+ private final String adminUsername = "admin";// password: admin
+ private final User admin = new User(adminUsername, "dQNjUIMorJb8Ubj2+wVGYp6eAeYkdekqAcnYp+aRq5w=", true, Arrays.asList(Role.ROLE_ADMIN));
+
+
+ public Mono findUserDetailsByUsername(String username) {
+ if (username.equals(userUsername)) {
+ return Mono.just(user);
+ } else if (username.equals(adminUsername)) {
+ return Mono.just(admin);
+ } else {
+ return Mono.empty();
+ }
+ }
+
+ public Mono findByUsername(String username) {
+ if (username.equals(userUsername)) {
+ return Mono.just(user);
+ } else if (username.equals(adminUsername)) {
+ return Mono.just(admin);
+ } else {
+ return Mono.empty();
+ }
+ }
+
+
+
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index e69de29..21614dd 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+springbootwebfluxjjwt.password.secret=mysecret
+springbootwebfluxjjwt.jjwt.secret=mysecret
+springbootwebfluxjjwt.jjwt.expiration=28800
\ No newline at end of file