shiro手机验证码登录,多Realm处理

最近项目用的shiro权限,然后 涉及到2种登录方式,第一种是基础的账号密码登陆,这是shiro提供的基础功能。第二种是手机短信验证码登陆。我具体讲讲第二种我的思路是用阿里云发送短信验证码的时候把code存到session中,key就是手机号,然后当用户用手机号登陆的时候我在shiro验证之前 验证用户提交的code 和 我存在session中的code是不是相同,下面详细写写我在这个项目中怎么做的。

首先是ShiroConfig

package com.mytest.configuation;

import com.mytest.filter.ShiroLoginFilter;
import com.mytest.shiro.MyCustomModularRealmAuthenticator;
import com.mytest.shiro.IdCardAndPasswordShiroRealm;
import com.mytest.shiro.PhoneAndVerificationCodeShiroRealm;
import com.mytest.shiro.UserShiroRealm;
import org.apache.shiro.authc.AbstractAuthenticator;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Configuration
public class ShiroConfig {

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

    /**
     * 账号密码登陆验证方式
     * @return 自定义的账号密码验证Realm
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public UserShiroRealm userShiroRealm(){
        return new UserShiroRealm();
    }

    /**
     * 手机短信验证码登陆
     * @return 自定义的手机短信验证Realm
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public PhoneAndVerificationCodeShiroRealm phoneShiroRealm(){
        return new PhoneAndVerificationCodeShiroRealm();
    }
    //这里把 前面两个bean 传入到 manager中
    @Bean
    public SecurityManager securityManager(UserShiroRealm userShiroRealm, PhoneAndVerificationCodeShiroRealm phoneShiroRealm, AbstractAuthenticator abstractAuthenticator){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        List<Realm> realms = new ArrayList<>();
        realms.add(userShiroRealm);
        realms.add(phoneShiroRealm);
        defaultWebSecurityManager.setRealms(realms);
        defaultWebSecurityManager.setAuthenticator(abstractAuthenticator);
        return defaultWebSecurityManager;
    }

    /**
     * 认证器 把我们的自定义验证加入到认证器中
     */
    @Bean
    public AbstractAuthenticator abstractAuthenticator(UserShiroRealm userRealm, PhoneAndVerificationCodeShiroRealm phoneShiroRealm){
        // 自定义模块化认证器,用于解决多realm抛出异常问题
        //开始没用自定义异常问题,发现不管是账号密码错误还是什么错误
        //shiro只会抛出一个AuthenticationException异常
        ModularRealmAuthenticator authenticator = new MyCustomModularRealmAuthenticator();
        // 认证策略:AtLeastOneSuccessfulStrategy(默认),AllSuccessfulStrategy,FirstSuccessfulStrategy
        authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        // 加入realms
        List<Realm> realms = new ArrayList<>();
        realms.add(userRealm);
        realms.add(phoneShiroRealm);
        authenticator.setRealms(realms);
        return authenticator;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //添加Shiro内置过滤器,常用的过滤器:
        //       anon:无需认证;authc:必须认证;user:如果使用rememberMe的功能可以直接访问;
        //       perms:必须得到资源权限;roles:必须得到角色权限
        Map<String,String> filterMap = new LinkedHashMap<>();
        filterMap.put("/logout.do","logout");
        filterMap.put("/menu.do", "authc");
        filterMap.put("/index.do","anon");
        filterMap.put("/org/**","anon");
        filterMap.put("/person/**","anon");
        filterMap.put("/resource/**","anon");
        filterMap.put("/**","anon");
        filterMap.put("/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 加入shiro注解
     * @param
     * @return
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }


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

}

 

1. 账号密码登陆

这种就相当于默认的shiro处理。

使用的是默认的 UsernamePasswordToken

只用自定义AuthorizingRealm

UserShiroRealm具体代码:

package com.mytest.shiro;

import com.mytest.entity.Person;
import com.mytest.repository.PersonRepository;
import com.mytest.service.PersonService;
import com.mytest.service.ResourceService;
import com.mytest.util.Constant;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;


public class UserShiroRealm extends AuthorizingRealm {

    @Autowired
    private ResourceService resourceService;

    @Autowired
    private PersonService personService;

    @Autowired
    private PersonRepository personRepository;

