Added support for JWT CSRF in Spring Security
This commit is contained in:
parent
6a057f33b1
commit
f1630a0199
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in New Issue