Added support for JWT CSRF in Spring Security

This commit is contained in:
Micah Silverman 2016-06-27 13:26:58 -04:00
parent 6a057f33b1
commit f1630a0199
8 changed files with 213 additions and 0 deletions

View File

@ -28,11 +28,17 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@ -0,0 +1,25 @@
package io.jsonwebtoken.jjwtfun.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.csrf.CsrfTokenRepository;
@Configuration
public class CSRFConfig {
private static final Logger log = LoggerFactory.getLogger(CSRFConfig.class);
@Value("#{ @environment['jjwtfun.secret'] ?: 'secret' }")
String secret;
@Bean
@ConditionalOnMissingBean
public CsrfTokenRepository jwtCsrfTokenRepository() {
return new JWTCsrfTokenRepository(secret);
}
}

View File

@ -0,0 +1,76 @@
package io.jsonwebtoken.jjwtfun.config;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.UUID;
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 String secret;
public JWTCsrfTokenRepository(String secret) {
this.secret = secret;
}
@Override
public CsrfToken generateToken(HttpServletRequest request) {
String id = UUID.randomUUID().toString().replace("-", "");
Date now = new Date();
Date exp = new Date(System.currentTimeMillis() + (1000*60)); // 1 minute
String token;
try {
token = Jwts.builder()
.setId(id)
.setIssuedAt(now)
.setNotBefore(now)
.setExpiration(exp)
.signWith(SignatureAlgorithm.HS256, secret.getBytes("UTF-8"))
.compact();
} catch (UnsupportedEncodingException e) {
log.error("Unable to create CSRf JWT: {}", e.getMessage(), e);
token = id;
}
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
if (token == null) {
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME);
}
}
else {
HttpSession session = request.getSession();
session.setAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME, token);
}
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
return (CsrfToken) session.getAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME);
}
}

View File

@ -0,0 +1,23 @@
package io.jsonwebtoken.jjwtfun.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CsrfTokenRepository;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CsrfTokenRepository jwtCsrfTokenRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().csrfTokenRepository(jwtCsrfTokenRepository).and()
.authorizeRequests()
.antMatchers("/**")
.permitAll();
}
}

View File

@ -0,0 +1,26 @@
package io.jsonwebtoken.jjwtfun.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@Controller
public class FormController {
@RequestMapping(value = "/jwt-csrf-form", method = GET)
public String csrfFormGet() {
return "jwt-csrf-form";
}
@RequestMapping(value = "/jwt-csrf-form", method = POST)
public String csrfFormPost(@RequestParam(name = "_csrf") String csrf, Model model) {
model.addAttribute("csrf", csrf);
return "jwt-csrf-form-result";
}
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head">
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="viewport" content="width=device-width"/>
<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"/>
<!--[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/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
</head>
<body>
<p>Nothing to see here, move along.</p>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" 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-1">
<h1>You made it!</h1>
<div style="overflow: scroll">
<h4 th:text="${csrf}">BLARG</h4>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" 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-1">
<p/>
<form method="post" th:action="@{/jwt-csrf-form}">
<input type="submit" class="btn btn-primary" value="Click Me!"/>
</form>
</div>
</div>
</div>
</body>
</html>