diff --git a/.idea/libraries/Maven__io_jsonwebtoken_jjwt_0_9_1.xml b/.idea/libraries/Maven__io_jsonwebtoken_jjwt_0_9_1.xml new file mode 100644 index 0000000..f25b99b --- /dev/null +++ b/.idea/libraries/Maven__io_jsonwebtoken_jjwt_0_9_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index aaad21d..13ec0f6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,27 +2,20 @@ - - - - - - - - + + + + + + - - - - - @@ -71,7 +64,7 @@ - + @@ -112,7 +105,7 @@ - + 1589796661163 @@ -140,6 +133,10 @@ + + + + @@ -160,42 +157,42 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + @@ -224,9 +221,9 @@ - + - + \ No newline at end of file diff --git a/jdbc.iml b/jdbc.iml index d39a43e..222e5ac 100644 --- a/jdbc.iml +++ b/jdbc.iml @@ -176,5 +176,6 @@ + \ No newline at end of file diff --git a/pom.xml b/pom.xml index b8fbc16..efad85f 100644 --- a/pom.xml +++ b/pom.xml @@ -77,6 +77,11 @@ + + io.jsonwebtoken + jjwt + 0.9.1 + diff --git a/src/main/kotlin/com/chantha/jdbc/config/WebConfig.java b/src/main/kotlin/com/chantha/jdbc/config/WebConfig.java index e637a2f..c5811e4 100644 --- a/src/main/kotlin/com/chantha/jdbc/config/WebConfig.java +++ b/src/main/kotlin/com/chantha/jdbc/config/WebConfig.java @@ -1,5 +1,6 @@ package com.chantha.jdbc.config; +import com.chantha.jdbc.utils.jwt.JwtRequestFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -13,6 +14,7 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @@ -21,7 +23,8 @@ public class WebConfig extends WebSecurityConfigurerAdapter { private final UserDetailsService userDetailsService; - + @Autowired + private JwtRequestFilter jwtRequestFilter; @Autowired public WebConfig(UserDetailsService userDetailsService){ @@ -41,6 +44,7 @@ public class WebConfig extends WebSecurityConfigurerAdapter { .antMatchers("/register").permitAll() .antMatchers("/**").hasAnyRole("ADMIN"); http.csrf().disable(); + http.addFilterBefore(jwtRequestFilter,UsernamePasswordAuthenticationFilter.class); } @Bean diff --git a/src/main/kotlin/com/chantha/jdbc/security/UserDetailServiceImpl.kt b/src/main/kotlin/com/chantha/jdbc/security/UserDetailServiceImpl.kt index 661b853..d80d5c1 100644 --- a/src/main/kotlin/com/chantha/jdbc/security/UserDetailServiceImpl.kt +++ b/src/main/kotlin/com/chantha/jdbc/security/UserDetailServiceImpl.kt @@ -3,13 +3,14 @@ package com.chantha.jdbc.security 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 @Service class UserDetailServiceImpl @Autowired constructor(private val userRepo: UserRepo):UserDetailsService { - @Throws(Exception::class) + @Throws(UsernameNotFoundException::class) override fun loadUserByUsername(p0: String?): UserDetails { - val user=userRepo.findByUsername(p0!!) + val user = userRepo.findByUsername(p0!!) return UserPrincipal(user) } diff --git a/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtAuthenticationEntryPoint.java b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..3459a98 --- /dev/null +++ b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,19 @@ +package com.chantha.jdbc.utils.jwt; + + +import java.io.IOException; +import java.io.Serializable; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { + private static final long serialVersionUID = -7858869558953243875L; + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + } +} diff --git a/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtRequest.java b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtRequest.java new file mode 100644 index 0000000..7814f2d --- /dev/null +++ b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtRequest.java @@ -0,0 +1,28 @@ +package com.chantha.jdbc.utils.jwt; + +import java.io.Serializable; +public class JwtRequest implements Serializable { + private static final long serialVersionUID = 5926468583005150707L; + private String username; + private String password; + //need default constructor for JSON Parsing + public JwtRequest() + { + } + public JwtRequest(String username, String password) { + this.setUsername(username); + this.setPassword(password); + } + public String getUsername() { + return this.username; + } + public void setUsername(String username) { + this.username = username; + } + public String getPassword() { + return this.password; + } + public void setPassword(String password) { + this.password = password; + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtRequestFilter.java b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtRequestFilter.java new file mode 100644 index 0000000..8cc54e7 --- /dev/null +++ b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtRequestFilter.java @@ -0,0 +1,63 @@ +package com.chantha.jdbc.utils.jwt; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.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.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import com.chantha.jdbc.security.UserDetailServiceImpl; +import io.jsonwebtoken.ExpiredJwtException; +@Component +public class JwtRequestFilter extends OncePerRequestFilter { + + @Autowired + private UserDetailServiceImpl jwtUserDetailsService; + + @Autowired + private JwtTokenUtil jwtTokenUtil; + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + final String requestTokenHeader = request.getHeader("Authorization"); + String username = null; + String jwtToken = null; + // JWT Token is in the form "Bearer token". Remove Bearer word and get + // only the Token + if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { + jwtToken = requestTokenHeader.substring(7); + try { + username = jwtTokenUtil.getUsernameFromToken(jwtToken); + } catch (IllegalArgumentException e) { + System.out.println("Unable to get JWT Token"); + } catch (ExpiredJwtException e) { + System.out.println("JWT Token has expired"); + } + } else { + logger.warn("JWT Token does not begin with Bearer String"); + } + // Once we get the token validate it. + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username); + // if token is valid configure Spring Security to manually set + // authentication + if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + usernamePasswordAuthenticationToken + .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + // After setting the Authentication in the context, we specify + // that the current user is authenticated. So it passes the + // Spring Security Configurations successfully. + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + } + } + chain.doFilter(request, response); + } +} diff --git a/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtResponse.java b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtResponse.java new file mode 100644 index 0000000..a910d54 --- /dev/null +++ b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtResponse.java @@ -0,0 +1,16 @@ +package com.chantha.jdbc.utils.jwt; + +import java.io.Serializable; + +public class JwtResponse implements Serializable { + private static final long serialVersionUID = -8091879091924046844L; + private final String jwttoken; + + public JwtResponse(String jwttoken) { + this.jwttoken = jwttoken; + } + public String getToken() { + return this.jwttoken; + } + +} diff --git a/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtTokenUtil.java b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtTokenUtil.java new file mode 100644 index 0000000..edce46c --- /dev/null +++ b/src/main/kotlin/com/chantha/jdbc/utils/jwt/JwtTokenUtil.java @@ -0,0 +1,63 @@ +package com.chantha.jdbc.utils.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +@Component +public class JwtTokenUtil implements Serializable { + private static final long serialVersionUID = -2550185165626007488L; + public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; + @Value("${jwt.secret}") + private String secret; + //retrieve username from jwt token + public String getUsernameFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + //retrieve expiration date from jwt token + public Date getExpirationDateFromToken(String token) { + return getClaimFromToken(token, Claims::getExpiration); + } + public T getClaimFromToken(String token, Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + //for retrieveing any information from token we will need the secret key + private Claims getAllClaimsFromToken(String token) { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + } + //check if the token has expired + private Boolean isTokenExpired(String token) { + final Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } + //generate token for user + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + return doGenerateToken(claims, userDetails.getUsername()); + } + //while creating the token - + //1. Define claims of the token, like Issuer, Expiration, Subject, and the ID + //2. Sign the JWT using the HS512 algorithm and secret key. + //3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1) + // compaction of the JWT to a URL-safe string + private String doGenerateToken(Map claims, String subject) { + return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + } + //validate token + public Boolean validateToken(String token, UserDetails userDetails) { + final String username = getUsernameFromToken(token); + return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5a53e97..d976841 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,6 +7,8 @@ spring.jpa.open-in-view=true server.port=8081 spring.thymeleaf.cache=true +jwt.secret=javainuse + diff --git a/target/classes/application.properties b/target/classes/application.properties index 5a53e97..d976841 100644 --- a/target/classes/application.properties +++ b/target/classes/application.properties @@ -7,6 +7,8 @@ spring.jpa.open-in-view=true server.port=8081 spring.thymeleaf.cache=true +jwt.secret=javainuse + diff --git a/target/classes/com/chantha/jdbc/config/WebConfig.class b/target/classes/com/chantha/jdbc/config/WebConfig.class index aaba873..5d2d5fb 100644 Binary files a/target/classes/com/chantha/jdbc/config/WebConfig.class and b/target/classes/com/chantha/jdbc/config/WebConfig.class differ diff --git a/target/classes/com/chantha/jdbc/security/UserDetailServiceImpl.class b/target/classes/com/chantha/jdbc/security/UserDetailServiceImpl.class index f7af3af..369e425 100644 Binary files a/target/classes/com/chantha/jdbc/security/UserDetailServiceImpl.class and b/target/classes/com/chantha/jdbc/security/UserDetailServiceImpl.class differ diff --git a/target/classes/com/chantha/jdbc/utils/jwt/JwtAuthenticationEntryPoint.class b/target/classes/com/chantha/jdbc/utils/jwt/JwtAuthenticationEntryPoint.class new file mode 100644 index 0000000..3c50e1e Binary files /dev/null and b/target/classes/com/chantha/jdbc/utils/jwt/JwtAuthenticationEntryPoint.class differ diff --git a/target/classes/com/chantha/jdbc/utils/jwt/JwtRequest.class b/target/classes/com/chantha/jdbc/utils/jwt/JwtRequest.class new file mode 100644 index 0000000..edba517 Binary files /dev/null and b/target/classes/com/chantha/jdbc/utils/jwt/JwtRequest.class differ diff --git a/target/classes/com/chantha/jdbc/utils/jwt/JwtRequestFilter.class b/target/classes/com/chantha/jdbc/utils/jwt/JwtRequestFilter.class new file mode 100644 index 0000000..2a892fb Binary files /dev/null and b/target/classes/com/chantha/jdbc/utils/jwt/JwtRequestFilter.class differ diff --git a/target/classes/com/chantha/jdbc/utils/jwt/JwtResponse.class b/target/classes/com/chantha/jdbc/utils/jwt/JwtResponse.class new file mode 100644 index 0000000..4886db3 Binary files /dev/null and b/target/classes/com/chantha/jdbc/utils/jwt/JwtResponse.class differ diff --git a/target/classes/com/chantha/jdbc/utils/jwt/JwtTokenUtil.class b/target/classes/com/chantha/jdbc/utils/jwt/JwtTokenUtil.class new file mode 100644 index 0000000..fc954cd Binary files /dev/null and b/target/classes/com/chantha/jdbc/utils/jwt/JwtTokenUtil.class differ