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>
還需要注意的一點是,在配置認證策略時要結合你的功能配置,我就是由於沒注意到這點,吃了個大虧,集中策略如下圖所示: