shiro實現手機驗證碼登錄(涉及到:自定義token、多realm配置、自定義ModularRealmAuthenticator)

shiro框架提供了一個UsernamePasswordToken令牌,用來驗證用戶名和密碼類的登錄。那如果想要通過替他方式登錄認證,例如通過手機驗證碼接口,就需要通過自定義token、自定義realm等來實現。

1、首先,自定義一個token繼承UsernamePasswordToken,爲什麼要繼承這個類而不是AuthenticationToken?,是因爲這樣做保證了用戶名密碼認證方式任然能正常使用。代碼如下。

package com.java.travel.token;

import java.io.Serializable;

import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;

public class UserNamePasswordTelphoneToken extends UsernamePasswordToken implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 4812793519945855483L;

	// 手機號碼
	private String telphoneNum;

	/**
	 * 重寫getPrincipal方法
	 */
	public Object getPrincipal() {
		// TODO Auto-generated method stub
		// 如果獲取到用戶名,則返回用戶名,否則返回電話號碼
		if (telphoneNum == null) {
			return getUsername();
		} else {
			return getTelphoneNum();
		}
	}

	/**
	 * 重寫getCredentials方法
	 */
	public Object getCredentials() {
		// TODO Auto-generated method stub
		// 如果獲取到密碼,則返回密碼,否則返回null
		if (telphoneNum == null) {
			return getPassword();
		} else {
			return "ok";
		}
	}

	public UserNamePasswordTelphoneToken() {
		// TODO Auto-generated constructor stub
	}

	public UserNamePasswordTelphoneToken(final String telphoneNum) {
		// TODO Auto-generated constructor stub
		this.telphoneNum = telphoneNum;
	}

	public UserNamePasswordTelphoneToken(final String userName, final String password) {
		// TODO Auto-generated constructor stub
		super(userName, password);
	}

	public String getTelphoneNum() {
		return telphoneNum;
	}

	public void setTelphoneNum(String telphoneNum) {
		this.telphoneNum = telphoneNum;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	@Override
	public String toString() {
		return "TelphoneToken [telphoneNum=" + telphoneNum + "]";
	}

}
重寫了getPrincipal方法和getCredentials方法,getPrincipal方法如果是用用戶名和密碼方式登錄的則就返回用戶名,手機驗證碼登錄則表示手機號碼,同理getCredentials在用戶名密碼登錄中表示密碼,在驗證碼登錄中則什麼都不表示,返回一個任意的字符串就可以了,不能返回null,否則認證不會通過的。

2、自定義一個ModularRealmAuthenticator的子類,重寫doAuthenticate方法,這個方法的功能是用來決定單realm或者多realm時應該怎麼做的,代碼如下:

package com.java.travel.ModularRealmAuthenticator;

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;

import com.java.travel.token.UserNamePasswordTelphoneToken;

public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {
	@Override
	protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
			throws AuthenticationException {
		// TODO Auto-generated method stub
		
		// 判斷getRealms()是否返回爲空
        assertRealmsConfigured();
        // 強制轉換回自定義的CustomizedToken
        UserNamePasswordTelphoneToken telphoneToken = (UserNamePasswordTelphoneToken) authenticationToken;
        // 所有Realm
        Collection<Realm> realms = getRealms();
     // 判斷是單Realm還是多Realm
        if (realms.size() == 1)
            return doSingleRealmAuthentication(realms.iterator().next(), telphoneToken);
        else
            return doMultiRealmAuthentication(realms, telphoneToken);
	}
}
3、自定義realm,繼承於AuthorizingRealm類,重寫doGetAuthorizationInfo和doGetAuthenticationInfo這兩個方法,前者是用來做授權處理的,後者用來身份認證。代碼如下:

package com.java.travel.realm;

import javax.annotation.Resource;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import com.java.travel.entity.ExUser;
import com.java.travel.service.ExUserService;

public class TelphoneRealm extends AuthorizingRealm{

	@Resource
	ExUserService exUserService;
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// TODO Auto-generated method stub
		String telphoneNum = (String) token.getPrincipal();
		ExUser exUser = exUserService.selectByTelphoneNum(telphoneNum);
		if (exUser != null) {
			AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(exUser.getTEL(), "ok", "xx");
			return authcInfo;
		} else {		
			return null;
		}
	}

}
這裏要注意AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(exUser.getTEL(), "ok", "xx");這個語句中SimpleAuthenticationInfo的第二個參數設爲和自定義token中返回的一樣。

4、控制器代碼如下:

/**
	 * 短信驗證碼登錄
	 * @param telphoneNum
	 * @return
	 */
	@RequestMapping(value = "codeLogin", method = RequestMethod.GET)
	@ResponseBody
	public int codeLogin(String telphoneNum) {		
		Subject subject = SecurityUtils.getSubject();
		UserNamePasswordTelphoneToken token = new UserNamePasswordTelphoneToken(telphoneNum);
		try {
			subject.login(token);
			return 1;
		}  catch (Exception e) {
			return -1;
		}
	}

5、多realm的配置,shiro的其他配置就不貼了,網上很多的。在securityManager配置屬性authenticator爲自定義的MyModularRealmAuthenicator類,配置如下:

	<!-- shiro的配置 -->
	<!-- 自定義Realm -->
	<bean id="userNamePasswordRealm" class="com.java.travel.realm.UserNamePasswordRealm" />
	<bean id="telphoneRealm" class="com.java.travel.realm.TelphoneRealm" />
	<!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<!-- 單realm的配置 -->
		<!-- <property name="realm" ref="myRealm" /> -->
		<!-- 多realm的配置 -->
		<property name="authenticator" ref="myModularRealmAuthenticator"></property>
		<property name="realms">
			<list>
				<ref bean="userNamePasswordRealm" />
				<ref bean="telphoneRealm" />
			</list>
		</property>		 
	</bean>
	<!-- 配置多個realm的時候如何認證 -->
	<bean id="myModularRealmAuthenticator" class="com.java.travel.ModularRealmAuthenticator.MyModularRealmAuthenticator">
		<property name="authenticationStrategy">
			<!-- 認證策略 -->
			<!-- <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean> -->
			<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
		</property>
	</bean>
還需要注意的一點是,在配置認證策略時要結合你的功能配置,我就是由於沒注意到這點,吃了個大虧,集中策略如下圖所示:




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章