♥ ha ha ha I’m coming

Today we will talk about using Shiro to implement user login permissions

Introduction: Shiro

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, password, and session management. Using Shiro’s easy-to-understand apis, you can quickly and easily obtain any application, from the smallest mobile applications to the largest web and enterprise applications.

There are three core components: Subject, SecurityManager, and Realms. Subject: current user. However, in Shiro, the concept of Subject does not just refer to people. It can also be a third-party process, a Daemon Account, or something similar. It simply means “what is currently interacting with the software.” Subject represents the security actions of the current user, and SecurityManager manages the security actions of all users. SecurityManager: It is the core of Shiro’s framework, a typical Facade pattern through which Shiro manages internal component instances and provides various services for security management. Realm: Realm acts as a “bridge” or “connector” between Shiro and application security data. That is, when authenticating a user (login) and authenticating a user (access control), Shiro looks up the user and their permission information from an application-configured Realm. In this sense, a Realm is essentially a security-related DAO: It encapsulates the connection details of the data source and provides related data to Shiro when needed. When configuring Shiro, you must specify at least one Realm for authentication and/or authorization. It is possible to configure multiple Realms, but at least one is required. Shiro has built-in Realms that can connect to a large number of secure data sources (aka directories), such as LDAP, relational databases (JDBC), ini-like text configuration resources, and properties files. If the default Realm does not meet your requirements, you can also insert your own Realm implementation that represents a custom data source.

Get straight to the point:

Step 1: Import our Pom file again

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional> </dependency> <! -- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4. 0</version>
        </dependency>
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.22.</version>
            <exclusions>
                <exclusion>
                    <groupId>com.puppycrawl.tools</groupId>
                    <artifactId>checkstyle</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0. 0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>
Copy the code

Do you feel familiar, these are our common Pom files oh.

Step 2: The application.yml file is not much. Let’s go to the code:

Start the port

server:
    port: 8888
spring:
  application:
    name: demo-shiro
Copy the code

This is too easy, using SpringBoot development is so good oh.

Step 4: Watch out, appetizers are coming…

Create User object :(User)

package com.demo.shiro.entity;

import lombok.Data;

import java.util.Set;

@Data
public class User {

    /** * user Id */
    private Long userId;

    /** * User name */
    private String userName;
    
    /** * User password */
    private String userPassword;

    /** * User role */
    private Set<String> role;

    /** * User permission */
    private Set<String> permission;

    /** * constructor *@param userId
     * @param userName
     * @param userPassword
     * @param role
     * @param permission
     */
    public User(Long userId, String userName, String userPassword, Set<String> role, Set<String> permission) {
        this.userId = userId;
        this.userName = userName;
        this.userPassword = userPassword;
        this.role = role;
        this.permission = permission; }}Copy the code

Create data Response :(Response)

package com.demo.shiro.entity;

import java.util.HashMap;
import java.util.Map;

/** * Response data */
public class Response extends HashMap<String.Object> {
/** * info *@param message
     * @return* /
    public Response message(String message){

        this.put("message", message);

        return this;
    }

    /** * data *@param data
     * @return* /
    public Response data(Object data){

        this.put("data", data);

        return this;
    }

    /** * new method *@param key
     * @param value
     * @return* /
    @Override
    public Object put(String key, Object value) {

        super.put(key, value);

        return this; }}Copy the code

Create our custom SystemException

package com.demo.shiro.exception;

/** * The exception throws */
public class SystemException extends Exception{

