I heard that if you search the public account “Java Fish boy” on wechat, you will improve your skills to the next level

(1) Overview

Shiro is a security framework of Apache, Shiro can easily develop a good enough security application, Shiro can complete authentication, authorization, encryption, session management, caching and other functions. Looking at Shiro from an application point of view, we can see that Shiro runs mainly as follows:

The object with which the application code interacts directly is Subject, which means that Shiro’s external API core is Subject. The meaning of each API:

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. An abstract concept; All subjects are bound to the SecurityManager, and all interactions with the Subject are delegated to the SecurityManager; Subject can be thought of as a facade; The SecurityManager is the actual enforcer;

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 and is responsible for interacting with other components described later. If you have studied SpringMVC, you can think of it as the DispatcherServlet front-end controller;

Realm: Shiro obtains security data from realms (users, roles, and permissions). To authenticate users, The SecurityManager needs to obtain the corresponding 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.

(2) SpringBoot integration Shiro

First, introduce Shiro dependencies

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
Copy the code

With these concepts in mind, we can start writing code that uses Shiro in just three steps

1. Create a Realm object

2. Create a security manager and bind realm objects

3. Create Shiro filter factory and bind security manager

The first step is to customize a Realm object

public class UserRealm extends AuthorizingRealm {
    / / authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    / / certification
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null; }}Copy the code

Define a UserRealm that inherits AuthorizingRealm and implements interface methods that represent authorization and authentication, much like SpringSecurity.

Next, create a ShiroConfig class to implement Shiro’s configuration

@Configuration
public class ShiroConfig {
    // Create a Realm object
    @Bean(name = "userRealm")
    public UserRealm userRealm(a){
        return new UserRealm();
    }
    // Create security manager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        // Bind realm objects
        securityManager.setRealm(userRealm());
        return securityManager;
    }
    // Create Shiro filter factory
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        // Set the security manager
        bean.setSecurityManager(defaultWebSecurityManager);
        returnbean; }}Copy the code

(iii) Shiro realizes landing interception

To implement login blocking in Shiro, you only need to configure the filter in Shiro’s filter factory. For more detailed display, we need to create four NEW HTML pages: index, Level1, Level2, and login

Additionally, thymeleaf and spring-boot-starter-Web dependencies need to be introduced

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
Copy the code

Index.html:

<! DOCTYPEhtml>
<html xmlns:th="http://www.thymeleaf.org"><! - introduce thymeleaf -- -- >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Home page</h1>
<h2 th:text="${msg}"></h2>
<a th:href="@{/level1}">level1</a>
<a th:href="@{/level2}">level2</a>
</body>
</html>
Copy the code

login.html

<! DOCTYPEhtml>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Landing page</title>
</head>
<body>
<div>
    <p th:text="${errormsg}"></p>
    <form action="/checklogin" method="post">
        <h2>Landing page</h2>
        <input type="text" id="username"  name="username" placeholder="username">
        <input type="password" id="password" name="password"  placeholder="password">
        <button type="submit">landing</button>
    </form>
</div>
</body>
</html>
Copy the code

Level1, level2 stores

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
level1
</body>
</html>
Copy the code

Then write the indexController

@Controller
public class indexController {
    @RequestMapping({"/","/index"})
    public String index(Model model){
        model.addAttribute("msg"."hello,shiro");
        return "index";
    }
    @RequestMapping("/level1")
    public String level1(Model model){
        return "level1";
    }
    @RequestMapping("/level2")
    public String level2(Model model){
        return "level2";
    }
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String tologin(a){
        return "login"; }}Copy the code

With this preliminary work done, we can configure the filter interceptor by adding the required filters to the ShiroFilterFactoryBean method in the ShiroConfig class. Shiro has five built-in filters

/** * anon: access without authentication * authc: access without authentication * user: access with remember me function * perms: access with permission on a resource * role: access with permission on a role */
Copy the code

In use, just assign different filters to the corresponding pages

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
    ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
    // Set the security manager
    bean.setSecurityManager(defaultWebSecurityManager);
    Add shiro's built-in filters
    Map<String,String> filterMap=new LinkedHashMap<>();
    filterMap.put("/index"."anon");
    filterMap.put("/level1"."authc");
    filterMap.put("/level2"."authc");
    bean.setFilterChainDefinitionMap(filterMap);
    // Set the jump to the login page
    bean.setLoginUrl("/login");
    return bean;
}
Copy the code

In this code, we set the index home page to be accessible without permission, level1 and level2 to be authenticated, and the login page to jump to the login page. Click level1 and Level2 to check whether there is any authentication. If there is no authentication, the login page is automatically redirected.

