Shiro study notes

Shiro takes notes, Xiaobai records the learning process, and the boss takes a detour

I What is Shiro

Shiro, like Security, is a Security framework. It can help us realize authentication, authorization, MD5 encryption, caching and other functions.

Shiro has three elements: Subject, Security Manager and Realm.

  • Subject: the current subject, which can be understood as a user.
  • Security Manager: Security Manager, which manages all subjects.
  • Realm: specifically implement authorization, authentication and other operations.

The relationship between the three is:

        1. Create Realm and inherit the AuthorizingRealm class, and implement the doGetAuthorizationInfo (authorization) and doGetAuthenticationInfo (authentication) methods.
        2. Create a security manager and inject the realm created in step 1 into the security manager.
        3. Create a Subject. And set its security manager to the security manager created in step 2.

II Shiro Trilogy

Real is the key to authentication, authorization and data reading. It is also the first step in building Shiro trilogy. We need to create our own Realm, inherit the authoringrealm class, and then rewrite the authentication and authorization methods. How to implement these two methods can be based on our actual business needs.

2.1 create and get Realm

Create the Myrealm class:

public class MyRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null; 
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

Get Myrealm class:

After creating a Realm class, we need to get an object of this class and inject it into Security Manager.
Create shiroconfig Java file and create the get function.

    @Bean  //Add Bean annotations to facilitate container identification
    public MyRealm getMyRealm(){
        return new MyRealm();
    }

2.2 setting safety manager

The Security Manager manages all subjects. The hub and Subject Security Manager are also in the middle. When users submit authentication and authorization requests, the Subject will be hosted to the Security Manager, which will call the authentication and authorization methods in real for authentication and authorization.

In shiroconfig Java file, create the security manager to obtain the method.

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){ //Pass the realm obtained in 2.1 and inject
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

2.3 setting Subject

Although Subject is a major element of Shiro, it needs to be written as ShiroFilterFactoryBean when encoding. Therefore, in shiroconfig. Created above Create ShiroFilterFactoryBean in Java file to obtain the method.

@Bean
//Similarly, pass the security manager obtained in 2.2 through parameters
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){ 
    
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        return bean;
    }

Through the above three steps, a basic framework has been set up. In the future, authentication, authorization, MD5 encryption and cache processing can be carried out by improving the methods in the real class and modifying the specific information of the trilogy in ShiroConfig.

III authentication

Authentication, in fact, means login. In the previous login, we wrote our own code to read the data from the database and match to judge whether we can log in. In Shiro, Shiro helps us with this function. We can't see the information about account, password and so on in the code. Ensure its safety.

Shiro certification implementation includes:

        1. Write mapper for database reading.
        2. The specific implementation of doGetAuthenticationInfo in real.
        3. Write Controller

About how to write Mapper to read the database, in Springboot landing demo actual combat It has been described in detail in. Here we have realized the first step. Start directly from step 2.

3.1 specific implementation of doGetAuthenticationInfo in realm

public class MyRealm extends AuthorizingRealm {

    @Autowired
    UserServiceImpl userService;  //Inject our data read class

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

		//Get the token generated by the account password
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //Query through the account in the token. If the query fails, null will be returned
        User user = userService.findOneByName(token.getUsername());
        if (user == null) {
            return null;
        }
		//After the query, an object of SimpleAuthenticationInfo is returned, and the second parameter is the password read from the database
        return new SimpleAuthenticationInfo("", user.getPassword(), "");
    }
}
}

3.2 writing Controller:

@Controller
public class LoginController {

   @RequestMapping("/logging") //Get the submitted form of the page
    public String login(String username, String password, Model model){
       Subject subject = SecurityUtils.getSubject(); //Get current user
       //Use the account and password submitted from the page to generate a token
       UsernamePasswordToken token = new UsernamePasswordToken(username,password);
       try {
           subject.login(token);//subject. The login method will automatically call the authentication method in realm
           return "success";
       } catch (UnknownAccountException e) {
           model.addAttribute("msg","Account does not exist");//null is returned in realm. Here, it will automatically determine what kind of exception is and throw it
           return "index";
       }catch (IncorrectCredentialsException e){
           model.addAttribute("msg","Password error");
           return "index";
       }
   }

IV to grant authorization

Why authorization is required:

        1. For example, the user does not log in, but he will automatically jump to the successful login page after entering the website "localhost:8080 / login success page".
        2. The content displayed on the page is different according to the user's permissions. For example, the administrator may have four buttons after successful login, but ordinary users may only have one function button.

Due to the above requirements, different authorization operations need to be carried out according to the user's permissions. Shiro's authorization operations include:

        1. Preparation of ShiroFilterFactoryBean in Shiro trilogy.
        2. Implementation of doGetAuthorizationInfo authorization method in real.

4.1 preparation of ShiroFilterFactoryBean in Shiro Trilogy

When we created Shiro trilogy just now, we only wrote the general framework: obtaining realm, obtaining security manager, injecting realm into security manager, obtaining Subject, setting security manager of Subject and other steps, but did not write the contents in detail.

Here, by calling subject The setfilterchaindefinitionmap() method performs permission control. The parameter required by this method is a hashmap type.

@Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        Map<String, String> mymap = new LinkedHashMap(); //new a hashmap
        //Store some values, which means that the success page must have permission [login] to access
        mymap.put("/success","perms[login]");
        bean.setFilterChainDefinitionMap(mymap);//Pass in the map through the setFilterChainDefinitionMap method
        return bean;
    }

