1. Introduction to Shiro

1.1. What is Shiro?

Apache Shiro is a security (permission) framework for Java.

Shiro can easily develop applications that are good enough to be used in both JavaSE and JavaEE environments.

Shiro can do: authentication, authorization, encryption, session management, integration with the Web, caching, and more.

Download address

Official website: shiro.apache.org/ github: github.com/apache/shir…

1.2. What are the functions?

  • Authentication: Authentication/login to verify that the user has the corresponding identity

  • Authorization: Verifies whether an authenticated user has a specific permission. That is, determine whether a user can perform operations, for example, verify whether a user has a role. Or fine-grained verification of whether a user has permissions on a resource

  • Session Management: A Session is managed after a user logs in. All information about the Session is stored in the Session before the user logs out. The session can be in a normal JavaSE environment or a Web environment

  • Cryptography: The security of data, such as encrypted passwords stored in a database rather than in clear text

  • Web Support:Web Support makes it easy to integrate into the Web environment

  • Caching: For example, after a user logs in, the user information and role/permission do not need to be queried every time. This improves efficiency

  • Concurrency:Shiro Concurrency allows for concurrent validation in multi-threaded applications. For example, when you open a thread within another thread, the Concurrency is automatically propagated

  • Esting: testing support

  • “Run As” : Allows one user to pretend to be another user (if they allow it) for access

  • Remember Me, this is a very common feature, that is, once you log in, you don’t need to log in the next time you come back

1.3 Shiro Architecture (External)

Looking at Shiro from the outside, that is, looking at how Shiro gets things done from an application point of view

  • Subject: The object with which the application code interacts directly is Subject, which means that Shiro’s external API is the core of Subject. Subject represents the current “user”, who is not necessarily a specific person. Anything that interacts with the current application is Subject, such as web crawler, robot, etc. All interactions with the Subject are delegated to the SecurityManager; The Subject is really the facade, and the SecurityManager is the actual performer

  • SecurityManager: SecurityManager. That is, all security-related operations interact with the SecurityManager; And it manages all subjects; As you can see, it is the core of Shiro, responsible for interacting with Shiro’s other components, and it corresponds to the role of the DispatcherServlet in SpringMVC

  • Realm: Shiro obtains security data from realms (such as users, roles, and permissions). To authenticate users, the SecurityManager needs to retrieve users from realms for comparison. You also need to get the user’s role/permissions from Realm to verify that the user can operate. You can think of a Realm as a DataSource

1.4 Shiro Architecture (internal)

  • Subject: any “user” who can interact with the application;
  • SecurityManager: equivalent to the DispatcherServlet in SpringMVC; Is Shiro’s heart; All specific interactions are controlled through the SecurityManager; It manages all subjects and is responsible for authentication, authorization, session, and cache management.
  • Authenticator: responsible for Subject authentication, is an extension point, can be customized implementation; You can use an Authentication Strategy, that is, when the user is authenticated.
  • Authorizer: an Authorizer, an access controller, used to determine whether a subject has permission to perform the corresponding operation; It controls what functions users can access in the application;
  • Realm: There can be one or more realms, which can be considered a secure entity data source for obtaining secure entities. It could be a JDBC implementation, it could be an in-memory implementation, and so on; Provided by the user; So you generally need to implement your own Realm in your applications;
  • SessionManager: component that manages the Session lifecycle; Shiro can be used not only in Web environments, but also in normal JavaSE environments
  • CacheManager: Cache controller that manages caches of users, roles, and permissions. Because this data rarely changes, putting it in the cache can improve access performance
  • Cryptography: The Cryptography module, Shiro enhances some common encryption components used such as password encryption/decryption.

2, Hello World

2.1. Quick practice

Look at the official document: shiro.apache.org/tutorial.ht…

Official Quickstart: github.com/apache/shir… 1. Create a Maven parent project to learn from Shiro and cut out unnecessary parts

2. Create a normal Maven subproject: Hell-Shiro

3. According to the official documentation, we import Shiro’s dependencies

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.5.3</version>
    </dependency>

    <! -- configure logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.26</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.26</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

Copy the code

