add spring security and token management

This commit is contained in:
dirgantarasiahaan
2023-05-23 19:42:51 +07:00
parent 597d4062c7
commit c76c01a174
35 changed files with 1095 additions and 53 deletions

View File

@ -0,0 +1,33 @@
package com.iconplus.smartproc.configuration;
import com.iconplus.smartproc.model.projection.UserRoleView;
import com.iconplus.smartproc.repository.RolesRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final RolesRepository rolesRepository;
public CustomUserDetailsService(RolesRepository rolesRepository) {
this.rolesRepository = rolesRepository;
}
@Override
public UserDetails loadUserByUsername(String username) {
List<SimpleGrantedAuthority> authorities = null;
Optional<UserRoleView> userRole = rolesRepository.getUserRoleByUserId(username);
if (userRole.isPresent()) {
authorities=List.of(new SimpleGrantedAuthority(userRole.get().getRole()));
}
return new org.springframework.security.core.userdetails.User(username, username, authorities);
}
}

View File

@ -0,0 +1,39 @@
package com.iconplus.smartproc.configuration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iconplus.smartproc.exception.ErrorResponse;
import com.iconplus.smartproc.util.Constants;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Log4j2
public class JwtAuthenticationAccessDenied implements AccessDeniedHandler {
private static final String DEFAULT_CODE = "30000";
private static final String DEFAULT_MESSAGE = "Access denied";
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
log.error("Access denied, {}", e.getMessage());
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setCode(DEFAULT_CODE);
errorResponse.setTitle(Constants.TITLE_INVALID_NEXT_STEP);
errorResponse.setMessage(DEFAULT_MESSAGE);
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.getOutputStream()
.println(new ObjectMapper().writeValueAsString(errorResponse));
}
}

View File

@ -0,0 +1,54 @@
package com.iconplus.smartproc.configuration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iconplus.smartproc.exception.ErrorResponse;
import com.iconplus.smartproc.util.Constants;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
ErrorResponse errorResponse = new ErrorResponse();
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
var exception = (Exception) request.getAttribute("exception");
String message;
if (exception != null) {
if (exception.getCause() != null) {
message = exception.getCause().toString() + " " + exception.getMessage();
} else {
message = exception.getMessage();
}
errorResponse.setCode(Constants.ERR_CODE_40051);
errorResponse.setTitle(Constants.TITLE_INVALID_NEXT_STEP);
errorResponse.setMessage(message);
response.getOutputStream()
.println(new ObjectMapper().writeValueAsString(errorResponse));
} else {
errorResponse.setCode(Constants.ERR_CODE_80007);
errorResponse.setTitle(Constants.TITLE_INVALID_NEXT_STEP);
errorResponse.setMessage("Invalid Access Token");
response.getOutputStream()
.println(new ObjectMapper().writeValueAsString(errorResponse));
}
}
}

View File

@ -0,0 +1,79 @@
package com.iconplus.smartproc.configuration;
import com.iconplus.smartproc.exception.BusinessException;
import com.iconplus.smartproc.model.request.PostAccessTokenRequest;
import com.iconplus.smartproc.service.authentication.PostCheckAccessTokenService;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final JwtTokenUtil jwtTokenUtil;
private final PostCheckAccessTokenService postCheckAccessTokenService;
public JwtRequestFilter(JwtTokenUtil jwtTokenUtil,
PostCheckAccessTokenService postCheckAccessTokenService) {
this.jwtTokenUtil = jwtTokenUtil;
this.postCheckAccessTokenService = postCheckAccessTokenService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
String jwtToken = extractJwtFromRequest(request);
if (StringUtils.hasText(jwtToken) && jwtTokenUtil.validateTokenOnly(jwtToken)) {
isValidToken(request, jwtToken);
UserDetails userDetails = new org.springframework.security.core.userdetails.User(jwtTokenUtil.getUsernameFromToken(jwtToken), "",
jwtTokenUtil.getRolesFromToken(jwtToken));
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
} catch(ExpiredJwtException | BadCredentialsException ex)
{
request.setAttribute("exception", ex);
}
chain.doFilter(request, response);
}
private void isValidToken(HttpServletRequest request, String jwtToken) {
String requestUrl = request.getRequestURI();
String refreshTokenUrl = "/authentication-service/authentication/v1/refresh-token";
if (!refreshTokenUrl.equals(requestUrl)) {
var isValid = isValidAuthenticateToken(jwtToken);
if (!isValid) {
throw new BusinessException(HttpStatus.UNAUTHORIZED, "Invalid Access Token");
}
}
}
private boolean isValidAuthenticateToken(String jwtToken) {
return postCheckAccessTokenService.execute(PostAccessTokenRequest.builder()
.accessToken(jwtToken)
.build()).getIsValid();
}
private String extractJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}

