diff --git a/apache-shiro/pom.xml b/apache-shiro/pom.xml
index 3df6283437..59bb91d400 100644
--- a/apache-shiro/pom.xml
+++ b/apache-shiro/pom.xml
@@ -39,10 +39,19 @@
jcl-over-slf4j
runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
- 1.4.0
+ 1.5.3
1.2.17
diff --git a/apache-shiro/src/main/java/com/baeldung/comparison/shiro/CustomRealm.java b/apache-shiro/src/main/java/com/baeldung/comparison/shiro/CustomRealm.java
new file mode 100644
index 0000000000..d82fb1a5d5
--- /dev/null
+++ b/apache-shiro/src/main/java/com/baeldung/comparison/shiro/CustomRealm.java
@@ -0,0 +1,96 @@
+package com.baeldung.comparison.shiro;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UnknownAccountException;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.jdbc.JdbcRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CustomRealm extends JdbcRealm {
+
+ private Logger logger = LoggerFactory.getLogger(CustomRealm.class);
+
+ private Map credentials = new HashMap<>();
+ private Map> roles = new HashMap<>();
+ private Map> permissions = new HashMap<>();
+
+ {
+ credentials.put("Tom", "password");
+ credentials.put("Jerry", "password");
+
+ roles.put("Jerry", new HashSet<>(Arrays.asList("ADMIN")));
+ roles.put("Tom", new HashSet<>(Arrays.asList("USER")));
+
+ permissions.put("ADMIN", new HashSet<>(Arrays.asList("READ", "WRITE")));
+ permissions.put("USER", new HashSet<>(Arrays.asList("READ")));
+ }
+
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+
+ UsernamePasswordToken userToken = (UsernamePasswordToken) token;
+
+ if (userToken.getUsername() == null || userToken.getUsername()
+ .isEmpty() || !credentials.containsKey(userToken.getUsername())) {
+ throw new UnknownAccountException("User doesn't exist");
+ }
+
+ return new SimpleAuthenticationInfo(userToken.getUsername(), credentials.get(userToken.getUsername()), getName());
+ }
+
+ @Override
+ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+ Set roles = new HashSet<>();
+ Set permissions = new HashSet<>();
+
+ for (Object user : principals) {
+ try {
+ roles.addAll(getRoleNamesForUser(null, (String) user));
+ permissions.addAll(getPermissions(null, null, roles));
+ } catch (SQLException e) {
+ logger.error(e.getMessage());
+ }
+ }
+ SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo(roles);
+ authInfo.setStringPermissions(permissions);
+ return authInfo;
+ }
+
+ @Override
+ protected Set getRoleNamesForUser(Connection conn, String username) throws SQLException {
+ if (!roles.containsKey(username)) {
+ throw new SQLException("User doesn't exist");
+ }
+ return roles.get(username);
+ }
+
+ @Override
+ protected Set getPermissions(Connection conn, String username, Collection roles) throws SQLException {
+ Set userPermissions = new HashSet<>();
+
+ for (String role : roles) {
+ if (!permissions.containsKey(role)) {
+ throw new SQLException("Role doesn't exist");
+ }
+ userPermissions.addAll(permissions.get(role));
+ }
+ return userPermissions;
+ }
+
+}
diff --git a/apache-shiro/src/main/java/com/baeldung/comparison/shiro/ShiroApplication.java b/apache-shiro/src/main/java/com/baeldung/comparison/shiro/ShiroApplication.java
new file mode 100644
index 0000000000..92d43ea957
--- /dev/null
+++ b/apache-shiro/src/main/java/com/baeldung/comparison/shiro/ShiroApplication.java
@@ -0,0 +1,33 @@
+package com.baeldung.comparison.shiro;
+
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
+import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+
+@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
+public class ShiroApplication {
+
+ public static void main(String... args) {
+ SpringApplication.run(ShiroApplication.class, args);
+ }
+
+ @Bean
+ public Realm customRealm() {
+ return new CustomRealm();
+ }
+
+ @Bean
+ public ShiroFilterChainDefinition shiroFilterChainDefinition() {
+ DefaultShiroFilterChainDefinition filter = new DefaultShiroFilterChainDefinition();
+
+ filter.addPathDefinition("/home", "authc");
+ filter.addPathDefinition("/**", "anon");
+
+ return filter;
+ }
+
+}
diff --git a/apache-shiro/src/main/java/com/baeldung/comparison/shiro/controllers/ShiroController.java b/apache-shiro/src/main/java/com/baeldung/comparison/shiro/controllers/ShiroController.java
new file mode 100644
index 0000000000..b819286c57
--- /dev/null
+++ b/apache-shiro/src/main/java/com/baeldung/comparison/shiro/controllers/ShiroController.java
@@ -0,0 +1,99 @@
+package com.baeldung.comparison.shiro.controllers;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import com.baeldung.comparison.shiro.models.UserCredentials;
+
+@Controller
+public class ShiroController {
+
+ private Logger logger = LoggerFactory.getLogger(ShiroController.class);
+
+ @GetMapping("/")
+ public String getIndex() {
+ return "comparison/index";
+ }
+
+ @GetMapping("/login")
+ public String showLoginPage() {
+ return "comparison/login";
+ }
+
+ @PostMapping("/login")
+ public String doLogin(HttpServletRequest req, UserCredentials credentials, RedirectAttributes attr) {
+
+ Subject subject = SecurityUtils.getSubject();
+
+ if (!subject.isAuthenticated()) {
+ UsernamePasswordToken token = new UsernamePasswordToken(credentials.getUsername(), credentials.getPassword());
+ try {
+ subject.login(token);
+ } catch (AuthenticationException ae) {
+ logger.error(ae.getMessage());
+ attr.addFlashAttribute("error", "Invalid Credentials");
+ return "redirect:/login";
+ }
+ }
+ return "redirect:/home";
+ }
+
+ @GetMapping("/home")
+ public String getMeHome(Model model) {
+
+ addUserAttributes(model);
+
+ return "comparison/home";
+ }
+
+ @GetMapping("/admin")
+ public String adminOnly(Model model) {
+ addUserAttributes(model);
+
+ Subject currentUser = SecurityUtils.getSubject();
+ if (currentUser.hasRole("ADMIN")) {
+ model.addAttribute("adminContent", "only admin can view this");
+ }
+ return "comparison/home";
+ }
+
+ @PostMapping("/logout")
+ public String logout() {
+ Subject subject = SecurityUtils.getSubject();
+ subject.logout();
+ return "redirect:/";
+ }
+
+ private void addUserAttributes(Model model) {
+ Subject currentUser = SecurityUtils.getSubject();
+ String permission = "";
+
+ if (currentUser.hasRole("ADMIN")) {
+ model.addAttribute("role", "ADMIN");
+ } else if (currentUser.hasRole("USER")) {
+ model.addAttribute("role", "USER");
+ }
+
+ if (currentUser.isPermitted("READ")) {
+ permission = permission + " READ";
+ }
+
+ if (currentUser.isPermitted("WRITE")) {
+ permission = permission + " WRITE";
+ }
+ model.addAttribute("username", currentUser.getPrincipal());
+ model.addAttribute("permission", permission);
+ }
+
+}
diff --git a/apache-shiro/src/main/java/com/baeldung/comparison/shiro/models/UserCredentials.java b/apache-shiro/src/main/java/com/baeldung/comparison/shiro/models/UserCredentials.java
new file mode 100644
index 0000000000..100809f6ea
--- /dev/null
+++ b/apache-shiro/src/main/java/com/baeldung/comparison/shiro/models/UserCredentials.java
@@ -0,0 +1,28 @@
+package com.baeldung.comparison.shiro.models;
+
+public class UserCredentials {
+
+ private String username;
+ private String password;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ @Override
+ public String toString() {
+ return "username = " + getUsername();
+ }
+}
diff --git a/apache-shiro/src/main/java/com/baeldung/comparison/springsecurity/Application.java b/apache-shiro/src/main/java/com/baeldung/comparison/springsecurity/Application.java
new file mode 100644
index 0000000000..6878c309df
--- /dev/null
+++ b/apache-shiro/src/main/java/com/baeldung/comparison/springsecurity/Application.java
@@ -0,0 +1,19 @@
+package com.baeldung.comparison.springsecurity;
+
+import org.apache.shiro.spring.boot.autoconfigure.ShiroAnnotationProcessorAutoConfiguration;
+import org.apache.shiro.spring.boot.autoconfigure.ShiroAutoConfiguration;
+import org.apache.shiro.spring.config.web.autoconfigure.ShiroWebAutoConfiguration;
+import org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication(exclude = {ShiroAutoConfiguration.class,
+ ShiroAnnotationProcessorAutoConfiguration.class,
+ ShiroWebAutoConfiguration.class,
+ ShiroWebFilterConfiguration.class})
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
\ No newline at end of file
diff --git a/apache-shiro/src/main/java/com/baeldung/comparison/springsecurity/config/SecurityConfig.java b/apache-shiro/src/main/java/com/baeldung/comparison/springsecurity/config/SecurityConfig.java
new file mode 100644
index 0000000000..d838eef9b3
--- /dev/null
+++ b/apache-shiro/src/main/java/com/baeldung/comparison/springsecurity/config/SecurityConfig.java
@@ -0,0 +1,45 @@
+package com.baeldung.comparison.springsecurity.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.csrf().disable().authorizeRequests(authorize -> authorize.antMatchers("/index", "/login")
+ .permitAll()
+ .antMatchers("/home", "/logout")
+ .authenticated()
+ .antMatchers("/admin/**")
+ .hasRole("ADMIN"))
+ .formLogin(formLogin -> formLogin.loginPage("/login")
+ .failureUrl("/login-error"));
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication()
+ .withUser("Jerry")
+ .password(passwordEncoder().encode("password"))
+ .authorities("READ", "WRITE")
+ .roles("ADMIN")
+ .and()
+ .withUser("Tom")
+ .password(passwordEncoder().encode("password"))
+ .authorities("READ")
+ .roles("USER");
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+}
diff --git a/apache-shiro/src/main/java/com/baeldung/comparison/springsecurity/web/SpringController.java b/apache-shiro/src/main/java/com/baeldung/comparison/springsecurity/web/SpringController.java
new file mode 100644
index 0000000000..34b6b0c0e3
--- /dev/null
+++ b/apache-shiro/src/main/java/com/baeldung/comparison/springsecurity/web/SpringController.java
@@ -0,0 +1,79 @@
+package com.baeldung.comparison.springsecurity.web;
+
+import java.util.Collection;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class SpringController {
+
+ @GetMapping("/")
+ public String getIndex() {
+ return "comparison/index";
+ }
+
+ @GetMapping("/login")
+ public String showLoginPage() {
+ return "comparison/login";
+ }
+
+ @RequestMapping("/login-error")
+ public String loginError(Model model) {
+ model.addAttribute("error", "Invalid Credentials");
+ return "comparison/login";
+ }
+
+ @PostMapping("/login")
+ public String doLogin(HttpServletRequest req) {
+ return "redirect:/home";
+ }
+
+ @GetMapping("/home")
+ public String showHomePage(HttpServletRequest req, Model model) {
+ addUserAttributes(model);
+ return "comparison/home";
+ }
+
+ @GetMapping("/admin")
+ public String adminOnly(HttpServletRequest req, Model model) {
+ addUserAttributes(model);
+ model.addAttribute("adminContent", "only admin can view this");
+ return "comparison/home";
+ }
+
+ private void addUserAttributes(Model model) {
+ Authentication auth = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (auth != null && !auth.getClass()
+ .equals(AnonymousAuthenticationToken.class)) {
+ User user = (User) auth.getPrincipal();
+ model.addAttribute("username", user.getUsername());
+
+ Collection authorities = user.getAuthorities();
+
+ for (GrantedAuthority authority : authorities) {
+ if (authority.getAuthority()
+ .contains("USER")) {
+ model.addAttribute("role", "USER");
+ model.addAttribute("permission", "READ");
+ } else if (authority.getAuthority()
+ .contains("ADMIN")) {
+ model.addAttribute("role", "ADMIN");
+ model.addAttribute("permission", "READ WRITE");
+ }
+ }
+ }
+ }
+
+}
diff --git a/apache-shiro/src/main/java/com/baeldung/Main.java b/apache-shiro/src/main/java/com/baeldung/intro/Main.java
similarity index 99%
rename from apache-shiro/src/main/java/com/baeldung/Main.java
rename to apache-shiro/src/main/java/com/baeldung/intro/Main.java
index 99515bb705..fbd3bc7ac7 100644
--- a/apache-shiro/src/main/java/com/baeldung/Main.java
+++ b/apache-shiro/src/main/java/com/baeldung/intro/Main.java
@@ -1,4 +1,4 @@
-package com.baeldung;
+package com.baeldung.intro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
diff --git a/apache-shiro/src/main/java/com/baeldung/MyCustomRealm.java b/apache-shiro/src/main/java/com/baeldung/intro/MyCustomRealm.java
similarity index 99%
rename from apache-shiro/src/main/java/com/baeldung/MyCustomRealm.java
rename to apache-shiro/src/main/java/com/baeldung/intro/MyCustomRealm.java
index 6d7c01d96e..9d65e37ec4 100644
--- a/apache-shiro/src/main/java/com/baeldung/MyCustomRealm.java
+++ b/apache-shiro/src/main/java/com/baeldung/intro/MyCustomRealm.java
@@ -1,4 +1,4 @@
-package com.baeldung;
+package com.baeldung.intro;
import java.sql.Connection;
import java.sql.SQLException;
diff --git a/apache-shiro/src/main/java/com/baeldung/ShiroSpringApplication.java b/apache-shiro/src/main/java/com/baeldung/intro/ShiroSpringApplication.java
similarity index 80%
rename from apache-shiro/src/main/java/com/baeldung/ShiroSpringApplication.java
rename to apache-shiro/src/main/java/com/baeldung/intro/ShiroSpringApplication.java
index e12d3ebffa..dc288a273b 100644
--- a/apache-shiro/src/main/java/com/baeldung/ShiroSpringApplication.java
+++ b/apache-shiro/src/main/java/com/baeldung/intro/ShiroSpringApplication.java
@@ -1,4 +1,4 @@
-package com.baeldung;
+package com.baeldung.intro;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
@@ -7,12 +7,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
/**
* Created by smatt on 21/08/2017.
*/
-@SpringBootApplication
+@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class ShiroSpringApplication {
private static final transient Logger log = LoggerFactory.getLogger(ShiroSpringApplication.class);
@@ -29,7 +30,7 @@ public class ShiroSpringApplication {
@Bean
- public ShiroFilterChainDefinition shiroFilterChainDefinition() {
+ public ShiroFilterChainDefinition filterChainDefinition() {
DefaultShiroFilterChainDefinition filter
= new DefaultShiroFilterChainDefinition();
diff --git a/apache-shiro/src/main/java/com/baeldung/controllers/ShiroSpringController.java b/apache-shiro/src/main/java/com/baeldung/intro/controllers/ShiroSpringController.java
similarity index 97%
rename from apache-shiro/src/main/java/com/baeldung/controllers/ShiroSpringController.java
rename to apache-shiro/src/main/java/com/baeldung/intro/controllers/ShiroSpringController.java
index 2713786d71..1605f390a8 100644
--- a/apache-shiro/src/main/java/com/baeldung/controllers/ShiroSpringController.java
+++ b/apache-shiro/src/main/java/com/baeldung/intro/controllers/ShiroSpringController.java
@@ -1,6 +1,5 @@
-package com.baeldung.controllers;
+package com.baeldung.intro.controllers;
-import com.baeldung.models.UserCredentials;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
@@ -13,6 +12,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+import com.baeldung.intro.models.UserCredentials;
+
import javax.servlet.http.HttpServletRequest;
@Controller
diff --git a/apache-shiro/src/main/java/com/baeldung/models/UserCredentials.java b/apache-shiro/src/main/java/com/baeldung/intro/models/UserCredentials.java
similarity index 95%
rename from apache-shiro/src/main/java/com/baeldung/models/UserCredentials.java
rename to apache-shiro/src/main/java/com/baeldung/intro/models/UserCredentials.java
index 51b429046a..6614d07257 100644
--- a/apache-shiro/src/main/java/com/baeldung/models/UserCredentials.java
+++ b/apache-shiro/src/main/java/com/baeldung/intro/models/UserCredentials.java
@@ -1,4 +1,4 @@
-package com.baeldung.models;
+package com.baeldung.intro.models;
public class UserCredentials {
diff --git a/apache-shiro/src/main/java/com/baeldung/shiro/permissions/custom/Main.java b/apache-shiro/src/main/java/com/baeldung/permissions/custom/Main.java
similarity index 98%
rename from apache-shiro/src/main/java/com/baeldung/shiro/permissions/custom/Main.java
rename to apache-shiro/src/main/java/com/baeldung/permissions/custom/Main.java
index a902a24388..c9ab2b6e37 100644
--- a/apache-shiro/src/main/java/com/baeldung/shiro/permissions/custom/Main.java
+++ b/apache-shiro/src/main/java/com/baeldung/permissions/custom/Main.java
@@ -1,4 +1,4 @@
-package com.baeldung.shiro.permissions.custom;
+package com.baeldung.permissions.custom;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
diff --git a/apache-shiro/src/main/java/com/baeldung/shiro/permissions/custom/PathPermission.java b/apache-shiro/src/main/java/com/baeldung/permissions/custom/PathPermission.java
similarity index 90%
rename from apache-shiro/src/main/java/com/baeldung/shiro/permissions/custom/PathPermission.java
rename to apache-shiro/src/main/java/com/baeldung/permissions/custom/PathPermission.java
index f7dfbda06a..828484087f 100644
--- a/apache-shiro/src/main/java/com/baeldung/shiro/permissions/custom/PathPermission.java
+++ b/apache-shiro/src/main/java/com/baeldung/permissions/custom/PathPermission.java
@@ -1,4 +1,4 @@
-package com.baeldung.shiro.permissions.custom;
+package com.baeldung.permissions.custom;
import org.apache.shiro.authz.Permission;
diff --git a/apache-shiro/src/main/java/com/baeldung/shiro/permissions/custom/PathPermissionResolver.java b/apache-shiro/src/main/java/com/baeldung/permissions/custom/PathPermissionResolver.java
similarity index 88%
rename from apache-shiro/src/main/java/com/baeldung/shiro/permissions/custom/PathPermissionResolver.java
rename to apache-shiro/src/main/java/com/baeldung/permissions/custom/PathPermissionResolver.java
index 4b60d2fbd4..30bdece65f 100644
--- a/apache-shiro/src/main/java/com/baeldung/shiro/permissions/custom/PathPermissionResolver.java
+++ b/apache-shiro/src/main/java/com/baeldung/permissions/custom/PathPermissionResolver.java
@@ -1,4 +1,4 @@
-package com.baeldung.shiro.permissions.custom;
+package com.baeldung.permissions.custom;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.PermissionResolver;
diff --git a/apache-shiro/src/main/resources/templates/comparison/home.ftl b/apache-shiro/src/main/resources/templates/comparison/home.ftl
new file mode 100644
index 0000000000..37eb3d1812
--- /dev/null
+++ b/apache-shiro/src/main/resources/templates/comparison/home.ftl
@@ -0,0 +1,19 @@
+
+
+ Home Page
+
+
+Welcome ${username}!
+Role: ${role}
+Permissions
+${permission}
+Admin only
+<#if adminContent??>
+ ${adminContent}
+#if>
+
+
+
+
\ No newline at end of file
diff --git a/apache-shiro/src/main/resources/templates/comparison/index.ftl b/apache-shiro/src/main/resources/templates/comparison/index.ftl
new file mode 100644
index 0000000000..8f35c0af1b
--- /dev/null
+++ b/apache-shiro/src/main/resources/templates/comparison/index.ftl
@@ -0,0 +1,10 @@
+
+
+ Index
+
+
+ Welcome Guest!
+
+ Go to the secured page
+
+
\ No newline at end of file
diff --git a/apache-shiro/src/main/resources/templates/comparison/login.ftl b/apache-shiro/src/main/resources/templates/comparison/login.ftl
new file mode 100644
index 0000000000..7340f47204
--- /dev/null
+++ b/apache-shiro/src/main/resources/templates/comparison/login.ftl
@@ -0,0 +1,25 @@
+
+
+ Login
+
+
+Login
+
+
+
+
\ No newline at end of file
diff --git a/apache-shiro/src/test/java/com/baeldung/comparison/shiro/SpringContextTest.java b/apache-shiro/src/test/java/com/baeldung/comparison/shiro/SpringContextTest.java
new file mode 100644
index 0000000000..0d150d0fad
--- /dev/null
+++ b/apache-shiro/src/test/java/com/baeldung/comparison/shiro/SpringContextTest.java
@@ -0,0 +1,20 @@
+package com.baeldung.comparison.shiro;
+
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import com.baeldung.comparison.shiro.ShiroApplication;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(classes = { ShiroApplication.class })
+public class SpringContextTest {
+
+ @Test
+ public void whenSpringContextIsBootstrapped_thenNoExceptions() {
+
+ }
+
+}
\ No newline at end of file
diff --git a/apache-shiro/src/test/java/com/baeldung/comparison/springsecurity/SpringContextTest.java b/apache-shiro/src/test/java/com/baeldung/comparison/springsecurity/SpringContextTest.java
new file mode 100644
index 0000000000..5044b40e31
--- /dev/null
+++ b/apache-shiro/src/test/java/com/baeldung/comparison/springsecurity/SpringContextTest.java
@@ -0,0 +1,19 @@
+package com.baeldung.comparison.springsecurity;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import com.baeldung.comparison.springsecurity.Application;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(classes = { Application.class })
+public class SpringContextTest {
+
+ @Test
+ public void whenSpringContextIsBootstrapped_thenNoExceptions() {
+
+ }
+
+}
\ No newline at end of file