如何在后端使用 JWT 实用程序从前端验证 JWT? (反应+弹簧靴)
How to validate JWT from frontend using a JWT Utility on the backend? (React + Springboot)
好的,所以我有一个 React 组件,如果本地存储中的 JWT
有效(意味着令牌未过期并且它是正确的令牌),我只想加载它。目前,我只能检查其中一项,是否已过期。
这是该组件中的 componentDidMount
函数:
componentDidMount() {
this._isMounted = true;
// validate token
const token = localStorage.getItem("token");
AuthService.validateToken()
.then((res) => {
console.log("step 1");
if (res == undefined || res == null || !token) {
this.props.history.push("/login");
}
})
.then(() => {
console.log("step 2");
TicketService.getTickets().then((res) => {
if (this._isMounted) {
this.setState({ tickets: res.data });
}
});
});
}
AuthService.validateToken()
看起来像这样:
// IS token expired?
validateToken() {
console.log(" about to try validating");
return AxiosInstance.get("authenticate")
.then("did it")
.catch((err) => console.log(err));
}
}
而 AxiosInstance
就是这样。我也会将代码分享到我的 Axios 实例:
const AxiosInstance = axios.create({
baseURL: API_BASE_URL,
headers: { Authorization: `Bearer ${getTokenFromLocalStorage()}` },
});
function getTokenFromLocalStorage() {
const token = localStorage.getItem("token");
console.log("the token is -> " + token);
if (token === null) {
return undefined;
}
return token;
}
最后,终点是这样的:
// returns the username
@GetMapping("/authenticate")
public ResponseEntity<?> validateHeaderToken() throws Exception {
String username = null;
try {
System.out.println("Attempting to validate token");
String token = request.getHeader("Authorization");
token = token.split("Bearer ")[1];
username = jwtTokenUtil.extractUsername(token);
// TODO : this should eventually use the .validateToken method once I figure out
// how to pass in UserDetails
if (!jwtTokenUtil.isTokenExpired(token)) {
return ResponseEntity.ok(username);
} else {
throw new Exception("Expired or Invalid Token");
}
} catch (Exception e) {
return ResponseEntity.status(400).build();
}
}
所以如你所见,我只是在上面的函数中检查令牌是否过期^^。我想使用我的 JWT 实用程序来使用已经存在的 validateToken
。这是我的 JWT 实用程序:
public String extractUsername(String token) {
// we set the username as the subject when we created the token so we can
// extract the username from the claim like this
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
// extract the claim that is attached to the token, if any
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
public Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
// create token using the username as subject
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
// Expiration is 10 hours from creation
// subject is username
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
所以,所有有用的功能都在我的 JWT 实用程序中,但是 validateToken()
需要 UserDetails
和 token
。我在哪里以及如何让 UserDetails
传递给 validateToken
函数?
编辑:用户详细信息已经加载,当我刷新页面时,我可以看到为登录的用户打印的用户详细信息,但不确定如何从其他地方访问它。
编辑:MyUserDetailsService
看起来像这样:
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private EmployeeServices employeeServices;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
System.out.println("S is -> " + s);
Employee employee = employeeServices.getEmployeeByUsername(s);
System.out.println(employee);
// if (employee == null) {
// throw new UsernameNotFoundException("Username was not found. Could not log
// in.");
// }
return new User(employee.getUsername(), employee.getPassword(), new ArrayList<>());
}
}
当我刷新页面时,每次都会调用它。我可以在每次刷新页面时看到 S is ->
打印到我的终端。这是我应该在 Cookie 中存储用户详细信息的地方吗?
听起来您想做的是将 cookie 中的数据与 UserDetails
中的数据进行比较。
大概您将 UserDetails
存储在 session cookie 中,或者至少是用户的 ID。由于不知道您的 session cookies 是如何形成的,我无法给出确切的答案,但大致情况如下。
在您的 validateHeaderToken
方法中,您可以访问传入的请求 object,您应该:
- 从传入请求的 cookie 中提取用户详细信息,或者
- 从传入请求的 cookie 中提取 ID,并从您的数据库中查找相应的用户详细信息。
然后
- 将用户详细信息与令牌一起传递给您的
validateToken
方法。
headers 中关于 cookie 的 MDN 文章:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie
好的,所以我有一个 React 组件,如果本地存储中的 JWT
有效(意味着令牌未过期并且它是正确的令牌),我只想加载它。目前,我只能检查其中一项,是否已过期。
这是该组件中的 componentDidMount
函数:
componentDidMount() {
this._isMounted = true;
// validate token
const token = localStorage.getItem("token");
AuthService.validateToken()
.then((res) => {
console.log("step 1");
if (res == undefined || res == null || !token) {
this.props.history.push("/login");
}
})
.then(() => {
console.log("step 2");
TicketService.getTickets().then((res) => {
if (this._isMounted) {
this.setState({ tickets: res.data });
}
});
});
}
AuthService.validateToken()
看起来像这样:
// IS token expired?
validateToken() {
console.log(" about to try validating");
return AxiosInstance.get("authenticate")
.then("did it")
.catch((err) => console.log(err));
}
}
而 AxiosInstance
就是这样。我也会将代码分享到我的 Axios 实例:
const AxiosInstance = axios.create({
baseURL: API_BASE_URL,
headers: { Authorization: `Bearer ${getTokenFromLocalStorage()}` },
});
function getTokenFromLocalStorage() {
const token = localStorage.getItem("token");
console.log("the token is -> " + token);
if (token === null) {
return undefined;
}
return token;
}
最后,终点是这样的:
// returns the username
@GetMapping("/authenticate")
public ResponseEntity<?> validateHeaderToken() throws Exception {
String username = null;
try {
System.out.println("Attempting to validate token");
String token = request.getHeader("Authorization");
token = token.split("Bearer ")[1];
username = jwtTokenUtil.extractUsername(token);
// TODO : this should eventually use the .validateToken method once I figure out
// how to pass in UserDetails
if (!jwtTokenUtil.isTokenExpired(token)) {
return ResponseEntity.ok(username);
} else {
throw new Exception("Expired or Invalid Token");
}
} catch (Exception e) {
return ResponseEntity.status(400).build();
}
}
所以如你所见,我只是在上面的函数中检查令牌是否过期^^。我想使用我的 JWT 实用程序来使用已经存在的 validateToken
。这是我的 JWT 实用程序:
public String extractUsername(String token) {
// we set the username as the subject when we created the token so we can
// extract the username from the claim like this
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
// extract the claim that is attached to the token, if any
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
public Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
// create token using the username as subject
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
// Expiration is 10 hours from creation
// subject is username
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
所以,所有有用的功能都在我的 JWT 实用程序中,但是 validateToken()
需要 UserDetails
和 token
。我在哪里以及如何让 UserDetails
传递给 validateToken
函数?
编辑:用户详细信息已经加载,当我刷新页面时,我可以看到为登录的用户打印的用户详细信息,但不确定如何从其他地方访问它。
编辑:MyUserDetailsService
看起来像这样:
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private EmployeeServices employeeServices;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
System.out.println("S is -> " + s);
Employee employee = employeeServices.getEmployeeByUsername(s);
System.out.println(employee);
// if (employee == null) {
// throw new UsernameNotFoundException("Username was not found. Could not log
// in.");
// }
return new User(employee.getUsername(), employee.getPassword(), new ArrayList<>());
}
}
当我刷新页面时,每次都会调用它。我可以在每次刷新页面时看到 S is ->
打印到我的终端。这是我应该在 Cookie 中存储用户详细信息的地方吗?
听起来您想做的是将 cookie 中的数据与 UserDetails
中的数据进行比较。
大概您将 UserDetails
存储在 session cookie 中,或者至少是用户的 ID。由于不知道您的 session cookies 是如何形成的,我无法给出确切的答案,但大致情况如下。
在您的 validateHeaderToken
方法中,您可以访问传入的请求 object,您应该:
- 从传入请求的 cookie 中提取用户详细信息,或者
- 从传入请求的 cookie 中提取 ID,并从您的数据库中查找相应的用户详细信息。
然后
- 将用户详细信息与令牌一起传递给您的
validateToken
方法。
headers 中关于 cookie 的 MDN 文章:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie