Skip to content

Commit

Permalink
JWT implementation but 403 on /authenticate
Browse files Browse the repository at this point in the history
  • Loading branch information
aidanywu committed Jan 16, 2023
1 parent 90e3b3f commit a5447a6
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 7 deletions.
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
Expand Down Expand Up @@ -133,6 +137,15 @@
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.nighthawk.spring_portfolio.mvc.jwt;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
// import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PostMapping;
// import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.nighthawk.spring_portfolio.mvc.person.*;

@RestController
@CrossOrigin
public class JwtApiController {

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Autowired
private JwtUserDetailsService jwtUserDetailsService;

@PostMapping("/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody Person authenticationRequest) throws Exception {

authenticate(authenticationRequest.getEmail(), authenticationRequest.getPassword());
System.out.println("good email and password");
final UserDetails userDetails = jwtUserDetailsService
.loadUserByUsername(authenticationRequest.getEmail());

final String token = jwtTokenUtil.generateToken(userDetails);

return ResponseEntity.ok(new JwtResponse(token));
}

private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
System.out.println("bad email and password");
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
System.out.println("bad email and password");
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.nighthawk.spring_portfolio.mvc.jwt;

import java.io.IOException;

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 {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.nighthawk.spring_portfolio.mvc.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.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.util.AntPathMatcher;


import io.jsonwebtoken.ExpiredJwtException;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

@Autowired
private JwtUserDetailsService 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);
}

@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return new AntPathMatcher().match("/authenticate", request.getServletPath());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.nighthawk.spring_portfolio.mvc.jwt;

public class JwtResponse {
private final String jwttoken;

public JwtResponse(String jwttoken) {
this.jwttoken = jwttoken;
}

public String getToken() {
return this.jwttoken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.nighthawk.spring_portfolio.mvc.jwt;
// import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

// @Service
// public class JwtGenerator{
// public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
// // @Value("${jwt.secret}")
// private final String secret = "nighthawk";
// public Map<String, String> generateJwt(Person user) {
// String jwtToken = Jwts.builder().setSubject(user.getEmail()).setIssuedAt(new Date(System.currentTimeMillis())).setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact();
// Map<String, String> jwtTokenGen = new HashMap<>();
// jwtTokenGen.put("token", jwtToken);
// return jwtTokenGen;
// }
// static public void main(String[] args) {
// // create and display LightBoard
// JwtGenerator jwtgen = new JwtGenerator();
// System.out.println(jwtgen.generateJwt(new Person("aidanywu@gmail.com", "password", "Aidan Wu", new java.util.GregorianCalendar(2006,4,6).getTime())));
// }
// }
@Component
public class JwtTokenUtil {

public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;

// @Value("${jwt.secret}")
private String secret = "nighthawk";

//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> 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
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<String, Object> 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<String, Object> 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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.nighthawk.spring_portfolio.mvc.jwt;

import java.util.ArrayList;

import org.springframework.security.core.userdetails.User;
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.nighthawk.spring_portfolio.mvc.person.*;

@Service
public class JwtUserDetailsService implements UserDetailsService {
private PersonJpaRepository repository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Person person = repository.findByEmail(email);
if (person != null) {
return new User(person.getEmail(), person.getPassword(),
new ArrayList<>());
} else {
throw new UsernameNotFoundException("User not found with username: " + email);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
@Entity
@TypeDef(name="json", typeClass = JsonType.class)
public class Person {

// automatic unique identifier for Person record
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.nighthawk.spring_portfolio.mvc.person;

// import com.nighthawk.spring_portfolio.mvc.jwt.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
Expand All @@ -12,6 +13,8 @@
@RestController
@RequestMapping("/api/person")
public class PersonApiController {
// @Autowired
// private JwtTokenUtil jwtGen;
/*
#### RESTful API ####
Resource: https://spring.io/guides/gs/rest-service/
Expand Down Expand Up @@ -126,4 +129,22 @@ public ResponseEntity<Person> personStats(@RequestBody final Map<String,Object>

}

// @PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE)
// public ResponseEntity<?> createToken(@RequestBody final Map<String,Object> stat_map) {
// // find ID
// // System.out.println(stat_map);
// String email=((String)stat_map.get("email"));
// // System.out.println('a');
// String password=((String)stat_map.get("password"));
// // System.out.println('b');
// try {
// Person person = repository.findByEmailAndPassword(email, password);
// return new ResponseEntity<>(jwtGen.generateToken(person), HttpStatus.OK);
// } catch(Exception e) {
// // return Bad ID
// return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
// }

// }

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface PersonJpaRepository extends JpaRepository<Person, Long> {
https://springframework.guru/spring-data-jpa-query/
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
*/

Person findByEmailAndPassword(String email, String password);
// Custom JPA query
@Query(
value = "SELECT * FROM Person p WHERE p.name LIKE ?1 or p.email LIKE ?1",
Expand Down
Loading

0 comments on commit a5447a6

Please sign in to comment.