1. 程式人生 > >shiro實現不同用戶多realm登錄

shiro實現不同用戶多realm登錄

shiro 多realm

現有app上因功能擴展,需另外一部分用戶登錄,和原來的用戶不在同一張表中。原來的shiro配置和單個realm不能滿足多個表中用戶(當然也可以在同一個realm中在兩個表中查找,一個表查不到就去另一個表查,這種方式太笨了),所以自己嘗試了以下擴展。實現不同的realm獲取不同表中的用戶。

一、先介紹一個用戶時是怎麽配置的

1、shiro.xml

<bean id="defaultSecurityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
    <property name="realm" ref="shiroDbRealm" />
    <property name="cacheManager" ref="shiroCacheManager" />
    <property name="authenticator" ref="authenticator"></property>
    <property name="sessionManager" ref="defaultSessionManager"/>
  </bean>
  <bean id="shiroDbRealm" class="com.su.ShiroCaptchaDbRealm">
    <!-- net.zkbc.shiro.AppConfig.hashedCredentialsMatcher() -->
    <property name="credentialsMatcher" ref="captchaCredentialsMatcher" />
  </bean>

2、

package com.su;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import net.zkbc.jcaptcha.util.JCaptchaUtils;
import net.zkbc.shiro.authc.IncorrectCaptchaException;
import net.zkbc.shiro.authc.UsernameCaptchaToken;
import net.zkbc.shiro.authc.UsernamePasswordCaptchaToken;
import net.zkbc.shiro.entity.ShiroUser;
import net.zkbc.shiro.service.ShiroCaptchaService;
import net.zkbc.shiro.service.ShiroUserService;
import redis.clients.jedis.Jedis;

public class ShiroCaptchaDbRealm extends ShiroDbRealm {

	@Autowired(required = false)
	private ShiroCaptchaService captchaService;
	@Autowired
	@Qualifier
	private ShiroUserService shiroUserService;
	
	@SuppressWarnings("resource")
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
			throws AuthenticationException {

		
		String loginName = ((UsernamePasswordToken) authcToken).getUsername();

		if (authcToken instanceof UsernameCaptchaToken) {
			Jedis jedis = new Jedis();
			String captcha = jedis.get(loginName); //TODO 獲取驗證碼相關
			byte[] salt_byte = null;
			return new SimpleAuthenticationInfo(loginName, captcha, ByteSource.Util.bytes(salt_byte), getName());
		}

		ShiroUser loginUser = shiroUserService.findUserByLoginName(loginName);

		if (loginUser == null) {
			throw new UnknownAccountException();
		}

		if (loginUser.isDisabled()) {
			throw new DisabledAccountException();
		}

		ByteSource salt = ByteSource.Util.bytes(shiroUserService.getSaltBytes(loginUser));

		return new SimpleAuthenticationInfo(loginName, loginUser.getPassword(), salt, getName());

	}

}

3、用postman訪問:http://localhost:10001/app/weicheLogin 參數 : {"custNum":"413185410"}

@RequestMapping(value = Urls.WCLOGIN, method = RequestMethod.POST)
	@ResponseBody
	public WcCustomerIsExistResponse wcLogin(@Valid @RequestBody WcCustomerIsExistRequest request, BindingResult result, Locale locale) {
		WcCustomerIsExistResponse response = new WcCustomerIsExistResponse();
		if (result.hasErrors()) {
			Validators.addParameterErrors(response, result, messageSource, locale);
			return response;
		}

		try {
			mobileService.bindSubject(request.getSessionId());
			response = messageMMSService.wcCustomerIsExist(request, response);
			mobileService.serviceForNoAuthcForm(request.getCustNum(), request, response);
		} catch (RemoteConnectFailureException e) {
			LOG.error(e.getMessage(), e);
			response = mockMessageMMSService.wcCustomerIsExist(request, response);
		} catch (ParameterException e) {
			Validators.addParameterErrors(response, e.getMessage(), messageSource, locale);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			response.error();
		}
		return response;
	}

4、

@Override
	public <REQUEST extends MobileRequest, RESPONSE extends MobileResponse> RESPONSE serviceForNoAuthcForm(
			String loginName, REQUEST request, RESPONSE response) {

		try {
			String sessionId = loginNoPassword(loginName).toString();
			request.setSessionId(sessionId);
			response.setSessionId(sessionId);
		} catch (UnknownAccountException e) {
			LOG.debug(e.getMessage(), e);
			response.error(MessageError.ERROR_AUTH);
		} catch (DisabledAccountException e) {
			LOG.debug(e.getMessage(), e);
			response.error(MessageError.ERROR_DISABLED);
		} catch (IncorrectCredentialsException e) {
			LOG.debug(e.getMessage(), e);
			response.error(MessageError.ERROR_AUTH);
		} catch (ExcessiveAttemptsException e) {
			LOG.debug(e.getMessage(), e);
			response.error(MessageError.ERROR_ATTEMPTS);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			response.error();
		}

		return response;
	}
	
	
