Project

General

Profile

« Previous | Next » 

Revision 57531

new authentication

View differences:

modules/dnet-orgs-database-application/trunk/src/main/java/eu/dnetlib/organizations/controller/SwaggerController.java
1
package eu.dnetlib.organizations.controller;
2

  
3
import org.springframework.stereotype.Controller;
4
import org.springframework.web.bind.annotation.RequestMapping;
5
import org.springframework.web.bind.annotation.RequestMethod;
6

  
7
@Controller
8
public class SwaggerController {
9

  
10
	@RequestMapping(value = { "/apidoc", "/api-doc", "/doc", "/swagger" }, method = RequestMethod.GET)
11
	public String apiDoc() {
12
		return "redirect:swagger-ui.html";
13
	}
14

  
15
}
modules/dnet-orgs-database-application/trunk/src/main/resources/static/messages/logout.html
1

  
2

  
3

  
4
<!doctype html>
5
<html lang="en">
6

  
7
<head>
8
<!-- Required meta tags -->
9
<meta charset="utf-8">
10
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
11

  
12
<!-- Bootstrap CSS -->
13
<link rel="stylesheet" href="../resources/css/bootstrap.min.css" />
14

  
15
<title>Organizations Database: Logout</title>
16

  
17
</head>
18

  
19
<body>
20
	<div ng-app="logoutApp" ng-controller="logoutCtrl" class="container">
21
		
22
		<div class="card text-center" style="margin-top: 25px">
23
			<div class="card-header">Organizations Database</div>
24
			<div class="card-body">
25
				<h5 class="card-title">You have been logged out !!!</h5>
26
				<p class="card-text">You will be redicted to the homepage between {{seconds}} second(s)</p>
27
				<button class="btn btn-sm btn-primary" ng-click="homepage()">return to homepage</button>
28
			</div>
29
		</div>
30
	</div>
31

  
32
	<script src="../resources/js/jquery-3.4.1.min.js"></script>
33
	<script src="../resources/js/popper.min.js"></script>
34
	<script src="../resources/js/bootstrap.min.js"></script>
35
	<script src="../resources/js/angular.min.js"></script>
36
	
37
	<script>
38
		angular
39
			.module('logoutApp', [])
40
			.controller('logoutCtrl', function($scope, $http, $timeout) {
41

  
42
				$scope.seconds   = 5;
43
				
44
				$scope.homepage  = function() { location.href = '/'; }
45
				$scope.onTimeout = function() {
46
					$scope.seconds--;
47
					if ($scope.seconds > 0) {
48
						$timeout($scope.onTimeout, 1000);
49
					} else {
50
						$scope.homepage();
51
					}
52
				};
53
				
54
				$timeout($scope.onTimeout, 1000);
55
				
56
			});
57
		</script>
58
	
59
</body>
60

  
61
</html>
modules/dnet-orgs-database-application/trunk/src/main/resources/templates/home.html
1
<!doctype html>
2
<html lang="en">
3

  
4
<head>
5
<!-- Required meta tags -->
6
<meta charset="utf-8">
7
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
8
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
9
<meta http-equiv="Pragma" content="no-cache">
10
<meta http-equiv="Expires" content="0">
11

  
12
<!-- Bootstrap CSS -->
13
<link rel="stylesheet" href="resources/css/bootstrap.min.css" />
14
<!-- Icons CSS -->
15
<link rel="stylesheet" href="resources/css/fontawesome-all.min.css">
16

  
17
<style type="text/css">
18
.table > tbody > tr > td {
19
     vertical-align: middle;
20
}
21
.card > .table {
22
	margin-bottom: 0 !important;
23
}
24

  
25
fieldset > legend {
26
	font-size: 1.2rem !important;  
27
}
28

  
29
</style>
30

  
31

  
32
<title>Organizations Database</title>
33

  
34
</head>
35

  
36
<body ng-app="orgs">
37
	<nav class="navbar navbar-expand-lg navbar-dark bg-primary" ng-controller="userCtrl">
38
		<a class="navbar-brand" href="#"> <img