View File

@ -0,0 +1,91 @@
package com.iconplus.smartproc.configuration;
import com.iconplus.smartproc.model.token.TokenContent;
import io.jsonwebtoken.*;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.security.KeyPair;
import java.util.*;
import java.util.function.Function;
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -2550185165626007488L;
@Autowired
private KeyPair keyPair;
//retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
//for retrieveing any information from token we will need the secret key
public Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(keyPair.getPublic()).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(String username, TokenContent tokenContent, Integer expirationInMs, String channel, String scopeType) {
Map<String, Object> claims = new HashMap<>();
claims.put("authorities", tokenContent.getAccessMenu());
claims.put("fullname", tokenContent.getFullname());
claims.put("username", tokenContent.getUsername());
claims.put("user_id", tokenContent.getUserId());
claims.put("role", tokenContent.getRole());
return doGenerateToken(claims, username, expirationInMs);
}
public String doGenerateToken(Map<String, Object> claims, String subject, Integer expirationInMs) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expirationInMs))
.signWith(SignatureAlgorithm.RS256, keyPair.getPrivate()).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public boolean validateTokenOnly(String authToken) {
try {
Jwts.parser().setSigningKey(keyPair.getPublic()).parseClaimsJws(authToken);
return true;
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) {
throw new BadCredentialsException("INVALID_CREDENTIALS", ex);
} catch (ExpiredJwtException ex) {
throw ex;
}
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser().setSigningKey(keyPair.getPublic()).parseClaimsJws(token).getBody();
return claims.getSubject();
}
@SuppressWarnings("squid:S4834")
public List<SimpleGrantedAuthority> getRolesFromToken(String token) {
Claims claims = Jwts.parser().setSigningKey(keyPair.getPublic()).parseClaimsJws(token).getBody();
List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
ArrayList<String> authorities = claims.get("authorities", ArrayList.class);
authorities.forEach(c-> authorityList.add(new SimpleGrantedAuthority(c)));
return authorityList;
}
}

View File

@ -0,0 +1,30 @@
package com.iconplus.smartproc.configuration;
import com.iconplus.smartproc.util.RSAUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
@Configuration
public class KeyConfiguration {
@Value("${jwt.private-key}")
private String privateKey;
@Value("${jwt.public-key}")
private String publicKey;
@Bean
public KeyPair keypairBean() {
PublicKey pubKey = RSAUtil.getPublicKey(publicKey);
PrivateKey privKey = RSAUtil.getPrivateKey(privateKey);
return new KeyPair(pubKey, privKey);
}
}

View File

@ -0,0 +1,57 @@
package com.iconplus.smartproc.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtAuthenticationAccessDenied jwtAuthenticationAccessDenied;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionFixation().none().and()
.csrf().disable();
httpSecurity.authorizeRequests()
.antMatchers("/api*/**").permitAll()
.antMatchers("/actuator/health").permitAll()
.antMatchers("/swagger*/**").permitAll()
.antMatchers("/v2*/**").permitAll()
.antMatchers("/token/jwks.json").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAuthenticationAccessDenied);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
}
}