add spring security and token management
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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/**");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user