4. Related configuration files

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
Copy the code
[users] # user 'root' with password 'secret' and the 'admin' role root = secret, admin # user 'guest' with the password 'guest' and the 'guest' role guest = guest, guest # user 'presidentskroob' with password '12345' ("That's the same combination on # my luggage!!!" ;) ), and role 'president' presidentskroob = 12345, president # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz' darkhelmet = ludicrousspeed, darklord, schwartz # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz' lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # # Each line conforms to the format defined in the # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc # ----------------------------------------------------------------------------- [roles] # 'admin' role has all permissions, indicated by the wildcard '*' admin = * # The 'schwartz' role can do anything (*) with any lightsaber: schwartz = lightsaber:* # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with # license plate 'eagle5' (instance specific id) goodguy = winnebago:drive:eagle5Copy the code
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        // Read config file:
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton. Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps. That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        // Get the current user object Subject
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        // The current user gets Shiro's Session to be removed from the web store
        Session session = currentUser.getSession();
        session.setAttribute("someKey"."aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        // Check whether the current user is authenticated
        if(! currentUser.isAuthenticated()) {/ / Token Token
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr"."vespa");
            // Set remember me
            token.setRememberMe(true);
            try {
                // Perform the login operation
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked. " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition? error?}}//say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        // Check the role
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        / / coarse granularity
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring. Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        / / fine-grained
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        / / logout
        currentUser.logout();

        / / end
        System.exit(0); }}Copy the code

2.2 Key steps in the case

1. Get SecurityManager

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
Copy the code

2. Obtain the current user Subject through SecurityUtils

SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();
Copy the code

Get Session from the current user

Session session = currentUser.getSession(); 4. Use Session to store values

session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
Copy the code

5. Check whether the user is authenticated

currentUser.isAuthenticated()
Copy the code

6. Perform the login operation

currentUser.login(token);
Copy the code

7. Print its identity body

currentUser.getPrincipal()
Copy the code

8, logout

currentUser.logout();
Copy the code

3. Spring integrates Shiro

3.1 Steps for integrating Shiro

Import SpringBoot and Shiro integration package dependencies

<! --SpringBoot and Shiro integration package --> <! -- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter --> <dependency> < groupId > org, apache shiro < / groupId > < artifactId > shiro - spring - the boot - web - the starter < / artifactId > < version > 1.6.0 < / version > </dependency>Copy the code

Create a custom realm that inherits AuthorizingRealm

// Custom Realm
public class UserRealm extends AuthorizingRealm {
    / / authorization

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // Print a prompt
        System.out.println("Authorization method performed");
        return null;
    }

    / / certification

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // Print a prompt
        System.out.println("Authentication method implemented");
        return null; }}Copy the code

3. Create a ShiroConfig configuration file

@Configuration
public class ShiroConfig {

    //ShiroFilterBean 3
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // Set Shiro's built-in filter
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/add"."perms[user:add]");  // Authc requires authentication
        filterMap.put("/user/update"."perms[user:update]");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        return shiroFilterFactoryBean;
    }

    //DefaultWebSecurityManager 2
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();

        / / associated UserRealm
        securityManager.setRealm(userRealm);

        return securityManager;
    }

    @Bean   / / 1
    public UserRealm userRealm(a) {
        UserRealm realm = new UserRealm();
        return realm;
    }

    ShiroDialect: used to integrate Shiro Thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(a) {
        return newShiroDialect(); }}Copy the code

3.2, tests,

1. Login interception

Add a filter to Shiro’s ShiroFilterBean configuration file

// Add Shiro's built-in filter =======================
        /* Anon: authc: must be authenticated to access user: must have the "remember me" function to access perms: must have the permission to access a resource role: must have the permission to access a role */Set the request to /user/addUser so that map.put("/user/addUser"."authc");
          map.put("/user/deleteUser"."authc");

Copy the code

2. User authentication

// The login method
    @RequestMapping("/login")
    public String login(String username, String password, Model model) {
        // Get the current user
        Subject subject = SecurityUtils.getSubject();
        // There is no authentication
        // Encapsulate the user's login data to get the token
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        // Login and exception handling
        try {
            // User login
            subject.login(token);
            return "index";
        } catch (UnknownAccountException uae) {
            // If the user name does not exist
            System.out.println("User name does not exist");
            model.addAttribute("exception"."User name does not exist");
            return "login";
        } catch (IncorrectCredentialsException ice) {
            // If the password is incorrect
            System.out.println("Password error");
            model.addAttribute("exception"."Password error");
            return "login"; }}Copy the code

3. Log out

// Log out
@RequestMapping("/logout")
public String logout(a){
    Subject subject = SecurityUtils.getSubject();
    subject.logout();
    return "login";
}
Copy the code