Spring Boot Register Login Logout example: Rest API with MySQL and JWT & AngularJS

 Hi sobat blogger semua. saya ingin membuat tutorial example REST API springboot Register, Login, Logout yang mendukung JWT token dan HttpOnly Cookies. 

Nanti sobat Blogger akan mengetahui tentang :

  • Flow for User Login dan Registration dengan JWT and Cookies
  • Springboot rest api arsitektur dengan Spring Security
  • Konfigurasi Spring Security dengan JWT
  • Definisi data model dan asosiasi dengan autentifikasi dan autorisasi
  • Penggunaan Spring Data JPA dengan MYSQL database
Overview Springboot application :
  • User bisa Register akun baru dan Login dengan USERNAME & PASSWORD
  • USER Role (admin, moderator, dan user), autorisasi user untuk mengakses Resource.

API yang dibutuhkan untuk metode login dan register :

MethodsUrlsActions
POST/api/auth/signupsignup new account
POST/api/auth/signinlogin an account
POST/api/auth/signoutlogout the account
GET/api/test/allretrieve public content
GET/api/test/useraccess User’s content
GET/api/test/modaccess Moderator’s content
GET/api/test/adminaccess Admin’s content

Diagram Flow Springboot Login dan Registration serta proses Authorization



JWT disimpan dalam bentuk Cookiesdan butuh implementasi refresh token. 

Spring Boot Rest API Server Architecture with Spring Security

spring security : 


Technology

  • JDK 17
  • Springboot 3.1.0
  • jjwt 0.11.5
  • MYSQL

Project Structure




Setup new Spring Boot Login project

Dependencies pom.xml :

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>3.1.0</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<groupId>com.joko</groupId>

<artifactId>SpringbootLogin-RestAPI-with-MYSQL-JWT</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>SpringbootLogin-RestAPI-with-MYSQL-JWT</name>

<description>SpringbootLogin-RestAPI-with-MYSQL-JWT</description>

<properties>

<java.version>17</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-security</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-validation</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-oauth2-client</artifactId>

</dependency>


<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt-api</artifactId>

<version>0.11.5</version>

</dependency>

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt-impl</artifactId>

<version>0.11.5</version>

<scope>runtime</scope>

</dependency>


<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt-jackson</artifactId>

<version>0.11.5</version>

<scope>runtime</scope>

</dependency>



<dependency>

<groupId>com.mysql</groupId>

<artifactId>mysql-connector-j</artifactId>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-test</artifactId>

<scope>test</scope>

</dependency>

</dependencies>


<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>


</project>



Configure Spring Datasource, JPA, App properties

application.properties :

server.port=8090


spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false

spring.datasource.username= root

spring.datasource.password= joko24051992


spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL8Dialect

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto= update

spring.jpa.generate-ddl=true

spring.jpa.show-sql=true

spring.jpa.open-in-view=false


# App Properties

joko.app.jwtCookie= jokokoder

joko.app.jwtSecret= 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970

joko.app.jwtExpirationMs= 86400000


Buat model atau entities project :
di dalam package com.joko.models ada 3 file :

ERole.java :

package com.joko.models;


public enum ERole {

ROLE_USER,

ROLE_MODERATOR,

ROLE_ADMIN

}



Role.java :

package com.joko.models;


import jakarta.persistence.Column;

import jakarta.persistence.Entity;

import jakarta.persistence.EnumType;

import jakarta.persistence.Enumerated;

import jakarta.persistence.GeneratedValue;

import jakarta.persistence.GenerationType;

import jakarta.persistence.Id;

import jakarta.persistence.Table;


@Entity

@Table(name = "roles")

public class Role {


@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Integer id;


@Enumerated(EnumType.STRING)

@Column(length = 20)

private ERole name;


public Role() {

super();

}


public Integer getId() {

return id;

}


public void setId(Integer id) {

this.id = id;

}


public ERole getName() {

return name;

}


public void setName(ERole name) {

this.name = name;

}

}




User.java :

package com.joko.models;


import java.util.Set;

import java.util.HashSet;


import jakarta.persistence.Entity;

import jakarta.persistence.FetchType;

import jakarta.persistence.GeneratedValue;

import jakarta.persistence.GenerationType;

import jakarta.persistence.Id;

import jakarta.persistence.JoinTable;

import jakarta.persistence.JoinColumn;