39
			src="resources/images/openaire_logo_small.png" width="30" height="30" alt="">
40
			OpenOrgs Database
41
		</a>
42
		<button class="navbar-toggler" type="button" data-toggle="collapse"
43
			data-target="#navbarSupportedContent">
44
			<span class="navbar-toggler-icon"></span>
45
		</button>
46

  
47
		<div class="collapse navbar-collapse w-100 order-1" id="navbarSupportedContent">
48
			<ul class="navbar-nav mr-auto">
49
				<li class="nav-item active"><a class="nav-link" href="#">Search</a></li>
50
				<li class="nav-item dropdown"><a
51
					class="nav-link dropdown-toggle" href="javascript:void(0)" id="navbarDropdown"
52
					role="button" data-toggle="dropdown">Browse</a>
53
					<div class="dropdown-menu">
54
						<a class="dropdown-item" href="#!/countries">by country</a> <a
55
							class="dropdown-item" href="#!/types">by type</a>
56
					</div></li>
57
				<li class="nav-item"><a class="nav-link" href="#!/new">New</a></li>
58
				
59
			</ul>
60
		</div>
61
		
62
		<div class="navbar-collapse collapse w-100 order-2">
63
			<ul class="navbar-nav ml-auto">
64
				<li class="nav-item dropdown">
65
					<a class="nav-link dropdown-toggle" href="javascript:void(0)" data-toggle="dropdown">{{user}}</a>
66
					<div class="dropdown-menu">
67
						<a class="dropdown-item" href="javascript:void(0)" ng-click="logout()">Logout</a>
68
					</div>
69
				</li>
70
				<li class="nav-item"><a class="btn btn-outline-secondary" href="doc">API</a></li>
71
			</ul>
72
		</div>
73
		
74
	</nav>
75

  
76
	<div class="container-fluid small mt-4" ng-view></div>
77

  
78
	<script src="resources/js/jquery-3.4.1.min.js"></script>
79
	<script src="resources/js/popper.min.js"></script>
80
	<script src="resources/js/bootstrap.min.js"></script>
81
	<script src="resources/js/angular.min.js"></script>
82
	<script src="resources/js/angular-route.min.js"></script>
83
	<script src="resources/js/organizations.js"></script>
84
</body>
85

  
86
</html>
modules/dnet-orgs-database-application/trunk/src/main/resources/templates/redirect.html
1
<html>
2
<head>
3
	<title>Organizations Database</title>
4
	<meta http-equiv="refresh" content="0">