Through the above steps, we have set certain permissions on some pages. Next, we will authenticate by implementing the authentication method in realm.

4.2 implementation of doGetAuthorizationInfo authorization method in real

Remember that in the process of rewriting the authentication method, if the authentication is successful, we will return a SimpleAuthenticationInfo object. The second parameter is the password read out by the database. In fact, the first parameter Principal is the current user. Therefore, we can obtain the current user through the Principal parameter in the SimpleAuthenticationInfo object returned by the authentication method.

  @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    	//Get the current object through the getSubject method in SecurityUntils
        Subject subject = SecurityUtils.getSubject();
        //new a SimpleAuthorizationInfo object
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //Obtain the current object and the permission field of the object through the Principal parameter in the return value of the authentication method
        User currentUser = (User) subject.getPrincipal();
        //Add it to SimpleAuthorizationInfo
        info.addStringPermission(currentUser.getPermission());
        return info;
    }

Through the above methods, we have implemented the method that requires authorization to access some pages.

V Md5 salt value encryption

Although Shiro does a series of operations such as password matching, there are still some security risks because your database stores plaintext passwords. Therefore, Shiro supports the Md5 encryption function, but because the Md5 algorithm is public, some people can have the chance to crack simple passwords by constantly trying. Therefore, Shiro also supports Md5 salt value encryption.
Using Shiro to realize Md5 salt value encryption includes three steps:

        1. Configure Realm.
        2. Modify the authentication function.

5.1 configuring Realm

After we get the realm object created by ourselves, don't rush back. We need to set it.

 @Bean
    public UserRealm getUserRealm(){
        UserRealm realm = new UserRealm();
        //The default is to generate SimpleCredentialsMaster. In order to use MD5, a HashCredentialsMaster is declared here
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //Tell it what encryption algorithm you use
        credentialsMatcher.setHashAlgorithmName("md5");
        //Set the number of hash hashes. The default is once
        credentialsMatcher.setHashIterations(1024);
        //Authenticator that sets it to realm
        realm.setCredentialsMatcher(credentialsMatcher);
        return realm;

We set the authentication manager of realm to hash, the hash algorithm used for authentication to Md5, and the number of hash hashes to 1024. The default is hash once

5.2 modify authentication function

 @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        User user = userService.findOneByName(token.getUsername());
        if(user == null){
            return null;
        }
        //The first parameter is principal user; The second parameter is the password of the database; The third parameter is salt. The setting method is bytesource Util. bytes(); The fourth parameter is the name of realm
        return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes("hky2ymf"),"");
    }

Obviously, we only modified the third parameter in the SimpleAuthenticationInfo object of return, which tells Shiro how much salt Md5 is. For example, we used Hash authenticator and Md5 encryption method, and hashed 1024 times. We don't need to transfer these information manually, because they have been set in realm. Shiro will send it automatically.

Vi cache

Why cache?

As the name suggests, cache is something stored in memory. For example, when you log in, the program will go to the database to read the information, and then Shiro will authenticate and authorize it. But when you refresh the page, the above steps will be repeated. In other words, you need to go to the database to read data every time you load a page. When the number of users is particularly large, the pressure on the database is great.

The purpose of caching is to relieve the pressure on the database. Only when we load the page for the first time, we will read the data from the database. During subsequent loading, Shiro will first find it in the cache. If it exists in the cache, we will not access the database, otherwise we will find it in the database. To some extent, it reduces the pressure on the database.
The default cache supported by Shiro is Ehcache, and Shiro Ehcache dependency needs to be imported.

6.1 import dependency

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.0</version> //The version here should be the same as the Shiro dependent version you imported
        </dependency>

6.2 configuring realm

Just like setting md5, you need to configure realm. After all, realm is the core.

 @Bean
    public UserRealm getUserRealm(){
        UserRealm realm = new UserRealm();
        //The default is to generate SimpleCredentialsMaster. In order to use MD5, a HashCredentialsMaster is declared here
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //Tell it what encryption algorithm you use
        credentialsMatcher.setHashAlgorithmName("md5");
        //Set the number of hash hashes. The default is once
        credentialsMatcher.setHashIterations(1024);
        //Authenticator that sets it to realm
        realm.setCredentialsMatcher(credentialsMatcher);
        //Set the cache manager shiro. The default cache manager is ehcache
        realm.setCacheManager(new EhCacheManager());
        //Enable shiro's cache
        realm.setCachingEnabled(true);
        //Enable authentication cache
        realm.setAuthenticationCachingEnabled(true);
        //Enable authorization cache
        realm.setAuthorizationCachingEnabled(true);
        //Set authentication cache name
        realm.setAuthenticationCacheName("authentication");
        //Set authorization cache name
        realm.setAuthorizationCacheName("authorization");

        return realm;

    }

By setting the cachemanager of realm to ehcache manager, enabling Shiro cache, enabling authorization cache and enabling authentication cache, the whole Shiro cache can be enabled.
However, this default cache also has disadvantages. When the server is powered off or the project is restarted, the database still needs to be accessed again. To solve this situation, you can use redis. There is not much research here, so I won't repeat it.

7 Summary

Through the above six steps, Shiro's basic functions of authentication, authorization, encryption and caching are realized. Because they all call API s and don't delve into the source code, there is still a lot of room for progress. But for beginners, Shiro is OK.

Keywords: Java Shiro Spring

Added by crispytown on Mon, 07 Mar 2022 18:40:45 +0200