import jakarta.persistence.ManyToMany;

import jakarta.persistence.Table;

import jakarta.persistence.UniqueConstraint;

import jakarta.validation.constraints.Email;

import jakarta.validation.constraints.NotBlank;

import jakarta.validation.constraints.Size;


@Entity

@Table(name = "users",

uniqueConstraints = {

@UniqueConstraint(columnNames = "username"),

@UniqueConstraint(columnNames = "email")

})

public class User {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;


@NotBlank

@Size(max = 20)

private String username;


@NotBlank

@Size(max = 50)

@Email

private String email;


@NotBlank

@Size(max = 120)

private String password;


@ManyToMany(fetch = FetchType.LAZY)

@JoinTable(name = "user_roles",

joinColumns = @JoinColumn(name = "user_id"),

inverseJoinColumns = @JoinColumn(name = "role_id"))

private Set<Role> roles = new HashSet<>();


public User() {

super();

// TODO Auto-generated constructor stub

}


public User(@NotBlank @Size(max = 20) String username, @NotBlank @Size(max = 50) @Email String email,

@NotBlank @Size(max = 120) String password) {

super();

this.username = username;

this.email = email;

this.password = password;

}


public Long getId() {

return id;

}


public void setId(Long id) {

this.id = id;

}


public String getUsername() {

return username;

}


public void setUsername(String username) {

this.username = username;

}


public String getEmail() {

return email;

}


public void setEmail(String email) {

this.email = email;

}


public String getPassword() {

return password;

}


public void setPassword(String password) {

this.password = password;

}


public Set<Role> getRoles() {

return roles;

}


public void setRoles(Set<Role> roles) {

this.roles = roles;

}

}



create 2 repositories : UserRepository & RoleRepository extends JpaRepository 
RoleRepository.java dengan model interface :

package com.joko.repository;


import java.util.Optional;


import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.stereotype.Repository;


import com.joko.models.ERole;

import com.joko.models.Role;


@Repository

public interface RoleRepository extends JpaRepository<Role, Long>{

Optional<Role> findByName(ERole name);

}


UserRepository .java dengan model interface :

package com.joko.repository;


import java.util.Optional;


import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.stereotype.Repository;


import com.joko.models.User;


@Repository

public interface UserRepository extends JpaRepository<User, Long>{

Optional<User> findByUsername(String username);


Boolean existsByUsername(String username);


Boolean existsByEmail(String email);

Optional <User> findByEmail(String email);

}



Configure Spring Security

com.joko.security :
WebSecurityConfig.java :

package com.joko.security;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.security.authentication.AuthenticationManager;

import org.springframework.security.authentication.dao.DaoAuthenticationProvider;

import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;

import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.config.http.SessionCreationPolicy;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.security.web.SecurityFilterChain;

import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


import com.joko.security.jwt.AuthEntryPointJwt;

import com.joko.security.jwt.AuthTokenFilter;

import com.joko.security.services.UserDetailsServiceImpl;



// WebSecurityConfigurerAdapter is Deprecated in Spring Boot 2.7


@Configuration

@EnableMethodSecurity

public class WebSecurityConfig {

@Autowired

UserDetailsServiceImpl userDetailsService;

@Autowired

private AuthEntryPointJwt unauthorizedHandler;

@Bean

public AuthTokenFilter authenticationJwtTokenFilter() {

return new AuthTokenFilter();

}

// @Override

// public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {

// authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

// }// version springboot 2.7 ke bawah

@Bean

public DaoAuthenticationProvider authenticationProvider() {

DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

authProvider.setUserDetailsService(userDetailsService);

authProvider.setPasswordEncoder(passwordEncoder());

return authProvider;

}

// @Bean

// @Override

// public AuthenticationManager authenticationManagerBean() throws Exception {

// return super.authenticationManagerBean();

// }

@Bean

public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {

return authConfig.getAuthenticationManager();

}


@Bean

public PasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();

}

// @Override

// protected void configure(HttpSecurity http) throws Exception {

// http.cors().and().csrf().disable()

// .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()

// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

// .authorizeRequests().antMatchers("/api/auth/**").permitAll()

// .antMatchers("/api/test/**").permitAll()

// .anyRequest().authenticated();

//

// http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

// }

@Bean

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

http.csrf(csrf -> csrf.disable())

.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))

.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