5
<head>
6
</html>
modules/dnet-orgs-database-application/trunk/src/main/java/eu/dnetlib/organizations/controller/HomeController.java
1 1
package eu.dnetlib.organizations.controller;
2 2

  
3
import java.io.IOException;
4
import java.security.Principal;
5
import java.util.UUID;
6

  
7
import javax.servlet.http.Cookie;
8
import javax.servlet.http.HttpServletResponse;
9

  
10
import org.apache.commons.io.IOUtils;
11
import org.springframework.http.HttpStatus;
12 3
import org.springframework.stereotype.Controller;
13
import org.springframework.web.bind.annotation.CookieValue;
4
import org.springframework.web.bind.annotation.GetMapping;
14 5
import org.springframework.web.bind.annotation.RequestMapping;
15 6
import org.springframework.web.bind.annotation.RequestMethod;
16 7

  
17 8
@Controller
18 9
public class HomeController {
19 10

  
20
	private static final String TEMPORARY_AUTH_CODE = "TEMP_COOKIE";
11
	@GetMapping("/")
12
	public String home() {
13
		return "/user";
14
	}
21 15

  
22
	@RequestMapping(value = "/", method = RequestMethod.GET)
23
	public void logout(@CookieValue(name = "auth_code", required = false) final String authCode, final HttpServletResponse res, final Principal principal)
24
			throws IOException {
25
		res.setContentType("text/html");
16
	@GetMapping("/login")
17
	public String login() {
18
		return "/login";
19
	}
26 20

  
27
		if (authCode == null) {
28
			res.addCookie(new Cookie("auth_code", TEMPORARY_AUTH_CODE));
29
			IOUtils.copy(getClass().getResourceAsStream("/templates/redirect.html"), res.getOutputStream());
30
		} else if (authCode.equals(TEMPORARY_AUTH_CODE) || principal == null) {
31
			res.setStatus(HttpStatus.UNAUTHORIZED.value());
32
			res.setHeader("WWW-Authenticate", "Basic realm=\"Realm\"");
33
			res.setHeader("X-Content-Type-Options", "nosniff");
34
			res.setHeader("X-XSS-Protection", "1; mode=block");
35
			res.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
36
			res.setHeader("Pragma", "no-cache");
37
			res.setHeader("Expires", "0");
38
			res.setHeader("X-Frame-Options", "DENY");
21
	@GetMapping("/403")
22
	public String error403() {
23
		return "/403";
24
	}
39 25

  
40
			final Cookie cookie = new Cookie("auth_code", UUID.randomUUID().toString());
41
			cookie.setMaxAge(-1);
26
	@RequestMapping(value = { "/doc", "/swagger" }, method = RequestMethod.GET)
27
	public String apiDoc() {
28
		return "redirect:swagger-ui.html";
29
	}
42 30

  
43
			res.addCookie(cookie);
44
			IOUtils.copy(getClass().getResourceAsStream("/templates/redirect.html"), res.getOutputStream());
45
		} else {
46
			IOUtils.copy(getClass().getResourceAsStream("/templates/home.html"), res.getOutputStream());
47
		}
48

  
49
	}
50 31
}
modules/dnet-orgs-database-application/trunk/src/main/java/eu/dnetlib/organizations/WebSecurityConfig.java
11 11
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
12 12
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
13 13
import org.springframework.security.crypto.password.PasswordEncoder;
14
import org.springframework.security.web.access.AccessDeniedHandler;
14 15

  
15 16
@Configuration
16 17
@EnableWebSecurity
......
19 20
	@Autowired
20 21
	private DataSource dataSource;
21 22

  
23
	@Autowired
24
	private AccessDeniedHandler accessDeniedHandler;
25

  
22 26
	@Override
23 27
	protected void configure(final HttpSecurity http) throws Exception {
24 28

  
25
		http.authorizeRequests()
26
				.antMatchers("/", "/swagger-ui.html", "/resources/**", "/messages/**").permitAll()
27
				.antMatchers("/api/**").fullyAuthenticated()
29
		http.csrf().disable()
30
				.authorizeRequests()
31
				.antMatchers("/", "/api/**").hasAnyRole("USER", "SUPERUSER")
32
				.antMatchers("/swagger-ui.html", "/doc", "/resources/**", "/webjars/**").permitAll()
33
				.anyRequest().authenticated()
28 34
				.and()
29
				.httpBasic()
35
				.formLogin()
36
				.loginPage("/login")
37
				.permitAll()
30 38
				.and()
31 39
				.logout()
32
				.logoutSuccessUrl("/messages/logout.html").permitAll()
33
				.deleteCookies("auth_code", "JSESSIONID")
34
				.clearAuthentication(true)
35
				.invalidateHttpSession(true)
40
				.permitAll()
36 41
				.and()
37
				.csrf().disable();
38

  
42
				.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
39 43
	}
40 44

  
41 45
	@Autowired
42 46
	public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
43

  
44
		auth.jdbcAuthentication()
45
				.dataSource(dataSource)
47
		auth.jdbcAuthentication().dataSource(dataSource)
46 48
				.usersByUsernameQuery("select email, password, valid from users where email=? and valid=true")
47 49
				.authoritiesByUsernameQuery("select email, 'ROLE_'||role from users where email=? and valid=true");
48 50
	}
