SpringBoot Shiro solves the problem that the custom Realm Autowired property in Shiro is empty

Keywords: Shiro Spring Spring Boot

SpringBoot is used as the main framework, and Shiro framework is used as the authentication and authorization module.

 

Before spring boot + Shiro + password encryption, I stepped on a lot of holes, so I walked through the Shiro process and made a record.

 

1. Introduce Shiro first

 

Everyone who has used Shiro knows that Shiro uses decorator mode internally. The big SecurityManager interface inherits three interfaces: Authenticator authentication, Authorizer authorization and SessionManager session management,

 

  Its implementation class is well understood according to its name. It should be noted that RealmSecurityManager and WebSecurityManager. WebSecurityManager is an interface. Its implementation class Shiro only provides one: DefaultWebSecurityManager, which is usually enough. Open this class to find a familiar Realm

 

In the constructor, this class needs a Realm, and then check the setRealm method. It is found that it has gone to the RealmSecurityManager. It can be roughly imagined that the DefaultWebSecurityManager inherits from the RealmSecurityManager.

 

 

In fact, it is true that RealmSecurityManager is an abstract class, and the parent class of RealmSecurityManager, cacheingsecuritymanager, is also an abstract class. We all know that abstract classes define the specification of a class of things or behavior processes. Let's look at the subclass implementation of RealmSecurityManager:

  You know, authorization management, authentication management, session management, and the DefaultWebSecurityManager provided by Shiro all rely on real.

Let's continue with Realm:

 

  As an interface, Realm has implementation classes under its command. Combined with Shiro's design on SecurityManager, it is easy to think that there must be abstract classes and default implementation classes in these classes. Also see cacheingrealm. In the design of SecurityManager, Cache is used as the abstract parent class of RealmManager. It must also be here:

  Let's look at its subclasses. Shiro is the security framework for authentication, and authentication should be in the next step of authentication, so click AuthenticatingRealm first:

  It's an abstract class, which is easy to understand. This abstract class must regulate Shiro's authentication steps or behaviors. See authentication authoringrealm:

It is still an abstract class and inherits from authenticated Realm:

  You can see that Realm inherits the authorized Realm --- authorizing Realm

This is true in the actual development. We added a custom Realm to write authentication and authorization logic, and the login module passed org.apache.shiro.subject.Subject#login As the entrance, the big head SecurityManager is responsible for calling the Realm, and the final authentication and authentication module will go to our customized Realm.

 

Shiro introduced Wuwu slag to be here for the time being.

 

2. What to start integrating:

 