.authorizeHttpRequests(auth ->

auth.requestMatchers("/api/auth/**").permitAll()

.requestMatchers("/api/test/**").permitAll()

.anyRequest().authenticated()

);

http.authenticationProvider(authenticationProvider());

http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

return http.build();

}

}


/*

Currently, if you want to use WebSecurityConfigurerAdapter, just downgrade Spring Boot to 2.6 or older versions.

But take note that WebSecurityConfigurerAdapter is getting deprecated in Spring Boot for new approach:

Component-based security configuration, and you may need to update your Web Security Config class in Spring Security without the WebSecurityConfigurerAdapter.

*/


Implement UserDetails & UserDetailsService

security/services/UserDetailsImpl.java

package com.joko.security.services;


import java.util.Collection;

import java.util.List;

import java.util.Objects;

import java.util.stream.Collectors;


import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.authority.SimpleGrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;


import com.fasterxml.jackson.annotation.JsonIgnore;

import com.joko.models.User;


public class UserDetailsImpl implements UserDetails{

private static final long serialVersionUID = 1L;


private Long id;


private String username;


private String email;


@JsonIgnore

private String password;


private Collection<? extends GrantedAuthority> authorities;


public UserDetailsImpl(Long id, String username, String email, String password,

Collection<? extends GrantedAuthority> authorities) {

super();

this.id = id;

this.username = username;

this.email = email;

this.password = password;

this.authorities = authorities;

}

public static UserDetailsImpl build(User user) {

List<GrantedAuthority> authorities = user.getRoles().stream()

.map(role -> new SimpleGrantedAuthority(role.getName().name()))

.collect(Collectors.toList());


return new UserDetailsImpl(

user.getId(),

user.getUsername(),

user.getEmail(),

user.getPassword(),

authorities);

}

@Override

public Collection<? extends GrantedAuthority> getAuthorities() {

return authorities;

}

public Long getId() {

return id;

}


public String getEmail() {

return email;

}


@Override

public String getPassword() {

return password;

}


@Override

public String getUsername() {

return username;

}


@Override

public boolean isAccountNonExpired() {

return true;

}


@Override

public boolean isAccountNonLocked() {

return true;

}


@Override

public boolean isCredentialsNonExpired() {

return true;

}


@Override

public boolean isEnabled() {

return true;

}

@Override

public boolean equals(Object o) {

if (this == o)

return true;

if (o == null || getClass() != o.getClass())

return false;

UserDetailsImpl user = (UserDetailsImpl) o;

return Objects.equals(id, user.id);

}

}


security/services/UserDetailsServiceImpl.java

package com.joko.security.services;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.core.userdetails.UsernameNotFoundException;

import org.springframework.stereotype.Service;


import com.joko.models.User;

import com.joko.repository.UserRepository;


import jakarta.transaction.Transactional;


@Service

public class UserDetailsServiceImpl implements UserDetailsService{

@Autowired

UserRepository userRepository;


@Override

@Transactional

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

User user = userRepository.findByUsername(username)

.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));


return UserDetailsImpl.build(user);

}

/*

* private final static String USER_NOT_FOUND_MSG =

* "user with email %s not found"; private final UserRepository userRepository;

*

* @Override public UserDetails loadUserByUsername(String email) throws

* UsernameNotFoundException { return userRepository.findByEmail(email)

* .orElseThrow(() -> new UsernameNotFoundException(String.format(

* USER_NOT_FOUND_MSG, email ))); }

*/

//Optional <User> user = userRepository.findByEmail(email);

// if (user.isEmpty()) user = userRepository.findByUsername(email);

//if you are using non optional you just check if the user==null instead of isEmp

}



Filter the Requests

security/jwt/AuthTokenFilter.java

package com.joko.security.jwt;


import java.io.IOException;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.context.SecurityContextHolder;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;

import org.springframework.web.filter.OncePerRequestFilter;


import com.joko.security.services.UserDetailsServiceImpl;



import jakarta.servlet.FilterChain;

import jakarta.servlet.ServletException;

import jakarta.servlet.http.HttpServletRequest;

import jakarta.servlet.http.HttpServletResponse;


public class AuthTokenFilter extends OncePerRequestFilter{

@Autowired

private JwtUtils jwtUtils;


@Autowired

private UserDetailsServiceImpl userDetailsService;


private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)

