Apache Shiro + Spring Boot Integration!

Introduction

A brief overview of Apache Shiro and Spring Boot:

Security is a critical aspect of application development, and Apache Shiro is a powerful Java security framework that can help developers build secure applications quickly and easily. Spring Boot, on the other hand, is a popular Java framework that simplifies the development of standalone and web-based applications.

Importance of security in applications:

In combination, Apache Shiro and Spring Boot can provide a comprehensive and secure development environment for applications. Apache Shiro provides features such as authentication, authorization, cryptography, and session management, while Spring Boot simplifies the setup and configuration of the application.

Why use Apache Shiro with Spring Boot:

In this article, we will explore how to use Apache Shiro with Spring Boot to build secure and scalable applications. We will cover the basics of Apache Shiro and Spring Boot, the importance of security in applications, and why these two technologies are a good fit for building secure applications.

Setting up a project with Spring Boot and Apache Shiro

To set up a project with Spring Boot and Apache Shiro, follow these steps:

  1. Creating a new Spring Boot project
  • Open your preferred IDE and create a new Spring Boot project.

  • Choose a project name, package name, and any other relevant settings.

  • Select the dependencies you want to include in the project, including Apache Shiro.

  1. Adding Apache Shiro dependency
  • To include Apache Shiro in your Spring Boot project, add the following dependency to your pom.xml file:
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.7.1</version>
</dependency>
  • This will include the Apache Shiro Spring integration libraries.
  1. Configuring Shiro security manager
  • In order to use Apache Shiro with Spring Boot, you need to configure the Shiro security manager. The security manager is responsible for managing authentication, authorization, and other security-related tasks.

  • To configure the Shiro security manager, create a new class that extends the org.apache.shiro.web.mgt.DefaultWebSecurityManager class. This class will handle authentication and authorization for your application.

  • Here is an example configuration class:

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        factoryBean.setLoginUrl("/login");
        factoryBean.setSuccessUrl("/");
        factoryBean.setUnauthorizedUrl("/unauthorized");

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return factoryBean;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    @Bean
    public Realm realm() {
        return new MyRealm();
    }

    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(1800000L);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionValidationInterval(1800000L);
        sessionManager.setSessionDAO(sessionDAO());
        return sessionManager;
    }

    @Bean
    public SessionDAO sessionDAO() {
        return new MemorySessionDAO();
    }
}
  • In this example, we create a new ShiroFilterFactoryBean bean that sets up the Shiro filter chain. We also create a SecurityManager bean that sets up the authentication and authorization rules for the application.

  • The Realm bean defines the data source and authentication rules for the application, and the SessionManager bean sets up the session management configuration.

With these steps completed, your Spring Boot application is now configured to use Apache Shiro for security. You can now begin implementing authentication and authorization features using Apache Shiro in your Spring Boot application.

Implementing authentication with Apache Shiro and Spring Boot:

We'll cover how to create a login page, configure Apache Shiro, implement authentication with Apache Shiro, and handle authentication errors.

  1. Creating a login page with Spring MVC

Before we can implement authentication with Apache Shiro, we need to create a login page. We'll use Spring MVC to create a login controller that will handle login requests and display the login view to the user.

First, let's create a new controller for handling login requests:

@Controller
public class LoginController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }

    @PostMapping("/login")
    public String doLogin(@RequestParam String username, @RequestParam String password) {
        // TODO: Implement authentication
        return "redirect:/dashboard";
    }

}

This controller has two methods: login() and doLogin(). The login() method returns the login view to the user, while the doLogin() method handles the form submission and authenticates the user.

Next, let's create a login form with fields for username and password. We'll use Thymeleaf to create the view:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form method="post" th:action="@{/login}">
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

This form has two fields for username and password, as well as a submit button that will submit the form to the doLogin() method of the LoginController.

  1. Configuring Apache Shiro

Now that we have a login page, we can configure Apache Shiro to handle authentication. We'll start by adding the Apache Shiro dependency to our project:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.8.0-RC2</version>
</dependency>

Next, we'll configure the Shiro security manager to use the appropriate authentication and authorization strategies. We can do this by adding a ShiroConfig class to our project:

@Configuration
public class ShiroConfig {

    @Bean
    public SecurityManager securityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    @Bean
    public Realm realm() {
        return new ShiroRealm();
    }

}

This configuration class defines a security manager that uses the ShiroRealm to authenticate users. We'll define the ShiroRealm in the next section.

Implementing authorization with Apache Shiro and Spring Boot

Once we have implemented authentication with Apache Shiro and Spring Boot, we can start defining roles and permissions to control access to different parts of the application.

Apache Shiro provides a flexible and intuitive permission model, allowing us to define permissions as simple strings or complex expressions. Permissions can be assigned to roles, and roles can be assigned to users.

To define roles and permissions, we can use a Shiro INI configuration file or a programmatic configuration using Java code. Here, we will use the INI file approach.

First, let's define some roles and permissions in the shiro.ini file:

bashCopy code[roles]
admin = *
user = article:read, article:comment