modules/dnet-orgs-database-application/trunk/src/main/java/eu/dnetlib/organizations/MyAccessDeniedHandler.java
1
package eu.dnetlib.organizations;
2

  
3
import java.io.IOException;
4

  
5
import javax.servlet.ServletException;
6
import javax.servlet.http.HttpServletRequest;
7
import javax.servlet.http.HttpServletResponse;
8

  
9
import org.slf4j.Logger;
10
import org.slf4j.LoggerFactory;
11
import org.springframework.security.access.AccessDeniedException;
12
import org.springframework.security.core.Authentication;
13
import org.springframework.security.core.context.SecurityContextHolder;
14
import org.springframework.security.web.access.AccessDeniedHandler;
15
import org.springframework.stereotype.Component;
16

  
17
@Component
18
public class MyAccessDeniedHandler implements AccessDeniedHandler {
19

  
20
	private static Logger logger = LoggerFactory.getLogger(MyAccessDeniedHandler.class);
21

  
22
	@Override
23
	public void handle(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final AccessDeniedException e)
24
			throws IOException, ServletException {
25

  
26
		final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
27

  
28
		if (auth != null) {
29
			logger.warn(String.format("User '%s' attempted to access the protected URL: %s", auth.getName(), httpServletRequest.getRequestURI()));
30
		}
31

  
32
		httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/403");
33
	}
34

  
35
}
modules/dnet-orgs-database-application/trunk/src/main/resources/sql/importNewRels.sql
10 10

  
11 11
COPY tmp_simrels(local_id, oa_original_id, oa_name, oa_acronym, oa_country, oa_url, oa_collectedfrom) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/src/main/resources/tmp_data/rels.part-r-00000.tsv' DELIMITER E'\t';;
12 12

  
13
DELETE FROM openaire_simrels WHERE reltype = 'suggested';
14

  
13 15
INSERT INTO openaire_simrels (local_id, oa_original_id, oa_name, oa_acronym, oa_country, oa_url, oa_collectedfrom)
14 16
SELECT local_id, oa_original_id, oa_name, oa_acronym, oa_country, oa_url, oa_collectedfrom
15 17
FROM tmp_simrels
modules/dnet-orgs-database-application/trunk/src/main/resources/static/resources/html/list.html
1
<p ng-if="orgs.number">Page: {{orgs.number + 1}} / {{orgs.totalPages}}<br />Total elements: {{orgs.totalElements}}<br />Searching for: {{fieldValue}}</p>
2
<p ng-if="!orgs.number">Page:<br />Total elements:<br />Searching for: {{fieldValue}}</p>
1
<p ng-if="orgs.number || orgs.number === 0">Page: {{orgs.number + 1}} / {{orgs.totalPages}}<br />Total elements: {{orgs.totalElements}}<br />Searching for: {{fieldValue}}</p>
2
<p ng-if="!(orgs.number || orgs.number === 0)">Page:<br />Total elements:<br />Searching for: {{fieldValue}}</p>
3 3

  
4 4
<nav>
5 5
	<ul class="pagination justify-content-center">
modules/dnet-orgs-database-application/trunk/src/main/resources/static/resources/js/organizations.js
34 34
	}, function errorCallback(res) {
35 35
		alert('ERROR: ' + res.data.error + ' (' + res.data.message + ')');
36 36
	});
37
	
38
	$scope.logout = function() {
39
		location.href= "/logout";
40
	}