throws ServletException, IOException {

try {

String jwt = parseJwt(request);

if (jwt != null && jwtUtils.validateJwtToken(jwt)) {

String username = jwtUtils.getUserNameFromJwtToken(jwt);


UserDetails userDetails = userDetailsService.loadUserByUsername(username);

UsernamePasswordAuthenticationToken authentication =

new UsernamePasswordAuthenticationToken(userDetails,

null,

userDetails.getAuthorities());

authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));


SecurityContextHolder.getContext().setAuthentication(authentication);

}

} catch (Exception e) {

logger.error("Cannot set user authentication: {}", e);

}


filterChain.doFilter(request, response);

}


private String parseJwt(HttpServletRequest request) {

String jwt = jwtUtils.getJwtFromCookies(request);

return jwt;

}


}


security/jwt/JwtUtils.java

package com.joko.security.jwt;


import java.security.Key;

import java.util.Date;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.http.ResponseCookie;

import org.springframework.stereotype.Component;

import org.springframework.web.util.WebUtils;


import com.joko.security.services.UserDetailsImpl;


import io.jsonwebtoken.*;

import io.jsonwebtoken.io.Decoders;

import io.jsonwebtoken.security.Keys;

import jakarta.servlet.http.Cookie;

import jakarta.servlet.http.HttpServletRequest;


@Component

public class JwtUtils {


private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);


@Value("${joko.app.jwtSecret}")

private String jwtSecret;


@Value("${joko.app.jwtExpirationMs}")

private int jwtExpirationMs;


@Value("${joko.app.jwtCookie}")

private String jwtCookie;


public String getJwtFromCookies(HttpServletRequest request) {

Cookie cookie = WebUtils.getCookie(request, jwtCookie);

if (cookie != null) {

return cookie.getValue();

} else {

return null;

}

}


public ResponseCookie generateJwtCookie(UserDetailsImpl userPrincipal) {

String jwt = generateTokenFromUsername(userPrincipal.getUsername());

ResponseCookie cookie = ResponseCookie.from(jwtCookie, jwt).path("/api").maxAge(24 * 60 * 60).httpOnly(true).build();

return cookie;

}


public ResponseCookie getCleanJwtCookie() {

ResponseCookie cookie = ResponseCookie.from(jwtCookie, null).path("/api").build();

return cookie;

}


public String getUserNameFromJwtToken(String token) {

return Jwts.parserBuilder().setSigningKey(getSignInKey()).build().parseClaimsJws(token).getBody().getSubject();

}

private Key getSignInKey() {

byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);

return Keys.hmacShaKeyFor(keyBytes);

}


public boolean validateJwtToken(String authToken) {

try {

Jwts.parserBuilder().setSigningKey(getSignInKey()).build().parse(authToken);

return true;

} /*

* catch (SignatureException e) { logger.error("Invalid JWT signature: {}",

* e.getMessage()); }

*/

catch (MalformedJwtException e) {

logger.error("Invalid JWT token: {}", e.getMessage());

} catch (ExpiredJwtException e) {

logger.error("JWT token is expired: {}", e.getMessage());

} catch (UnsupportedJwtException e) {

logger.error("JWT token is unsupported: {}", e.getMessage());

} catch (IllegalArgumentException e) {

logger.error("JWT claims string is empty: {}", e.getMessage());

}


return false;

}

public String generateTokenFromUsername(String username) {

return Jwts.builder()

.setSubject(username)

.setIssuedAt(new Date())

.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))

.signWith(getSignInKey(), SignatureAlgorithm.HS256)

.compact();

}

}


security/jwt/AuthEntryPointJwt.java

package com.joko.security.jwt;


import java.io.IOException;

import java.util.HashMap;

import java.util.Map;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.http.MediaType;

import org.springframework.security.core.AuthenticationException;

import org.springframework.security.web.AuthenticationEntryPoint;

import org.springframework.stereotype.Component;


import jakarta.servlet.ServletException;

import jakarta.servlet.http.HttpServletRequest;

import jakarta.servlet.http.HttpServletResponse;


import com.fasterxml.jackson.databind.ObjectMapper;


@Component

public class AuthEntryPointJwt implements AuthenticationEntryPoint{


private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);

@Override

public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)