private Serializable loginNoPassword(String loginName) {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		UsernameNoPasswordCaptchaToken token=new UsernameNoPasswordCaptchaToken(loginName,null,null);
		Subject subject = SecurityUtils.getSubject();
		try {
			subject.login(token);
		} catch (InvalidSessionException e) {
			subject.logout();
			//subject.login(token);
		}

		Session session = subject.getSession();
		Serializable sessionId = session.getId();

		LOG.debug("Session with id [{}] startTimestamp:{}", sessionId, session.getStartTimestamp().getTime());

		try {
			Thread.sleep(100);
		} catch (Exception ignored) {
		}

		session.touch();

		LOG.debug("Session with id [{}] lastAccessTime:{}", sessionId, session.getLastAccessTime().getTime());

		processConcurrentSessions();

		return sessionId;
	}	
	

5、

import org.apache.shiro.authc.UsernamePasswordToken;

public class UsernameNoPasswordCaptchaToken extends UsernamePasswordToken {
	private static final long serialVersionUID = 1L;

	private String username;

	public UsernameNoPasswordCaptchaToken(String username,String password,String host) {
		super(username,password,host);
		this.username = username;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}
	

}

二、多個realm

1、

<bean id="defaultSecurityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
   <!--  <property name="realm" ref="shiroDbRealm" /> -->
    <property name="cacheManager" ref="shiroCacheManager" />
    <property name="authenticator" ref="authenticator"></property>
    <property name="realms">
            <list>
                <ref bean="qggRealm" />
                <ref bean="weicheRealm"/>
            </list>
        </property>
    <!-- net.zkbc.shiro.AppConfig.defaultSessionManager() -->
    <property name="sessionManager" ref="defaultSessionManager"/>
  </bean>
  
  
  
  <bean id="authenticator" class="net.zkbc.shiro.authc.CustomizedModularRealmAuthenticator">
        <!-- 配置認證策略,只要有一個Realm認證成功即可,並且返回所有認證成功信息 -->
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
        </property>
    </bean>
    
    
    <bean id="qggRealm" class="net.zkbc.shiro.realm.QggRealm">
    <!-- net.zkbc.shiro.AppConfig.hashedCredentialsMatcher() -->
    <property name="credentialsMatcher" ref="captchaCredentialsMatcher" />
  </bean>
  
   <bean id="weicheRealm" class="net.zkbc.shiro.realm.WeicheRealm">
    <!-- net.zkbc.shiro.AppConfig.hashedCredentialsMatcher() -->
    <property name="credentialsMatcher" ref="captchaCredentialsMatcher" />
  </bean>

2、

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
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 org.springframework.beans.factory.annotation.Qualifier;


public class QggRealm extends AuthorizingRealm{

	@Autowired
	@Qualifier("qggUserService") 
	private ShiroUserService shiroUserService;
	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

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

		String loginName = ((CustomizedToken) authcToken).getUsername();

		ShiroUser loginUser = shiroUserService.findUserByLoginName(loginName);

		if (loginUser == null) {
			throw new UnknownAccountException();
		}

		if (loginUser.isDisabled()) {
			throw new DisabledAccountException();
		}

		ByteSource salt = ByteSource.Util.bytes(shiroUserService.getSaltBytes(loginUser));

		return new SimpleAuthenticationInfo(loginName, loginUser.getPassword(), salt, getName());
	}

	
}

2、

public class WeicheRealm extends AuthorizingRealm{

	@Autowired
	@Qualifier("weicheUserService") 
	private ShiroUserService shiroUserService;
	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

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

		String loginName = ((CustomizedToken) authcToken).getUsername();

		ShiroUser loginUser = shiroUserService.findUserByLoginName(loginName);

		if (loginUser == null) {
			throw new UnknownAccountException();
		}

		if (loginUser.isDisabled()) {
			throw new DisabledAccountException();
		}

		ByteSource salt = ByteSource.Util.bytes(shiroUserService.getSaltBytes(loginUser));

		return new SimpleAuthenticationInfo(loginName, loginUser.getPassword(), salt, getName());
	}

	
}

3、入參WcCustomerIsExistRequest 類中包括登錄名 custNum和登錄類型loginType (Qgg和Weiche)

用postman訪問:http://localhost:10001/app/mutilRealmLogin 參數 : {"custNum":"413185410","loginType":"Weiche"}

