Simple authentication example with spring boot 2 + shiro

Posted by Dread on Mon, 09 Dec 2019 03:01:33 +0100

Shiro is a powerful and easy-to-use Java security framework on the official website: https://shiro.apache.org/.

The main functions are authentication, authorization, encryption, and session management.
Other features include Web support, caching, test support, allowing one user to access as another user, and remembering me.

Shiro has three core components: Subject, SecurityManager, and Realm.

Subject: That is, the current operation "user", "user" does not only refer to people, but also third-party processes, background accounts, or other similar things.
SecurityManager: Security Manager, the core of Shiro framework, manages all Subject s through SecurityManager and provides various services for security management.
Realm: Domain, acting as a "bridge" or "connector" between Shiro and application security data.That is, when authenticating (login) and authorizing (access control) users, Shiro will look up the user and its permission information from the Relm in the application configuration.When configuring Shiro, you must specify at least one Realm for authentication and/or authorization.

Shiro is integrated in Spring Boot and there are two different approaches depending on the introduced dependent packages shiro-spring and shiro-spring-boot-web-starter (both version 1.4.2).

Method 1: Introduce dependent package shiro-spring

1. Create a new SpringBoot project in IDEA with the following dependent packages referenced by pom.xml:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.2</version>
        </dependency>
        

2. Create Realm and configure shiro

(1) Create Realm

package com.example.demo.config;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class MyRealm extends AuthorizingRealm {

    /**Permission information, not implemented yet*/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**Authentication: Verify that the user has entered the correct account and password.*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //Get account entered by user
        String userName = (String) token.getPrincipal();
        //Authenticate User admin And password 123456 are correct
        if (!"admin".equals(userName)) {
            throw new UnknownAccountException("Account does not exist!");
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, "123456", getName());
        return authenticationInfo;
        //In the actual project, the above account retrieves the user object from the database to determine if it exists
        /*User user = userService.findByUserName(userName);
        if (user == null) {
            throw new UnknownAccountException("Account does not exist! ";
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(), getName());
        return authenticationInfo;
        */
    }
}

(2) Configure Shiro

package com.example.demo.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    @Bean
    MyRealm myRealm() {
        return new MyRealm();
    }

    @Bean
    DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        //If you do not set the default, it will look for it automatically Web Project Root"/login.jsp"page
        bean.setLoginUrl("/login");
        //Links to jump after successful login
        bean.setSuccessUrl("/index");
        //Unauthorized interface
        bean.setUnauthorizedUrl("/403");
        //Configure links that will not be blocked
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/doLogin", "anon");
        map.put("/**", "authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }
}

3. Controller Test Method

package com.example.demo.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {

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

    @PostMapping("/doLogin")
    public String doLogin(String userName, String password) {
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(userName, password));
            return "Login Successful!";
        } catch (UnknownAccountException e) {
            return e.getMessage();
        } catch (AuthenticationException e) {
            return "Logon failure, bad password!";
        }
    }

    //If you don't log in first, the visit skips to/login
    @GetMapping("/index")
    public String index() {
        return "index";
    }

    @GetMapping("/403")
    public String unauthorizedRole(){
        return "No privileges";
    }
}

Method 2: Introduce dependent package shiro-spring-boot-web-starter

1. Remove shiro-spring from pom.xml and introduce shiro-spring-boot-web-starter

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

2. Create Realm and configure shiro

(1) Create Realm, the same code and method.
(2) Configure Shiro

package com.example.demo.config;

import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {
    @Bean
    MyRealm myRealm() {
        return new MyRealm();
    }

    @Bean
    DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        definition.addPathDefinition("/doLogin", "anon");
        definition.addPathDefinition("/**", "authc");
        return definition;
    }
}

(3) application.yml configuration

shiro:
  unauthorizedUrl: /403
  successUrl: /index
  loginUrl: /login

Topics: Java Shiro Apache Spring