throws IOException, ServletException {

logger.error("Unauthorized error: {}", authException.getMessage());

response.setContentType(MediaType.APPLICATION_JSON_VALUE);

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

final Map<String, Object> body = new HashMap<>();

body.put("status", HttpServletResponse.SC_UNAUTHORIZED);

body.put("error", "Unauthorized");

body.put("message", authException.getMessage());

body.put("path", request.getServletPath());


final ObjectMapper mapper = new ObjectMapper();

mapper.writeValue(response.getOutputStream(), body);

}

}



Define payloads for Authentication Controller

– Requests: 
  • LoginRequest: { username, password }
com.joko.payload.request
LoginRequest.java

package com.joko.payload.request;


import jakarta.validation.constraints.NotBlank;


public class LoginRequest {

@NotBlank

private String username;

@NotBlank

private String password;


public String getUsername() {

return username;

}


public void setUsername(String username) {

this.username = username;

}


public String getPassword() {

return password;

}


public void setPassword(String password) {

this.password = password;

}


}


  • SignupRequest: { username, email, password }
SignUpRequest.java

package com.joko.payload.request;


import java.util.Set;


import jakarta.validation.constraints.Email;

import jakarta.validation.constraints.NotBlank;

import jakarta.validation.constraints.Size;


public class SignupRequest {

@NotBlank

@Size(min = 3, max = 20)

private String username;

@NotBlank

@Size(max = 50)

@Email

private String email;

@NotBlank

@Size(min = 6, max = 40)

private String password;

private Set<String> role;


public String getUsername() {

return username;

}


public void setUsername(String username) {

this.username = username;

}


public String getEmail() {

return email;

}


public void setEmail(String email) {

this.email = email;

}


public String getPassword() {

return password;

}


public void setPassword(String password) {

this.password = password;

}


public Set<String> getRole() {

return role;

}


public void setRole(Set<String> role) {

this.role = role;

}

}


– Responses:
com.joko.payload.response
  • MessageResponse: { message }
MessageResponse.java

package com.joko.payload.response;


public class MessageResponse {


private String message;

public MessageResponse(String message) {

this.message = message;

}


public String getMessage() {

return message;

}


public void setMessage(String message) {

this.message = message;

}

}


  • UserInfoResponse: { id, username, email, roles }
UserInfoResponse.java

package com.joko.payload.response;


import java.util.List;


public class UserInfoResponse {


private Long id;

private String username;

private String email;

private List<String> roles;

public UserInfoResponse(Long id, String username, String email, List<String> roles) {

super();

this.id = id;

this.username = username;

this.email = email;

this.roles = roles;

}

public Long getId() {

return id;

}

public void setId(Long id) {

this.id = id;

}

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getEmail() {

return email;

}

public void setEmail(String email) {

this.email = email;

}

public List<String> getRoles() {

return roles;

}

public void setRoles(List<String> roles) {

this.roles = roles;

}

}




Create Spring Rest Controllers

– /api/auth/signup

  • check existing username/email
  • create new User (with ROLE_USER if not specifying role)
  • save User to database using UserRepository

– /api/auth/signin

  •  authenticate { username, pasword }
  • update SecurityContext using Authentication object
  • generate JWT
  • get UserDetails from Authentication object
  • response contains JWT and UserDetails data

– /api/auth/signout: clear the Cookie.



controllers/AuthController.java

package com.joko.controllers;


import java.util.HashSet;

import java.util.List;

import java.util.Set;

import java.util.stream.Collectors;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpHeaders;

import org.springframework.http.ResponseCookie;

import org.springframework.http.ResponseEntity;

import org.springframework.security.authentication.AuthenticationManager;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.Authentication;

import org.springframework.security.core.context.SecurityContextHolder;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.web.bind.annotation.CrossOrigin;

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 com.joko.models.ERole;

import com.joko.models.Role;

import com.joko.models.User;

import com.joko.payload.request.LoginRequest;

import com.joko.payload.request.SignupRequest;

import com.joko.payload.response.UserInfoResponse;

import com.joko.payload.response.MessageResponse;

import com.joko.repository.RoleRepository;

import com.joko.repository.UserRepository;

import com.joko.security.jwt.JwtUtils;

import com.joko.security.services.UserDetailsImpl;


import jakarta.validation.Valid;


//@CrossOrigin(origins = "http://localhost:8091", maxAge = 3600, allowCredentials="true")

@CrossOrigin(origins = "*", maxAge = 3600)

@RestController

@RequestMapping("/api/auth")