[urls]
/login = anon
/logout = logout
/admin/** = authc, roles[admin]
/articles/** = authc, roles[user]

Here, we have defined two roles, admin and user, with different sets of permissions. The admin role has permission to access all resources, denoted by the * wildcard. The user role has permission to read articles and add comments, but not to create or delete articles.

Next, we have defined URL-based access control using the [urls] section. The anon filter allows unauthenticated access to the /login page, while the logout filter logs out the user and redirects to the login page.

The admin role is required to access any resource under the /admin path, while the user role is required to access any resource under the /articles path. The authc filter requires authentication to access these resources.

We can configure these filters in our Spring Boot application by creating a ShiroFilterFactoryBean bean and setting its filterChainDefinitions property to the contents of the shiro.ini file:

@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
    shiroFilter.setSecurityManager(securityManager);
    shiroFilter.setLoginUrl("/login");
    shiroFilter.setSuccessUrl("/dashboard");
    shiroFilter.setUnauthorizedUrl("/error");

    // Load filter chain definitions from shiro.ini
    String filterChainDefinitions = FileUtils.readFileToString(new File("classpath:shiro.ini"), "UTF-8");
    shiroFilter.setFilterChainDefinitions(filterChainDefinitions);

    return shiroFilter;
}

Here, we have also set the login and error URLs, and loaded the filter chain definitions from the shiro.ini file using the FileUtils class from Apache Commons IO.

Now, any attempt to access a resource under the /admin or /articles the path will require authentication and the appropriate role. If the user does not have the required role, they will be redirected to the error page specified by the setUnauthorizedUrl() method.

@Component
public class MyShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        User user = userService.findByUsername(username);

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRoles(user.getRoles());
        authorizationInfo.setStringPermissions(user.getPermissions());
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        User user = userService.findByUsername(username);

        if (user == null) {
            throw new UnknownAccountException("No account found for user [" + username + "]");
        }

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
        return authenticationInfo;
    }
}

In this example, MyShiroRealm extends AuthorizingRealm and provides implementations for doGetAuthorizationInfo and doGetAuthenticationInfo.

doGetAuthorizationInfo retrieves the roles and permissions associated with the user and returns an instance of AuthorizationInfo, which is used by Shiro to enforce access controls.

doGetAuthenticationInfo retrieves the user's credentials from the UserService and returns an instance of AuthenticationInfo, which is used by Shiro to verify the user's identity.

Note that MyShiroRealm is annotated with @Component to allow it to be automatically detected and registered with Spring's application context.

Implementing session management with Apache Shiro and Spring Boot

Session management is an essential aspect of web application security that involves managing and securing user sessions. Apache Shiro provides a robust session management mechanism that can be easily integrated into a Spring Boot application. In this section, we'll discuss how to implement session management with Apache Shiro and Spring Boot.

  1. Configuring session management in Shiro

To configure session management in Shiro, we need to define a session manager bean in our Spring Boot application. We can do this by adding the following code to our Shiro configuration class:

@Bean
public DefaultWebSessionManager sessionManager() {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setGlobalSessionTimeout(1800000); // 30 minutes
    sessionManager.setSessionValidationSchedulerEnabled(true);
    sessionManager.setDeleteInvalidSessions(true);
    return sessionManager;
}

In the above code, we're creating a new instance of DefaultWebSessionManager and configuring it with a session timeout of 30 minutes, session validation scheduler, and the option to delete invalid sessions.

  1. Enabling session management in Shiro filters

Once we've defined the session manager, we need to enable session management in our Shiro filters. We can do this by updating the Shiro filter bean definition as follows:

@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
    filterFactoryBean.setSecurityManager(securityManager);

    Map<String, Filter> filters = new LinkedHashMap<>();
    filters.put("authc", new FormAuthenticationFilter());
    filters.put("logout", new LogoutFilter());
    filters.put("session", new ServletContainerSessionFilter());

    filterFactoryBean.setFilters(filters);

    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    filterChainDefinitionMap.put("/login", "authc");
    filterChainDefinitionMap.put("/logout", "logout");
    filterChainDefinitionMap.put("/**", "session");

    filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

    return filterFactoryBean;
}

In the above code, we're creating a new instance of ShiroFilterFactoryBean and configuring it with a set of filters. We're adding the ServletContainerSessionFilter filter to enable session management.

  1. Controlling session creation and destruction

Finally, we can control session creation and destruction by using the SessionDAO interface provided by Shiro. We can implement this interface to store and retrieve session data from a data source of our choice. To use a custom SessionDAO implementation, we can update our session manager bean definition as follows:

@Bean
public DefaultWebSessionManager sessionManager(SessionDAO sessionDAO) {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setGlobalSessionTimeout(1800000); // 30 minutes
    sessionManager.setSessionValidationSchedulerEnabled(true);
    sessionManager.setDeleteInvalidSessions(true);
    sessionManager.setSessionDAO(sessionDAO);
    return sessionManager;
}

In the above code, we're injecting a SessionDAO instance into our sessionManager bean definition to enable session storage and retrieval.

I hope this helps, you!!

More such articles:

https://medium.com/techwasti

https://www.youtube.com/channel/UCiTaHm1AYqMS4F4L9zyO7qA

https://www.techwasti.com/

\==========================**=========================

If this article adds any value to you then please clap and comment.

Let’s connect on Stackoverflow, LinkedIn, & Twitter.

Did you find this article valuable?

Support techwasti by becoming a sponsor. Any amount is appreciated!

Β