Add Security & Repository Package (JWT, Repository, Enums)
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
package com.hakimfauzi23.boilerplatespringsecurity.modules.auth;
|
||||||
|
|
||||||
|
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt.AuthEntryPointJwt;
|
||||||
|
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt.AuthTokenFilter;
|
||||||
|
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.UserDetailsServiceImpl;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableMethodSecurity
|
||||||
|
public class WebSecurityConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
UserDetailsServiceImpl userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthEntryPointJwt unauthorizedHandler;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthTokenFilter authenticationJwtTokenFilter() {
|
||||||
|
return new AuthTokenFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DaoAuthenticationProvider authenticationProvider() {
|
||||||
|
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
|
|
||||||
|
authProvider.setUserDetailsService(userDetailsService);
|
||||||
|
authProvider.setPasswordEncoder(passwordEncoder());
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
|
||||||
|
return authConfig.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
||||||
|
httpSecurity.csrf(csrf -> csrf.disable())
|
||||||
|
.exceptionHandling(ex -> ex.authenticationEntryPoint(unauthorizedHandler))
|
||||||
|
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests(auth ->
|
||||||
|
auth.requestMatchers("/api/auth/**").permitAll()
|
||||||
|
.requestMatchers("api/test/**").permitAll()
|
||||||
|
.anyRequest().authenticated());
|
||||||
|
|
||||||
|
httpSecurity.authenticationProvider(authenticationProvider());
|
||||||
|
httpSecurity.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
|
return httpSecurity.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
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 java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@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 {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt;
|
||||||
|
|
||||||
|
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.UserDetailsServiceImpl;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
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 java.io.IOException;
|
||||||
|
|
||||||
|
public class AuthTokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtUtils jwtUtils;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserDetailsServiceImpl userDetailsService;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt;
|
||||||
|
|
||||||
|
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.UserDetailsImpl;
|
||||||
|
import io.jsonwebtoken.*;
|
||||||
|
import io.jsonwebtoken.io.Decoders;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import jakarta.servlet.http.Cookie;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
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 java.security.Key;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtUtils {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JwtUtils.class);
|
||||||
|
|
||||||
|
@Value("${spring.app.jwtSecret}")
|
||||||
|
private String jwtSecret;
|
||||||
|
|
||||||
|
@Value("${spring.app.jwtExpirationMs}")
|
||||||
|
private int jwtExpirationMs;
|
||||||
|
|
||||||
|
@Value("${spring.app.jwtCookieName}")
|
||||||
|
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 userDetails) {
|
||||||
|
String jwt = generateTokenFromUsername(userDetails.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(key()).build()
|
||||||
|
.parseClaimsJws(token).getBody().getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Key key() {
|
||||||
|
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateJwtToken(String authToken) {
|
||||||
|
try {
|
||||||
|
Jwts.parserBuilder().setSigningKey(key()).build().parse(authToken);
|
||||||
|
return true;
|
||||||
|
} 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(key(), SignatureAlgorithm.HS256)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository;
|
||||||
|
|
||||||
|
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.ERole;
|
||||||
|
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.Role;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface RoleRepository extends JpaRepository<Role, Long> {
|
||||||
|
|
||||||
|
Optional<Role> findByName(ERole name);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository;
|
||||||
|
|
||||||
|
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.User;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
|
||||||
|
Optional<User> findByUsername(String username);
|
||||||
|
|
||||||
|
Boolean existsByUsername(String username);
|
||||||
|
|
||||||
|
Boolean existsByEmail(String email);
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user