public class AuthController {


@Autowired

AuthenticationManager authenticationManager;


@Autowired

UserRepository userRepository;


@Autowired

RoleRepository roleRepository;


@Autowired

PasswordEncoder encoder;


@Autowired

JwtUtils jwtUtils;

@PostMapping("/signin")

public ResponseEntity<?>authenticateUser (@Valid @RequestBody LoginRequest loginRequest){

Authentication authentication = authenticationManager

.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));


SecurityContextHolder.getContext().setAuthentication(authentication);


UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();


ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails);


List<String> roles = userDetails.getAuthorities().stream()

.map(item -> item.getAuthority())

.collect(Collectors.toList());


return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString())

.body(new UserInfoResponse(userDetails.getId(),

userDetails.getUsername(),

userDetails.getEmail(),

roles));

}

@PostMapping("/signup")

public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {

if (userRepository.existsByUsername(signUpRequest.getUsername())) {

return ResponseEntity.badRequest().body(new MessageResponse("Error: Username is already taken!"));

}


if (userRepository.existsByEmail(signUpRequest.getEmail())) {

return ResponseEntity.badRequest().body(new MessageResponse("Error: Email is already in use!"));

}


// Create new user's account

User user = new User(signUpRequest.getUsername(),

signUpRequest.getEmail(),

encoder.encode(signUpRequest.getPassword()));


Set<String> strRoles = signUpRequest.getRole();

Set<Role> roles = new HashSet<>();


if (strRoles == null) {

Role userRole = roleRepository.findByName(ERole.ROLE_USER)

.orElseThrow(() -> new RuntimeException("Error: Role is not found."));

roles.add(userRole);

} else {

strRoles.forEach(role -> {

switch (role) {

case "admin":

Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)

.orElseThrow(() -> new RuntimeException("Error: Role is not found."));

roles.add(adminRole);


break;

case "mod":

Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR)

.orElseThrow(() -> new RuntimeException("Error: Role is not found."));

roles.add(modRole);


break;

default:

Role userRole = roleRepository.findByName(ERole.ROLE_USER)

.orElseThrow(() -> new RuntimeException("Error: Role is not found."));

roles.add(userRole);

}

});

}


user.setRoles(roles);

userRepository.save(user);


return ResponseEntity.ok(new MessageResponse("User registered successfully!"));

}

@PostMapping("/signout")

public ResponseEntity<?> logoutUser() {

ResponseCookie cookie = jwtUtils.getCleanJwtCookie();

return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString())

.body(new MessageResponse("You've been signed out!"));

}

}


controllers/TestController.java

package com.joko.controllers;


import org.springframework.security.access.prepost.PreAuthorize;

import org.springframework.web.bind.annotation.CrossOrigin;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;


//@CrossOrigin(origins = "http://localhost:8091", maxAge = 3600, allowCredentials="true")

@CrossOrigin(origins = "*", maxAge = 3600)

@RestController

@RequestMapping("/api/test")

public class TestController {

@GetMapping("/all")

public String allAccess() {

return "Public Content.";

}


@GetMapping("/user")

@PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")

public String userAccess() {

return "User Content.";

}


@GetMapping("/mod")

@PreAuthorize("hasRole('MODERATOR')")

public String moderatorAccess() {

return "Moderator Board.";

}


@GetMapping("/admin")

@PreAuthorize("hasRole('ADMIN')")

public String adminAccess() {

return "Admin Board.";

}

}


com.joko

package com.joko;


import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication

public class SpringbootLoginRestApiWithMysqlJwtApplication {


public static void main(String[] args) {

SpringApplication.run(SpringbootLoginRestApiWithMysqlJwtApplication.class, args);

}


}


Run & Check

Run application with command: mvn spring-boot:run
otomatis generate database dalam MYSQL







Comments

Popular posts from this blog

create image slider using phyton in web

Tahukah kamu Algoritma Genetika dan Penerapannya dalam Industri

create animated futuristic profile card using html+css+js

CRUD SPRING REACTIVE WEBFLUX +Mongo DB

Top 7 Digital Transformation Companies

100 perusahaan perangkat lunak (software) populer dari Eropa dan Amerika yang memiliki kehadiran atau operasional di Indonesia.

TOP 8 Framework Populer menggunakan bahasa .NET

Python Date and Time Manipulation

TOP 5 Trends Programming 2024

Daftar Kata Kunci (Keyword) dalam Bahasa Pemrograman Python