    /**
     * 执行授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权");
        Person person = (Person) principalCollection.getPrimaryPrincipal();
        Set<String> permissionSet = resourceService.getResourceSetByPersonId(person.getPersonId());
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addStringPermissions(permissionSet);
        return simpleAuthorizationInfo;
    }

    /**
     * 执行认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证");
        String userName = authenticationToken.getPrincipal().toString();
        SimpleAuthenticationInfo info=null;
        Person user = personService.getLoginPerson(userName);
        if (user == null) {
            throw new UnknownAccountException();
        }
        //密码加密的盐值
        ByteSource credentialsSalt = ByteSource.Util.bytes(user.getUsername());
        info = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, getName());
        }
        return info;
    }

    /**
     * 自定义加密模式 MD5
     *
     * @param credentialsMatcher
     */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher MD5CredentialsMatcher = new HashedCredentialsMatcher();
        MD5CredentialsMatcher.setHashAlgorithmName("MD5");
        MD5CredentialsMatcher.setHashIterations(10);
        super.setCredentialsMatcher(MD5CredentialsMatcher);
    }

    /**
     * 用来判断是否使用当前的 realm
     * @param var1 传入的token
     * @return true就使用,false就不使用
     */
    @Override
    public boolean supports(AuthenticationToken var1){
        return var1 instanceof UsernamePasswordToken;
    }
}

账号密码登陆的 算是搞定了。因为是shiro提供的基础功能所以这一种登陆方式很简单。

2.手机短信验证码登陆

首先自定义PhoneAndVerificationCodeToken,因为是手机验证码登陆,没有密码 所以不能用shiro自带的 UsernamePasswordToken

package com.mytest.shiro;

import org.apache.shiro.authc.HostAuthenticationToken;
import org.apache.shiro.authc.RememberMeAuthenticationToken;

public class PhoneAndVerificationCodeToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
   private String phone;
   private boolean rememberMe;
   private String host;

   public PhoneAndVerificationCodeToken(String phone){
       this(phone,false,null);
   }


    public PhoneAndVerificationCodeToken(String phone,boolean rememberMe){
        this(phone,rememberMe,null);
    }
    public PhoneAndVerificationCodeToken(String phone,boolean rememberMe,String host){
        this.phone=phone;
        this.rememberMe=rememberMe;
        this.host=host;
    }
    @Override
    public String getHost() {
        return host;
    }

    @Override
    public boolean isRememberMe() {
        return rememberMe;
    }

    @Override
    public Object getPrincipal() {
        return phone;
    }

    @Override
    public Object getCredentials() {
        return phone;
    }
}

然后自定义处理PhoneAndVerificationCodeToken的Realm

package com.mytest.shiro;

import com.mytest.entity.Person;
import com.mytest.repository.PersonRepository;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;


public class PhoneAndVerificationCodeShiroRealm extends AuthorizingRealm {

    @Autowired
    private PersonRepository personRepository;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        PhoneAndVerificationCodeToken phoneToken=(PhoneAndVerificationCodeToken) token;
        String phone = (String) phoneToken.getPrincipal();
        Person user = personRepository.findByPhone(phone);
        //因为没有密码,并且验证码在之前就验证了 所以我这里没有验证密码
        if(user == null){
            throw new UnknownAccountException();
        }
        return new SimpleAuthenticationInfo(user,phone,getName());
    }

    /**
     * 用来判断是否使用当前的 realm
     * @param var1 传入的token
     * @return true就使用,false就不使用
     */
    @Override
    public boolean supports(AuthenticationToken var1) {
        return var1 instanceof PhoneAndVerificationCodeToken;
    }
}

最后最重要的是 要自定义模块化认证器,这也是我卡了很久的问题,之前一直没加这个,然后发现一直跟我抛AuthenticationException异常。

package com.mytest.shiro;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;

import java.util.Collection;
import java.util.Iterator;


public class MyCustomModularRealmAuthenticator extends ModularRealmAuthenticator{

    @Override
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
        AuthenticationStrategy authenticationStrategy = this.getAuthenticationStrategy();
        AuthenticationInfo authenticationInfo = authenticationStrategy.beforeAllAttempts(realms,token);

        Iterator var5 = realms.iterator();
        while(var5.hasNext()) {
            Realm realm = (Realm)var5.next();
            authenticationInfo = authenticationStrategy.beforeAttempt(realm, token, authenticationInfo);
            if (realm.supports(token)) {

                AuthenticationInfo info = null;
                Throwable t = null;

                info = realm.getAuthenticationInfo(token);

                authenticationInfo = authenticationStrategy.afterAttempt(realm, token, info, authenticationInfo, t);
            }
        }
        authenticationInfo = authenticationStrategy.afterAllAttempts(token, authenticationInfo);
        return authenticationInfo;
    }
}

OK,基本完成了。剩下的就是在 你的登录 controller中 不同的 登录方式 用不同的 token 然后 

SecurityUtils.getSubject().logon(token)。就好了

emmmmm,shiro还有可能会遇到前后端分离的问题,光有CorsFilter还不够, 不过这篇博客不是讲这个 我就不写拉。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值