ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringBoot Shiro,解决Shiro中自定义Realm Autowired属性为空问题

2021-11-23 13:35:29  阅读:180  来源: 互联网

标签:return 自定义 SecurityManager Bean Realm public Shiro


SpringBoot作为主体框架,使用Shiro框架作为鉴权与授权模块。

 

之前弄SpringBoot+Shiro+密码加密还是踩了不少坑,于是把Shiro流程走了一遍,做个记录。

 

1.先介绍Shiro

 

用过Shiro的都知道,shiro内部使用装饰者模式,大头SecurityManager接口继承Authenticator认证、Authorizer授权、SessionManager会话管理 三个接口,

 

 其实现类根据名字很好理解,需要注意的就是RealmSecurityManager、WebSecurityManager。其中WebSecurityManager是一个接口,其实现类Shiro只提供了一个:DefaultWebSecurityManager,通常这一个也足够用了,打开这个类查看,可以发现一个很熟悉的Realm

 

构造函数中,该类要了一个Realm,再查看setRealm方法,发现走到了RealmSecurityManager里了,大致可以联想到,DefaultWebSecurityManager继承自RealmSecurityManager。

 

 

实际上也的确如此,RealmSecurityManager是一个抽象类且RealmSecurityManager的父类CachingSecurityManager同样也是抽象类。我们都知道抽象类定义了一类事物或行为流程的规范,再来看RealmSecurityManager的子类实现:

 那心里就有数了,授权管理、认证管理、会话管理、Shiro提供的DefaultWebSecurityManager都依赖于Realm。

那继续来看Realm:

 

 Realm作为一个接口,其麾下皆是实现类,再结合之前看到的Shiro有关SecurityManager的设计,容易想到这些类中必定有抽象类,默认实现类。又看到CachingRealm,在SecurityManager的设计中Cache便作为RealmManager的抽象父类,想必这里也是

 再看其子类,因为Shiro是认证鉴权的安全框架,又因为鉴权应当在认证的后一步,所以先点开AuthenticatingRealm:

 是个抽象类很好理解,该抽象类肯定是规范了Shiro的认证步骤或者行为,再看鉴权AuthorizingRealm:

依然是个抽象类,且继承自认证Realm:

 可以看到Realm继承了授权Realm----AuthorizingRealm

实际开发中也的确是如此,我们增加自定义Realm编写认证、授权逻辑,登陆模块通过org.apache.shiro.subject.Subject#login 作为入口,由大头SecurityManager来负责调用Realm,最终认证、鉴权模块便会走到我们自定义的Realm中。

 

Shiro介绍五五渣渣暂时到这里。

 

2. 那开始弄集成的内容:

 

添加maven依赖:

<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>
                

 

添加ShiroConfig配置类:

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;



    /**
     * 权限管理 主要是配置realm的管理认证
     * @return
     */
    @Bean
    public SecurityManager securityManager(){
        return new DefaultWebSecurityManager();

    }

    /**
     * 处理拦截资源问题
     * @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生命周期
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * Shiro 提供的代理增强
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 授权属性增强
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor attributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
        attributeSourceAdvisor.setSecurityManager(securityManager());
        return attributeSourceAdvisor;
    }

}

 

2.1  抛出问题:

 

上面有关Shiro的Config严格意义上其实少了一点,那就是自定义的Realm,之前介绍Shiro的时候,我们便看到SecurityManager中构造函数有Realm,但上述配置中配置SecurityManager这里是直接return new

DefaultWebSecurityManager();

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

 

但是上述方式为SecurityManager设置Realm可能会产生一个问题,就是如果自定义Realm中有依赖其它注入Bean的对象或者参数,可能导致Realm中通过@Autowired注入的属性为null,这是因为Shiro的bean在初始化完成之后才开始初始化其它Bean,即SecurityManager、Realm在初始化Bean的时候其它Bean并未初始化,为null。如果通过上述方式在构造SecurityManager这个Bean的时候我们直接塞一个new Realm的话,那其实MyRealm中通过如@Autowired注入的属性便为null了。

 

2.2 如何解决:

 

出现这种Realm中注入属性为空的问题通常是Shiro的Bean在其它Bean加载完成之前就已完全完成初始化了,那从这点考虑,将我们自定义的Realm作为一个Bean,由Spring容器来初始化,但这样会导致我们在ShiroConfig中配置的SecurityManager这个Bean中没有Realm属性。那问题就变成解决SecurityManager中注入我们Realm的问题了:

 

1. 在自定义Realm中注入SecurityManager,对SecurityManager设置属性Realm为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("==为DefaultWebSecurityManager 设置Realm==");
            DefaultWebSecurityManager webSecurityManager = (DefaultWebSecurityManager) securityManager;
            webSecurityManager.setRealm(this);
        }
    }
    
    
    /**
     * 授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    }
    
    
    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    }

    
}

 

 

2.个人不推荐,Realm作为Bean,在Spring容器完全初始化完成后对SecurityManager设置Realm,或者使用@PostConstruct注解。

 

 

ShiroConfig 实现implements ApplicationListener<ContextRefreshedEvent> 接口,刷新时为SecurityManager赋值,但这样不如第一种来的直接。

个人感觉虽然能实现功能,但也的确破坏了Bean流程。

 

以上。

 

 

3. 密码比对器:CredentialsMatcher

 

补充介绍另外一个内容,Shiro提供的密码验证器,包括加密算法、加密次数

自定义一个密码验证器:

 

@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);
    }
}

 

 

 

 说明:上述自定义密码比对器继承自HashedCredentialsMatcher,设置加密次数默认为1024次,加密算法为Md5

这样需要Realm与登陆入口subject.login() 相对应,如密码、盐 等。

登陆入口校验:

 

        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("用户名或密码不正确");
        }

 

 

Realm中认证校验:

    /**
     * 认证
     *
     * @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());
    }

 

 

Realm中认证和Subject.login(token); 可以这样区分,token中传用户名、加密前的密码、盐, 这些数据会根据SecurityManager中密码比较器中的参数,以及Realm中传递的AuthenticationInfo中盐值,过一遍加盐加密算法然后与 Realm中userName、password比较。

 

以上

 

标签:return,自定义,SecurityManager,Bean,Realm,public,Shiro
来源: https://www.cnblogs.com/notably/p/15592752.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有