Spring security series

This commit is contained in:
javadevjournal
2020-09-08 15:49:28 -07:00
parent 57cba77bd6
commit d2490de509
15 changed files with 125 additions and 74 deletions

View File

@@ -69,6 +69,13 @@
<version>3.11</version> <version>3.11</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@@ -57,7 +57,6 @@
<orderEntry type="library" name="Maven: org.thymeleaf:thymeleaf:3.0.11.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.thymeleaf:thymeleaf:3.0.11.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.attoparser:attoparser:2.0.5.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.attoparser:attoparser:2.0.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.unbescape:unbescape:1.1.6.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.unbescape:unbescape:1.1.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" />
<orderEntry type="library" name="Maven: org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.4.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-jpa:2.3.1.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-jpa:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-aop:2.3.1.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-aop:2.3.1.RELEASE" level="project" />
@@ -77,13 +76,19 @@
<orderEntry type="library" name="Maven: org.glassfish.jaxb:jaxb-runtime:2.3.3" level="project" /> <orderEntry type="library" name="Maven: org.glassfish.jaxb:jaxb-runtime:2.3.3" level="project" />
<orderEntry type="library" name="Maven: org.glassfish.jaxb:txw2:2.3.3" level="project" /> <orderEntry type="library" name="Maven: org.glassfish.jaxb:txw2:2.3.3" level="project" />
<orderEntry type="library" name="Maven: com.sun.istack:istack-commons-runtime:3.0.11" level="project" /> <orderEntry type="library" name="Maven: com.sun.istack:istack-commons-runtime:3.0.11" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: com.sun.activation:jakarta.activation:1.2.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-jpa:2.3.1.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.springframework.data:spring-data-jpa:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:2.3.1.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-orm:5.2.7.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.springframework:spring-orm:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-tx:5.2.7.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.springframework:spring-tx:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aspects:5.2.7.RELEASE" level="project" /> <orderEntry type="library" name="Maven: org.springframework:spring-aspects:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:8.0.18" level="project" /> <orderEntry type="library" name="Maven: mysql:mysql-connector-java:8.0.18" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-mail:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context-support:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.sun.mail:jakarta.mail:1.6.5" level="project" />
<orderEntry type="library" name="Maven: com.sun.activation:jakarta.activation:1.2.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.11" level="project" />
<orderEntry type="library" name="Maven: org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.3.1.RELEASE" level="project" /> <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.3.1.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.3.1.RELEASE" level="project" /> <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.3.1.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.3.1.RELEASE" level="project" /> <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.3.1.RELEASE" level="project" />

View File

@@ -37,4 +37,4 @@ public class DefaultEmailService implements EmailService{
mimeMessageHelper.setText(emailContent, true); mimeMessageHelper.setText(emailContent, true);
emailSender.send(message); emailSender.send(message);
} }
} }

View File

@@ -10,8 +10,11 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.sql.DataSource;
@EnableWebSecurity @EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter { public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
@@ -22,18 +25,27 @@ public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired @Autowired
PasswordEncoder passwordEncoder; PasswordEncoder passwordEncoder;
@Autowired
private DataSource dataSource;
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() http.authorizeRequests()
.antMatchers("/login", "/register") .antMatchers("/login", "/register")
.permitAll() .permitAll()
.antMatchers("/account/**").hasAuthority("USER") .antMatchers("/account/**").hasAuthority("USER")
.and()
.rememberMe().tokenRepository(persistentTokenRepository())
.and() .and()
.formLogin(form -> form .formLogin(form -> form
.defaultSuccessUrl("/account/home") .defaultSuccessUrl("/account/home")
.loginPage("/login") .loginPage("/login")
.failureUrl("/login?error=true") .failureUrl("/login?error=true")
)
.logout(logout->logout
.deleteCookies("dummyCookie")
); );
} }
@Override @Override
@@ -55,5 +67,10 @@ public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
auth.authenticationProvider(authProvider()); auth.authenticationProvider(authProvider());
} }
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
db.setDataSource(dataSource);
return db;
}
} }

View File

