Added JWT CSRF expired handling.
This commit is contained in:
parent
f1630a0199
commit
38e829ef35
|
@ -1,7 +1,5 @@
|
||||||
package io.jsonwebtoken.jjwtfun.config;
|
package io.jsonwebtoken.jjwtfun.config;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -11,9 +9,6 @@ import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||||
@Configuration
|
@Configuration
|
||||||
public class CSRFConfig {
|
public class CSRFConfig {
|
||||||
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(CSRFConfig.class);
|
|
||||||
|
|
||||||
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }")
|
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }")
|
||||||
String secret;
|
String secret;
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,9 @@ import java.util.UUID;
|
||||||
|
|
||||||
public class JWTCsrfTokenRepository implements CsrfTokenRepository {
|
public class JWTCsrfTokenRepository implements CsrfTokenRepository {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(JWTCsrfTokenRepository.class);
|
|
||||||
private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = CSRFConfig.class.getName().concat(".CSRF_TOKEN");
|
private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = CSRFConfig.class.getName().concat(".CSRF_TOKEN");
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(JWTCsrfTokenRepository.class);
|
||||||
private String secret;
|
private String secret;
|
||||||
|
|
||||||
public JWTCsrfTokenRepository(String secret) {
|
public JWTCsrfTokenRepository(String secret) {
|
||||||
|
@ -32,7 +32,7 @@ public class JWTCsrfTokenRepository implements CsrfTokenRepository {
|
||||||
String id = UUID.randomUUID().toString().replace("-", "");
|
String id = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
Date exp = new Date(System.currentTimeMillis() + (1000*60)); // 1 minute
|
Date exp = new Date(System.currentTimeMillis() + (1000*30)); // 30 seconds
|
||||||
|
|
||||||
String token;
|
String token;
|
||||||
try {
|
try {
|
||||||
|
@ -68,7 +68,7 @@ public class JWTCsrfTokenRepository implements CsrfTokenRepository {
|
||||||
@Override
|
@Override
|
||||||
public CsrfToken loadToken(HttpServletRequest request) {
|
public CsrfToken loadToken(HttpServletRequest request) {
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
if (session == null) {
|
if (session == null || "GET".equals(request.getMethod())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (CsrfToken) session.getAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME);
|
return (CsrfToken) session.getAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME);
|
||||||
|
|
|
@ -1,23 +1,72 @@
|
||||||
package io.jsonwebtoken.jjwtfun.config;
|
package io.jsonwebtoken.jjwtfun.config;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.web.csrf.CsrfFilter;
|
||||||
|
import org.springframework.security.web.csrf.CsrfToken;
|
||||||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.RequestDispatcher;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }")
|
||||||
|
String secret;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
CsrfTokenRepository jwtCsrfTokenRepository;
|
CsrfTokenRepository jwtCsrfTokenRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf().csrfTokenRepository(jwtCsrfTokenRepository).and()
|
.addFilterAfter(new JwtCsrfValidatorFilter(), CsrfFilter.class)
|
||||||
.authorizeRequests()
|
.csrf()
|
||||||
.antMatchers("/**")
|
.csrfTokenRepository(jwtCsrfTokenRepository)
|
||||||
.permitAll();
|
.ignoringAntMatchers("/dynamic-builder-general")
|
||||||
|
.ignoringAntMatchers("/dynamic-builder-specific")
|
||||||
|
.and().authorizeRequests()
|
||||||
|
.antMatchers("/**")
|
||||||
|
.permitAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class JwtCsrfValidatorFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
// NOTE: A real implementation should have a nonce cache so the token cannot be reused
|
||||||
|
|
||||||
|
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
|
||||||
|
|
||||||
|
// CsrfFilter already made sure the token matched.
|
||||||
|
// Here, we'll make sure it's not expired
|
||||||
|
if ("POST".equals(request.getMethod()) && token != null) {
|
||||||
|
try {
|
||||||
|
Jwts.parser()
|
||||||
|
.setSigningKey(secret.getBytes("UTF-8"))
|
||||||
|
.parseClaimsJws(token.getToken());
|
||||||
|
} catch (JwtException e) {
|
||||||
|
// most likely an ExpiredJwtException, but this will handle any
|
||||||
|
request.setAttribute("exception", e);
|
||||||
|
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||||
|
RequestDispatcher dispatcher = request.getRequestDispatcher("expired-jwt");
|
||||||
|
dispatcher.forward(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,12 @@ public class FormController {
|
||||||
|
|
||||||
@RequestMapping(value = "/jwt-csrf-form", method = POST)
|
@RequestMapping(value = "/jwt-csrf-form", method = POST)
|
||||||
public String csrfFormPost(@RequestParam(name = "_csrf") String csrf, Model model) {
|
public String csrfFormPost(@RequestParam(name = "_csrf") String csrf, Model model) {
|
||||||
|
|
||||||
model.addAttribute("csrf", csrf);
|
model.addAttribute("csrf", csrf);
|
||||||
|
|
||||||
return "jwt-csrf-form-result";
|
return "jwt-csrf-form-result";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/expired-jwt")
|
||||||
|
public String expiredJwt() {
|
||||||
|
return "expired-jwt";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<!--/*/ <th:block th:include="fragments/head :: head"/> /*/-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="box col-md-6 col-md-offset-3">
|
||||||
|
<h1>JWT CSRF Token expired</h1>
|
||||||
|
<h3 th:text="${exception.message}"></h3>
|
||||||
|
|
||||||
|
<a href="/jwt-csrf-form" class="btn btn-primary">Back</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -8,6 +8,17 @@
|
||||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,300,400italic,400,600italic,600,700italic,700,800italic,800" rel="stylesheet" type="text/css"/>
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,300,400italic,400,600italic,600,700italic,700,800italic,800" rel="stylesheet" type="text/css"/>
|
||||||
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
|
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
.box {
|
||||||
|
padding: 50px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js"></script>
|
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js"></script>
|
||||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="box col-md-6 col-md-offset-1">
|
<div class="box col-md-6 col-md-offset-3">
|
||||||
<h1>You made it!</h1>
|
<h1>You made it!</h1>
|
||||||
<div style="overflow: scroll">
|
<div style="overflow: scroll">
|
||||||
<h4 th:text="${csrf}">BLARG</h4>
|
<h4 th:text="${csrf}">BLARG</h4>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="box col-md-6 col-md-offset-1">
|
<div class="box col-md-6 col-md-offset-3">
|
||||||
<p/>
|
<p/>
|
||||||
<form method="post" th:action="@{/jwt-csrf-form}">
|
<form method="post" th:action="@{/jwt-csrf-form}">
|
||||||
<input type="submit" class="btn btn-primary" value="Click Me!"/>
|
<input type="submit" class="btn btn-primary" value="Click Me!"/>
|
||||||
|
|
Loading…
Reference in New Issue