(4) Shiro realizes user authentication

Shiro’s user authentication is done in realm. First, we need to change the controller login logic. We need to encapsulate the username and password passed by the user into Shiro and write a new method to determine whether the user is logged in

@RequestMapping(value = "/checklogin",method = RequestMethod.POST)
public String login(@RequestParam("username") String username,@RequestParam("password") String password,Model model){
    // Get the current user
    Subject subject = SecurityUtils.getSubject();
    // Store error information
    String msg="";
    // If not authenticated
    if(! subject.isAuthenticated()){// Encapsulate the username and password in Shiro
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            // Execute the login method
            subject.login(token);
        }catch (UnknownAccountException e){// The user name does not exist
            msg=e.getMessage();
        }catch (IncorrectCredentialsException e){// The password is incorrect
            msg=e.getMessage();
        }catch (Exception e){
            msg="User login exception";
            e.printStackTrace();
        }
        // If MSG is empty, there is no exception
        if (msg.isEmpty()){
            return "redirect:/index";
        }else {
            model.addAttribute("errormsg",msg);
            return "login"; }}return "login";
}
Copy the code

In this login controller, we first judge whether the user exists through the subject, and if not, encapsulate the user data for login. The login action needs to be executed in a Realm. Back to UserRealm’s authentication method:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // The token is obtained
    UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
    // Get the user name and password from the token
    String username = token.getUsername();
    String password = String.valueOf(token.getPassword());
    // For convenience, the user is not fetched from the database
    if (!"root".equals(username)) {
        throw new UnknownAccountException("User does not exist");
    }else if (!"123456".equals(password)){
        throw new IncorrectCredentialsException("Password error");
    }
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"123456",getName());
    return info;
}
Copy the code

In the previous code, we stored the username and password in UsernamePasswordToken, which can be obtained from UserRealm using authenticationToken. After obtaining the username and password, we can compare the username and password with the database. And raises different exceptions, which are picked up by LLDB etMessage().

The important thing to notice here is that the return value SimpleAuthenticationInfo. This class is the implementation class of AuthenticationInfo. The constructor of SimpleAuthenticationInfo takes three arguments:

The first argument is principal, which is usually passed in the user name or user entity class, and then gets the current logged-in user from somewhere else using this code

SecurityUtils.getSubject().getPrincipal();
Copy the code

The second parameter is the password. Note that this refers to the password in the database, since we have already done a layer of password judgment in the previous code, the password verification here is not very effective.

The third argument is the name of the Realm, which can be obtained directly using the getName() method.

(4.1) Effect display

After entering the home page, click Level1 or Level2 to automatically jump to the landing page

On the login page, if the user name is entered incorrectly, the user does not exist. If the password is incorrect, the password is displayed

When the user name and password are entered correctly, click Level1 or Level2 to enter the home page.

(V) Shiro realizes user authorization

In the front we have achieved user authentication, next we will do user authorization, user authorization can be seen in many scenarios, such as some pages of a website only VIP can see.

We to simulate this scenario, the first change ShiroConfiggetShiroFilterFactoryBean method, increase the permissions

 @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        // Set the security manager
        bean.setSecurityManager(defaultWebSecurityManager);
        Add shiro's built-in filters
        Map<String,String> filterMap=new LinkedHashMap<>();
        // Level1 is accessible only by vip1
        filterMap.put("/level1"."perms[vip1]");
        filterMap.put("/index"."anon");
        bean.setFilterChainDefinitionMap(filterMap);
        bean.setLoginUrl("/login");
        // If you do not have permission to jump to the page
        bean.setUnauthorizedUrl("/unauthorizedUrl");
        return bean;
    }
Copy the code

Level1: /unauthorizedUrl = /unauthorizedUrl = /unauthorizedUrl = /unauthorizedUrl = /unauthorizedUrl

@RequestMapping("/unauthorizedUrl")
@ResponseBody
public String unAuthorizedUrl(a){
    return "Current user does not have access";
}
Copy the code

Then code the authorization part in UserRealm:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    // Add vip1 permissions here for each user
    info.addStringPermission("vip1");
    return info;
}
Copy the code

In this code, add vip1 permission to all users by addStringPermission. In a real environment, we would give different permissions to different users. For example, add a permission field in the database, and replace vip1 in the above code with the database permission field.

(6) Summary

So far, we have covered shiro’s introduction, basic usage, authentication, and authorization. Shiro and SpringSecurity each have their advantages, so it’s up to your company to decide which framework to use.