@@ -9,7 +9,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service("userDetailsService")
public class CustomUserDetailService implements UserDetailsService{ public class CustomUserDetailService implements UserDetailsService{
@Autowired @Autowired

View File

@@ -5,7 +5,6 @@ import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*; import javax.persistence.*;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Entity @Entity

View File

@@ -51,6 +51,7 @@ public class DefaultUserService implements UserService{
encodePassword(user, userEntity); encodePassword(user, userEntity);
userRepository.save(userEntity); userRepository.save(userEntity);
sendRegistrationConfirmationEmail(userEntity); sendRegistrationConfirmationEmail(userEntity);
} }
@Override @Override
@@ -58,17 +59,13 @@ public class DefaultUserService implements UserService{
return userRepository.findByEmail(email)!=null ? true : false; return userRepository.findByEmail(email)!=null ? true : false;
} }
private void encodePassword(UserData source, UserEntity target){
target.setPassword(passwordEncoder.encode(source.getPassword()));
}
@Override @Override
public void sendRegistrationConfirmationEmail(UserEntity userEntity){ public void sendRegistrationConfirmationEmail(UserEntity user) {
SecureToken secureToken = secureTokenService.createSecureToken(); SecureToken secureToken= secureTokenService.createSecureToken();
secureToken.setUser(userEntity); secureToken.setUser(user);
secureTokenRepository.save(secureToken); secureTokenRepository.save(secureToken);
AccountVerificationEmailContext emailContext = new AccountVerificationEmailContext(); AccountVerificationEmailContext emailContext = new AccountVerificationEmailContext();
emailContext.init(userEntity); emailContext.init(user);
emailContext.setToken(secureToken.getToken()); emailContext.setToken(secureToken.getToken());
emailContext.buildVerificationUrl(baseURL, secureToken.getToken()); emailContext.buildVerificationUrl(baseURL, secureToken.getToken());
try { try {
@@ -76,6 +73,7 @@ public class DefaultUserService implements UserService{
} catch (MessagingException e) { } catch (MessagingException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override @Override
@@ -95,4 +93,10 @@ public class DefaultUserService implements UserService{
secureTokenService.removeToken(secureToken); secureTokenService.removeToken(secureToken);
return true; return true;
} }
private void encodePassword(UserData source, UserEntity target){
target.setPassword(passwordEncoder.encode(source.getPassword()));
}
} }

View File

@@ -4,12 +4,23 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@Controller @Controller
@RequestMapping("/account") @RequestMapping("/account")
public class HomeController { public class HomeController {
@GetMapping("/home") @GetMapping("/home")
public String home(){ public String home(HttpServletResponse response){
setDummyCookie(response);
return "index"; return "index";
} }
private void setDummyCookie(HttpServletResponse response){
Cookie cookie = new Cookie("dummyCookie", "dummy_cookie");
cookie.setMaxAge(2592000);
cookie.setPath("/");
response.addCookie(cookie);
}
} }

View File

@@ -58,12 +58,12 @@ public class RegistrationController {
@GetMapping("/verify") @GetMapping("/verify")
public String verifyCustomer(@RequestParam(required = false) String token, final Model model, RedirectAttributes redirAttr){ public String verifyCustomer(@RequestParam(required = false) String token, final Model model, RedirectAttributes redirAttr){
if(StringUtils.isEmpty(token)){ if(StringUtils.isEmpty(token)){
redirAttr.addFlashAttribute("tokenError", messageSource.getMessage("user.registration.verification.missing.token", null,LocaleContextHolder.getLocale())); redirAttr.addFlashAttribute("tokenError", messageSource.getMessage("user.registration.verification.missing.token", null,LocaleContextHolder.getLocale()));
return REDIRECT_LOGIN; return REDIRECT_LOGIN;
} }
try { try {
userService.verifyUser(token); userService.verifyUser(token);
} catch (InvalidTokenException e) { } catch (InvalidTokenException e) {
redirAttr.addFlashAttribute("tokenError", messageSource.getMessage("user.registration.verification.invalid.token", null,LocaleContextHolder.getLocale())); redirAttr.addFlashAttribute("tokenError", messageSource.getMessage("user.registration.verification.invalid.token", null,LocaleContextHolder.getLocale()));
return REDIRECT_LOGIN; return REDIRECT_LOGIN;
@@ -72,4 +72,5 @@ public class RegistrationController {
redirAttr.addFlashAttribute("verifiedAccountMsg", messageSource.getMessage("user.registration.verification.success", null,LocaleContextHolder.getLocale())); redirAttr.addFlashAttribute("verifiedAccountMsg", messageSource.getMessage("user.registration.verification.success", null,LocaleContextHolder.getLocale()));
return REDIRECT_LOGIN; return REDIRECT_LOGIN;
} }
} }

View File

@@ -1,6 +1,6 @@
#Enable below property to check what all Spring security filters are configured and used by Spring security.This can also help #Enable below property to check what all Spring security filters are configured and used by Spring security.This can also help
# to get better understanding as how security filters work internally. # to get better understanding as how security filters work internally.
logging.level.org.springframework.security.web.FilterChainProxy=DEBUG #logging.level.org.springframework.security.web.FilterChainProxy=DEBUG
#database configuration.Change these based on your setup #database configuration.Change these based on your setup
spring.jpa.generate-ddl=true spring.jpa.generate-ddl=true
@@ -19,3 +19,10 @@ jdj.secure.token.validity = 28800
site.base.url.http=http://localhost:8080 site.base.url.http=http://localhost:8080
site.base.url.https=http://localhost:8080 site.base.url.https=http://localhost:8080
spring.mail.host=test
spring.mail.port=25
spring.mail.username=abc@dummy.com
spring.mail.password=test
#spring.mail.properties.mail.smtp.auth=true
#spring.mail.properties.mail.smtp.starttls.enable=true

View File

@@ -36,7 +36,7 @@
</div> </div>
<div class="input-group mb-3"> <div class="input-group mb-3">
<input type="email" class="form-control" name="username" placeholder="Email"> <input type="email" class="form-control" name="username" placeholder="Email" autocomplete=”off”>
<div class="input-group-append"> <div class="input-group-append">
<div class="input-group-text"> <div class="input-group-text">
<span class="fas fa-envelope"></span> <span class="fas fa-envelope"></span>
@@ -55,7 +55,7 @@
<div class="row"> <div class="row">
<div class="col-8"> <div class="col-8">
<div class="icheck-primary"> <div class="icheck-primary">
<input type="checkbox" id="remember"> <input type="checkbox" id="remember" name="remember-me">
<label for="remember"> <label for="remember">
Remember Me Remember Me
</label> </label>

View File

@@ -18,4 +18,5 @@
<link rel="stylesheet" th:href="@{/dist/css/adminlte.min.css}"> <link rel="stylesheet" th:href="@{/dist/css/adminlte.min.css}">
<!-- Google Font: Source Sans Pro --> <!-- Google Font: Source Sans Pro -->
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
</head> </head>

View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
<!-- Left navbar links -->
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="index.html" class="nav-link">Home</a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="#" class="nav-link">Contact</a>
</li>
</ul>
<!-- SEARCH FORM -->
<form class="form-inline ml-3">
<div class="input-group input-group-sm">
<input class="form-control form-control-navbar" type="search" placeholder="Search" aria-label="Search">
<div class="input-group-append">
<button class="btn btn-navbar" type="submit">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</form>
<div sec:authorize="isAuthenticated()">
<header class="main-header">
<div class="navbar-custom-menu">
<ul class="navbar-nav">
<li class="dropdown user user-menu">
<!--<a th:href="@{/logout}">Logout</a> -->
<a href="javascript: document.logoutForm.submit()" class="dropdown-toggle">Sign out</a>
<form name="logoutForm" th:action="@{/logout}" method="post" th:hidden="true">
<input hidden type="submit" value="Sign Out"/>
</form>
</ul>
</div>
</header>
</div>
</nav>

View File

@@ -3,7 +3,7 @@
This is a starter template page. Use this page to start your new project from This is a starter template page. Use this page to start your new project from
scratch. This page gets rid of all links and provides the needed markup only. scratch. This page gets rid of all links and provides the needed markup only.
--> -->
<html xmlns:th="http://www.thymeleaf.org" lang="en"> <html xmlns:th="http://www.thymeleaf.org" lang="en" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head th:replace="core/header :: head"> <meta charset="utf-8"> <head th:replace="core/header :: head"> <meta charset="utf-8">
</head> </head>
<body class="hold-transition sidebar-mini"> <body class="hold-transition sidebar-mini">
@@ -43,8 +43,6 @@ scratch. This page gets rid of all links and provides the needed markup only.
<aside class="main-sidebar sidebar-dark-primary elevation-4"> <aside class="main-sidebar sidebar-dark-primary elevation-4">
<!-- Brand Logo --> <!-- Brand Logo -->
<a href="index3.html" class="brand-link"> <a href="index3.html" class="brand-link">
<img src="dist/img/AdminLTELogo.png" alt="JavaDevJournal" class="brand-image img-circle elevation-3"
style="opacity: .8">
<span class="brand-text font-weight-light">JavaDevJournal</span> <span class="brand-text font-weight-light">JavaDevJournal</span>
</a> </a>
@@ -80,7 +78,7 @@ scratch. This page gets rid of all links and provides the needed markup only.
Anything you want Anything you want
</div> </div>
<!-- Default to the left --> <!-- Default to the left -->
<strong>Copyright &copy; 2014-2020 <a href="https://javadevjournal.com">Java Dev Journal</a>.</strong> All rights reserved. <strong>Copyright &copy; 2014-2020 <a href="https://www.javadevjournal.com">Java Dev Journal</a>.</strong> All rights reserved.
</footer> </footer>
</div> </div>
<!-- ./wrapper --> <!-- ./wrapper -->

View File

@@ -1,58 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en"> <html xmlns:th="http://www.thymeleaf.org" lang="en">
<head> <head th:replace="core/header :: head"> <meta charset="utf-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>JavaDevJournal | Learn Spring Security</title>
<!-- Font Awesome Icons -->
<link rel="stylesheet" th:href="@{/plugins/fontawesome-free/css/all.min.css}">
<!-- IonIcons -->
<link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<!-- Theme style -->
<link rel="stylesheet" th:href="@{/dist/css/adminlte.min.css}">
<!-- Google Font: Source Sans Pro -->
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
</head> </head>
<body class="hold-transition sidebar-mini"> <body class="hold-transition sidebar-mini">
<div class="wrapper"> <div class="wrapper">
<!-- Navbar --> <nav th:replace="core/userProfile :: nav"></nav>
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
<!-- Left navbar links -->
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="index.html" class="nav-link">Home</a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="#" class="nav-link">Contact</a>
</li>
</ul>
<!-- SEARCH FORM -->
<form class="form-inline ml-3">
<div class="input-group input-group-sm">
<input class="form-control form-control-navbar" type="search" placeholder="Search" aria-label="Search">
<div class="input-group-append">
<button class="btn btn-navbar" type="submit">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</form>
</nav>
<!-- /.navbar --> <!-- /.navbar -->
<!-- Main Sidebar Container --> <!-- Main Sidebar Container -->
<aside class="main-sidebar sidebar-dark-primary elevation-4"> <aside class="main-sidebar sidebar-dark-primary elevation-4">
<!-- Brand Logo --> <!-- Brand Logo -->
<a href="index.html" class="brand-link"> <a href="index.html" class="brand-link">
<img src="dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3"
style="opacity: .8">
<span class="brand-text font-weight-light">Learn Spring Security</span> <span class="brand-text font-weight-light">Learn Spring Security</span>
</a> </a>
@@ -60,9 +18,6 @@
<div class="sidebar"> <div class="sidebar">
<!-- Sidebar user panel (optional) --> <!-- Sidebar user panel (optional) -->
<div class="user-panel mt-3 pb-3 mb-3 d-flex"> <div class="user-panel mt-3 pb-3 mb-3 d-flex">
<div class="image">
<img src="dist/img/user.png" class="img-circle elevation-2" alt="User Image">
</div>
<div class="info"> <div class="info">
<a href="#" class="d-block">Java Dev Journal</a> <a href="#" class="d-block">Java Dev Journal</a>
</div> </div>