Add maven dependency:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro-spring}</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>${spring-boot.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

<shiro-spring>1.8.0</shiro-spring>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
                

 

Add ShiroConfig configuration class:

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.mgt.SecurityManager;
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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * User: Pfatman
 * Date: 2021/11/9
 * Time: 16:31
 * Description: ShiroConfig
 */
@Slf4j
@Configuration
public class ShiroConfig {

    @Value("shiro_loginPage:login")
    private String loginPage;



    /**
     * Permission management is mainly used to configure the management authentication of realm
     * @return
     */
    @Bean
    public SecurityManager securityManager(){
        return new DefaultWebSecurityManager();

    }

    /**
     * Dealing with intercepting resources
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        factoryBean.setLoginUrl(loginPage);
        Map<String,String> map=new LinkedHashMap<>();
        map.put("/static/**","anon");
        map.put("/logout","logout");
        factoryBean.setFilterChainDefinitionMap(map);
        return factoryBean;
    }

    /**
     * Shiro Bean life cycle
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * Shiro Agent enhancements provided
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * Authorization attribute enhancement
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor attributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
        attributeSourceAdvisor.setSecurityManager(securityManager());
        return attributeSourceAdvisor;
    }

}

 

two point one   Throw a question:

 

In the strict sense of Shiro's Config above, there is a little less, that is, the custom Realm. When introducing Shiro, we saw that the constructor in the SecurityManager has Realm, but the SecurityManager is configured in the above configuration. Here is a direct return new

DefaultWebSecurityManager();

    @Bean
    public Realm realm(){
       Realm realm = new MyRealm();
        return realm;
    }
@Bean public SecurityManager securityManager(Realm realm){ return new DefaultWebSecurityManager(realm); }

 

However, setting the Realm for SecurityManager in the above way may cause a problem. If there are objects or parameters in the custom Realm that depend on other injected beans, the properties injected through @ Autowired in the Realm may be null. This is because Shiro's beans start initializing other beans after initialization, that is, SecurityManager When real initializes a bean, other beans are not initialized and are null. If we directly insert a new Realm when constructing the bean of SecurityManager in the above way, the attribute injected through @ Autowired in MyRealm will be null.

 

2.2 how to solve:

 

The problem that the injection attribute in the Realm is empty usually occurs when Shiro's beans have been fully initialized before other beans are loaded. From this point of view, take our customized Realm as a Bean and initialize it by the Spring container, but this will lead to the absence of the Realm attribute in the SecurityManager Bean configured in ShiroConfig. Then the problem becomes to solve the problem of injecting our Realm into SecurityManager:

 

1. Inject SecurityManager into the custom Realm, and set the SecurityManager property Realm to this:

 

@Slf4j
@Service("wencharRealm")
public class WencharRealm extends AuthorizingRealm {


    @Autowired
    ILoginUserInfoService loginUserInfoService;
    
    @Autowired
    public WencharRealm(WencharCredentialsMatcher matcher){
        super.setCredentialsMatcher(matcher);
    }

    
    
    @Autowired
    private void webSecurityManager(SecurityManager securityManager) {
        if (securityManager instanceof DefaultWebSecurityManager) {
            log.info("==by DefaultWebSecurityManager set up Realm==");
            DefaultWebSecurityManager webSecurityManager = (DefaultWebSecurityManager) securityManager;
            webSecurityManager.setRealm(this);
        }
    }
    
    
    /**
     * to grant authorization
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    }
    
    
    /**
     * authentication
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    }

    
}

 

 

2. Personally, I don't recommend that Realm, as a Bean, set Realm for SecurityManager after the Spring container is fully initialized, or use @ PostConstruct annotation.

 

 

ShiroConfig implements the implements applicationlistener < contextrefreshedevent > interface and assigns a value to the SecurityManager during refresh, but this is not as direct as the first method.

I feel that although it can realize the function, it does destroy the Bean process.

 

above.

 

 

3. Password comparator: CredentialsMatcher

 

Another supplementary content is the password verifier provided by Shiro, including encryption algorithm and encryption times

Customize a password verifier:

 

@Component
public class WencharCredentialsMatcher extends HashedCredentialsMatcher {


    @Value("${REAL_SALTCOUNT:1024}")
    private int saltCount;

    @Override
    public int getHashIterations() {
        return saltCount;
    }

    @Override
    public void setHashAlgorithmName(String hashAlgorithmName) {
        super.setHashAlgorithmName(Md5Hash.ALGORITHM_NAME);
    }
}

 

 

 

  Note: the above user-defined password comparator inherits from HashedCredentialsMatcher, sets the encryption times to 1024 by default, and the encryption algorithm is Md5

In this way, the Realm needs to correspond to the login entry subject.login (), such as password, salt, etc.

Login verification:

 

        Subject subject = SecurityUtils.getSubject();
        try {
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                    loginUser.getLoginName(),
                    loginUser.getLoginPwd());
            subject.login(usernamePasswordToken);
        } catch (AuthenticationException e) {
            log.debug("===loginUser failed login==[{}]",loginUser);
            return ResponseVo.failResponse("Username or password incorrect ");
        }

 

 

Authentication verification in Realm:

    /**
     * authentication
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String userName = authenticationToken.getPrincipal().toString();
        LoginUserVo loginUserVo = userInfoService.queryUserLoginInfo(userName);
        return new SimpleAuthenticationInfo(loginUserVo.getAccountId(),
                loginUserVo.getPassword(),
                ByteSource.Util.bytes(loginUserVo.getSalt()),
                getWencharRealmName());
    }

 

 

Authentication and Subject.login(token) in Realm; It can be distinguished in this way. The user name, password before encryption and salt are transmitted in the token. These data will go through the salt encryption algorithm according to the parameters in the password comparator in the SecurityManager and the salt value in the AuthenticationInfo transmitted in the real, and then compare with the userName and password in the real.

 

above

 

Posted by Ryan0r on Mon, 22 Nov 2021 23:27:42 -0800