41 37
});
42 38

  
43 39
orgsModule.controller('newOrgCtrl', function ($scope, $http, $routeParams, $location) {
modules/dnet-orgs-database-application/trunk/src/main/resources/templates/user.html
1
<!doctype html>
2
<html lang="en">
3

  
4
<head>
5
<!-- Required meta tags -->
6
<meta charset="utf-8">
7
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
8
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
9
<meta http-equiv="Pragma" content="no-cache">
10
<meta http-equiv="Expires" content="0">
11

  
12
<!-- Bootstrap CSS -->
13
<link rel="stylesheet" href="resources/css/bootstrap.min.css" />
14
<!-- Icons CSS -->
15
<link rel="stylesheet" href="resources/css/fontawesome-all.min.css">
16

  
17
<style type="text/css">
18
.table > tbody > tr > td {
19
     vertical-align: middle;
20
}
21
.card > .table {
22
	margin-bottom: 0 !important;
23
}
24

  
25
fieldset > legend {
26
	font-size: 1.2rem !important;  
27
}
28

  
29
</style>
30

  
31

  
32
<title>Organizations Database</title>
33

  
34
</head>
35

  
36
<body ng-app="orgs">
37
	<nav class="navbar navbar-expand-lg navbar-dark bg-primary" ng-controller="userCtrl">
38
		<a class="navbar-brand" href="#"> <img
39
			src="resources/images/openaire_logo_small.png" width="30" height="30" alt="OpenOrgs Database">
40
			OpenOrgs Database
41
		</a>
42
		<button class="navbar-toggler" type="button" data-toggle="collapse"
43
			data-target="#navbarSupportedContent">
44
			<span class="navbar-toggler-icon"></span>
45
		</button>
46

  
47
		<div class="collapse navbar-collapse w-100 order-1" id="navbarSupportedContent">
48
			<ul class="navbar-nav mr-auto">
49
				<li class="nav-item active"><a class="nav-link" href="#">Search</a></li>
50
				<li class="nav-item dropdown"><a
51
					class="nav-link dropdown-toggle" href="javascript:void(0)" id="navbarDropdown"
52
					role="button" data-toggle="dropdown">Browse</a>
53
					<div class="dropdown-menu">
54
						<a class="dropdown-item" href="#!/countries">by country</a> <a
55
							class="dropdown-item" href="#!/types">by type</a>
56
					</div></li>
57
				<li class="nav-item"><a class="nav-link" href="#!/new">New</a></li>
58
				
59
			</ul>
60
		</div>
61
		
62
		<div class="navbar-collapse collapse w-100 order-2">
63
			<ul class="navbar-nav ml-auto">
64
				<li class="nav-item dropdown">
65
					<a class="nav-link dropdown-toggle" href="javascript:void(0)" data-toggle="dropdown">{{user}}</a>
66
					<div class="dropdown-menu">
67
						<a class="dropdown-item" th:href="@{/logout}">Logout</a>
68
					</div>
69
				</li>
70
				<li class="nav-item"><a class="btn btn-outline-secondary" href="doc">API</a></li>
71
			</ul>
72
		</div>
73
		
74
	</nav>
75

  
76
	<div class="container-fluid small mt-4" ng-view></div>
77

  
78
	<script src="resources/js/jquery-3.4.1.min.js"></script>
79
	<script src="resources/js/popper.min.js"></script>
80
	<script src="resources/js/bootstrap.min.js"></script>
81
	<script src="resources/js/angular.min.js"></script>
82
	<script src="resources/js/angular-route.min.js"></script>
83
	<script src="resources/js/organizations.js"></script>
84
</body>
85

  
86
</html>
modules/dnet-orgs-database-application/trunk/src/main/resources/templates/403.html
1
<!DOCTYPE HTML>
2
<html xmlns:th="http://www.thymeleaf.org">
3

  
4
<head>
5
	<meta charset="utf-8">
6
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
	<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
8
	<meta http-equiv="Pragma" content="no-cache">
9
	<meta http-equiv="Expires" content="0">
10

  
11
	<!-- Bootstrap CSS -->
12
	<link rel="stylesheet" href="resources/css/bootstrap.min.css" />
13
	
14
	<title>Organizations Database: not authorized</title>
15
</head>
16

  
17
<body>
18

  
19
	<div class="container">
20
		<nav class="navbar navbar-expand-lg navbar-dark bg-primary" ng-controller="userCtrl">
21
			<a class="navbar-brand" href="#"> 
22
				<img src="resources/images/openaire_logo_small.png" width="30" height="30" alt="OpenOrgs Database"> OpenOrgs Database
23
			</a>
24
		</nav>
25
	    
26
	    
27
	    <div class="card text-center" style="margin-top: 25px">
28
			<div class="card-body">
29
				<h5 class="card-title">403 - Access is denied</h5>
30
				<p class="card-text" th:inline="text">Hello '[[${#httpServletRequest.remoteUser}]]', you do not have permission to access this page.</p>
31
			</div>
32
		</div>
33
    
34
	
35
	</div>
36

  
37
	<script src="resources/js/bootstrap.min.js"></script>
38

  
39
</body>
40

  
41
</html>
modules/dnet-orgs-database-application/trunk/src/main/resources/templates/login.html
1
<!DOCTYPE html>
2

  
3
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
4

  
5
<head>
6
	<meta charset="utf-8">
7
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
8
	<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
9
	<meta http-equiv="Pragma" content="no-cache">
10
	<meta http-equiv="Expires" content="0">
11

  
12
	<!-- Bootstrap CSS -->
13
	<link rel="stylesheet" href="resources/css/bootstrap.min.css" />
14
	
15
	<title>Organizations Database: Login</title>
16
</head>
17

  
18
<body>
19

  
20
	<div>
21
	    <nav class="navbar navbar-expand-lg navbar-dark bg-primary" ng-controller="userCtrl">
22
			<a class="navbar-brand" href="#"> 
23
				<img src="resources/images/openaire_logo_small.png" width="30" height="30" alt="OpenOrgs Database"> OpenOrgs Database
24
			</a>
25
		</nav>
26
	</div>
27

  
28
	<div class="container">
29
	
30
	    <div class="row" style="margin-top:20px">
31
	        <div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
32
	            <form th:action="@{/login}" method="post">
33
	                <fieldset>
34
	                    <h1>Please Sign In</h1>
35
	
36
	                    <div th:if="${param.error}">
37
	                        <div class="alert alert-danger">
38
	                            Invalid username and password.
39
	                        </div>
40
	                    </div>
41
	                    <div th:if="${param.logout}">
42
	                        <div class="alert alert-info">
43
	                            You have been logged out.
44
	                        </div>
45
	                    </div>
46
	
47
	                    <div class="form-group">
48
	                        <input type="text" name="username" id="username" class="form-control input-lg"
49
	                               placeholder="UserName" required="true" autofocus="true"/>
50
	                    </div>
51
	                    <div class="form-group">
52
	                        <input type="password" name="password" id="password" class="form-control input-lg"
53
	                               placeholder="Password" required="true"/>
54
	                    </div>
55
	
56
	                    <div class="row">
57
	                        <div class="col-xs-6 col-sm-6 col-md-6">
58
	                            <input type="submit" class="btn btn-lg btn-primary btn-block" value="Sign In"/>
59
	                        </div>
60
	                        <div class="col-xs-6 col-sm-6 col-md-6">
61
	                        </div>
62
	                    </div>
63
	                </fieldset>
64
	            </form>
65
	        </div>
66
	    </div>
67
	
68
	</div>
69

  
70
	<script src="resources/js/bootstrap.min.js"></script>
71

  
72
</body>
73
</html>
modules/dnet-orgs-database-application/trunk/pom.xml
66 66
			<artifactId>spring-boot-starter-web</artifactId>
67 67
		</dependency>
68 68
		<dependency>
69
            <groupId>org.springframework.boot</groupId>
70
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
71
        </dependency>
72
		<dependency>
69 73
			<groupId>org.springframework.boot</groupId>
70 74
			<artifactId>spring-boot-starter-data-jpa</artifactId>
71 75
		</dependency>
......
78 82
			<artifactId>spring-boot-starter-security</artifactId>
79 83
		</dependency>
80 84
		<dependency>
85
            <groupId>org.thymeleaf.extras</groupId>
86
            <artifactId>thymeleaf-extras-springsecurity4</artifactId>
87
        </dependency>
88
		<dependency>
81 89
			<groupId>org.postgresql</groupId>
82 90
			<artifactId>postgresql</artifactId>
83 91
		</dependency>
......
86 94
			<artifactId>hibernate-types-52</artifactId>
87 95
			<version>2.3.5</version>
88 96
		</dependency>
97
		 <!-- hot swapping, disable cache for template, enable live reload -->
98
        <dependency>
99
            <groupId>org.springframework.boot</groupId>
100
            <artifactId>spring-boot-devtools</artifactId>
101
            <optional>true</optional>
102
        </dependency>
89 103

  
90 104
		<!-- Utils -->
91 105
		<dependency>

Also available in: Unified diff