//
@RequestMapping(value = Urls.MUTILREALMLOGIN, method = RequestMethod.POST)
	@ResponseBody
	public WcCustomerIsExistResponse mutiRealmLogin(@Valid @RequestBody WcCustomerIsExistRequest request, BindingResult result, Locale locale) {
		WcCustomerIsExistResponse response = new WcCustomerIsExistResponse();
		if (result.hasErrors()) {
			Validators.addParameterErrors(response, result, messageSource, locale);
			return response;
		}

		try {
			mobileService.bindSubject(request.getSessionId());
			mobileService.serviceForMultiRealmAuthcForm(request.getCustNum(),request.getLoginType(), request, response);
		} catch (RemoteConnectFailureException e) {
			response = mockMessageMMSService.wcCustomerIsExist(request, response);
		} catch (ParameterException e) {
			Validators.addParameterErrors(response, e.getMessage(), messageSource, locale);
		} catch (Exception e) {
			response.error();
		}
		return response;
	}

4、

@Override
	public <REQUEST extends MobileRequest, RESPONSE extends MobileResponse> RESPONSE serviceForMultiRealmAuthcForm(
			String loginName, String loginType, REQUEST request, RESPONSE response) {

		try {
			String sessionId = loginMultiRealm(loginName,loginType).toString();
			request.setSessionId(sessionId);
			response.setSessionId(sessionId);
		} catch (UnknownAccountException e) {
			LOG.debug(e.getMessage(), e);
			response.error(MessageError.ERROR_AUTH);
		} catch (DisabledAccountException e) {
			LOG.debug(e.getMessage(), e);
			response.error(MessageError.ERROR_DISABLED);
		} catch (IncorrectCredentialsException e) {
			LOG.debug(e.getMessage(), e);
			response.error(MessageError.ERROR_AUTH);
		} catch (ExcessiveAttemptsException e) {
			LOG.debug(e.getMessage(), e);
			response.error(MessageError.ERROR_ATTEMPTS);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			response.error();
		}

		return response;
	}

5、

private Serializable loginMultiRealm(String loginName,String loginType) {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		CustomizedToken token=new CustomizedToken(loginName,null,loginType);
		Subject subject = SecurityUtils.getSubject();
		try {
			subject.login(token);
		} catch (InvalidSessionException e) {
			subject.logout();
			//subject.login(token);
		}

		Session session = subject.getSession();
		Serializable sessionId = session.getId();

		LOG.debug("Session with id [{}] startTimestamp:{}", sessionId, session.getStartTimestamp().getTime());

		try {
			Thread.sleep(100);
		} catch (Exception ignored) {
		}

		session.touch();

		LOG.debug("Session with id [{}] lastAccessTime:{}", sessionId, session.getLastAccessTime().getTime());

		processConcurrentSessions();

		return sessionId;
	}

6、

import org.apache.shiro.authc.UsernamePasswordToken;

public class CustomizedToken extends UsernamePasswordToken {

    //登錄類型,判斷是哪種用戶登錄
    private String loginType;

    public CustomizedToken(final String username, final String password,String loginType) {
        super(username,password);
        this.loginType = loginType;
    }

    public String getLoginType() {
        return loginType;
    }

    public void setLoginType(String loginType) {
        this.loginType = loginType;
    }
}

7、重要:這個類選擇使用哪個realm

import java.util.ArrayList;
import java.util.Collection;

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

/**
 * @author Alan_Xiang 
 * 自定義Authenticator
 * 註意,當需要分別定義處理普通用戶和管理員驗證的Realm時,對應Realm的全類名應該包含字符串“User”,或者“Admin”。
 * 並且,他們不能相互包含,例如,處理普通用戶驗證的Realm的全類名中不應該包含字符串"Admin"。
 */
public class CustomizedModularRealmAuthenticator extends ModularRealmAuthenticator {

    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        // 判斷getRealms()是否返回為空
        assertRealmsConfigured();
        // 強制轉換回自定義的CustomizedToken
        CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
        // 登錄類型
        String loginType = customizedToken.getLoginType();
        // 所有Realm
        Collection<Realm> realms = getRealms();
        // 登錄類型對應的所有Realm
        Collection<Realm> typeRealms = new ArrayList<>();
        for (Realm realm : realms) {
            if (realm.getName().contains(loginType))
                typeRealms.add(realm);
        }

        // 判斷是單Realm還是多Realm
        if (typeRealms.size() == 1)
            return doSingleRealmAuthentication(typeRealms.iterator().next(), customizedToken);
        else
            return doMultiRealmAuthentication(typeRealms, customizedToken);
    }

}

參考:https://blog.csdn.net/xiangwanpeng/article/details/54802509

shiro實現不同用戶多realm登錄