    public SystemException(String messgae){

        super(messgae); }}Copy the code

Create a system utility class to simulate data information (SystemUtil) :

package com.demo.shiro;

import com.demo.shiro.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import javax.lang.model.type.ArrayType;
import java.util.*;

/** * simulate database data */
public class SystemUtils {
 /** * Simulate two data ** in the database@return* /
    public static List<User> users(a) {
    
    List<User> users = new ArrayList<>();

        // Add data
        users.add(new User(
                1L."admin"."123456".new HashSet<>(Collections.singleton("admin")),
                new HashSet<>(Arrays.asList("user:add"."user:delete"))));

        users.add(new User(
                2L."register"."123456".new HashSet<>(Collections.singleton("register")),
                new HashSet<>(Arrays.asList("user:view"))));

        return users;
    }

    /** * Get user *@param username
     * @return* /
    public static User getUser(String username){

        List<User> users = SystemUtils.users();

        return users.stream().filter(user ->

            StringUtils.equalsAnyIgnoreCase(username,user.getUserName())).findFirst().orElse(null); }}Copy the code

OK, at this point, we have created all the basic classes.

Step 5: Create our Shiro-related configuration information

Shiro configuration class :(ShiroConfig)

package com.demo.shiro.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.*;

@Configuration
public class ShiroConfig {

 @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        / / set the securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager(ShiroRealm shiroRealm) {

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // Configure the SecurityManager and inject shiroRealm
        securityManager.setRealm(shiroRealm);

        return securityManager;
    }
    
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(a) {
        return new LifecycleBeanPostProcessor();
    }

    /** * Resolve the issue of annotations not working *@return* /
    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(a){
        return newDefaultAdvisorAutoProxyCreator(); }}Copy the code

Shiro Authentication and Authorization Classes (ShiroRelam)

package com.demo.shiro.config;

import com.demo.shiro.SystemUtils;
import com.demo.shiro.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;

@Component
public class ShiroRealm extends AuthorizingRealm {

 /** * Login module *@param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        // Get the user name
        String username = (String) authenticationToken.getPrincipal();

        // Get the password
        String password = new String((char[]) authenticationToken.getCredentials());

        // Get the user based on the user name
        User user = SystemUtils.getUser(username);

        if (user == null| |! StringUtils.equals(password, user.getUserPassword())){throw new IncorrectCredentialsException("Wrong username or password");
        }

        // Successful login
        return new SimpleAuthenticationInfo(user, user.getUserPassword(), getName());
    }

    /** * Authorization module *@param principalCollection
     * @return* /
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        User user = (User) SecurityUtils.getSubject().getPrincipal();

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // Set the role to simulate the database
        simpleAuthorizationInfo.setRoles(user.getRole());

        // Set permissions
        simpleAuthorizationInfo.setStringPermissions(user.getPermission());

        returnsimpleAuthorizationInfo; }}Copy the code

Does it feel easy? Yes, just configure these two classes to complete Shiro…

Step 6: Now we can start our test… Are you ready?

Create a landing page :(login.html)

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>In the test</title>
</head>
<body>

<form action="/login" method="post">

    <input type="text" name="username">
    <br>

    <input type="password" name="password">

    <br>

    <input type="submit" value="Login">

</form>
</body>
</html>
Copy the code

Create a ViewController to access our page:

package com.demo.shiro.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/** * page control **/

@Controller
public class ViewController {

@RequestMapping("login.html")
    public String showLogin(a){

        return "login"; }}Copy the code

This page does not need to show, this is too simple…

Create our LoginController (LoginController)

package com.demo.shiro.controller;

import com.demo.shiro.entity.Response;
import com.demo.shiro.exception.SystemException;
import com.demo.shiro.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

/** * log in to Controller */
@Controller
public class LoginController {

    @Autowired(required = false)
    private LoginService loginService;

    /** * login *@param username
     * @param password
     * @return* /
    @PostMapping("login")
    public ResponseEntity<Response> login(@RequestParam("username") String username,
                                          @RequestParam("password") String password) throws SystemException {

        Response response = this.loginService.login(username, password);

        returnResponseEntity.ok(response); }}Copy the code

There may be an error, don’t panic, because our LoginService was not created…

Create our LoginService (LoginService) :

package com.demo.shiro.service;

import com.demo.shiro.entity.Response;
import com.demo.shiro.exception.SystemException;

public interface LoginService {

    / / login
    Response login(String username, String password) throws SystemException;
}
Copy the code

Create its implementation class (LoginServiceImpl)

package com.demo.shiro.service.impl;

import com.demo.shiro.SystemUtils;
import com.demo.shiro.entity.Response;
import com.demo.shiro.entity.User;
import com.demo.shiro.exception.SystemException;
import com.demo.shiro.service.LoginService;
import org.apache.commons.lang3.StringUtils;
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.springframework.stereotype.Service;

/** * log in to Service */
@Service
public class LoginServiceImpl implements LoginService {

 /** * login operation *@param username
     * @param password
     * @return* /
    @Override
    public Response login(String username, String password) throws SystemException {

        Subject subject = SecurityUtils.getSubject();
        
        // Check whether it exists
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)){

            throw new SystemException("Please enter information");
        }

        try {
            // Perform login
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);

            subject.login(usernamePasswordToken);

            User user = (User) subject.getPrincipal();

            return new Response().message("Certification successful").data(user);

        } catch (AuthenticationException e) {

            e.printStackTrace();

            throw new SystemException("Authentication failed"); }}}Copy the code

The code in here is also easy to understand, the code quality is poor… Hahaha, need to optimize their own oh…

At this point, our log-in logic ends… Does it feel easy…

It’s time for our test. Are you ready?

Step 7: Test environment:

Create TestController (TestController)

package com.demo.shiro.controller;

import com.demo.shiro.entity.Response;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.security.RolesAllowed;

/** * Test Controller */
@Controller
@RequestMapping("test")
public class TestController {

/** * You need the admin role to access *@return* /
    @GetMapping("admin")
    @RequiresRoles("admin")
    public ResponseEntity<Response> test1(a){

        return ResponseEntity.ok(new Response().message("You are the admin"));
    }
    
    /** * Have permission to view *@return* /
    @GetMapping("view")
    @RequiresPermissions("admin:view")
    public ResponseEntity<Response> test2(a){

        return ResponseEntity.ok(new Response().message("You have access.")); }}Copy the code

There are two annotations that I have not met before, so you will learn this article oh…

Here is only the implementation of login, no filters, interceptors set… We need to log in to access the page… Have you ever thought about it… Ha, ha, ha

Directory screenshot:

Test screenshot:

Login interface:

Test login with admin: After successful login, we will see the following message

I believe you understand, yes is our own set up simulation data oh. Next we’ll look at other features… Access the test/admin interface:Test /view = test/viewDo you see this page is very panic, do not panic, we want to steady, a careful look, the original we do not have this permission, so… Understand!

OK, here we are, we have a demo, we have a registered user, we can test ourselves… GitHub address: shiro-demo good, write so long finally finished ,,,, very tired oh… If you have any questions please feel free to comment in the comments at ,,,, thank you for browsing… mua