閑暇功夫想起來2年前的一場面試,當時對shiro算是剛剛入門,大佬問道怎麼在shiro基礎上增加短信随機碼認證?當時确實不知道在怎麼做,今天給大家說下如何擴充shiro.
預設大家對shiro的應用是熟悉的,我們直接看登入認證方法,前台收到使用者名,密碼後封裝到UsernamePasswordToken中,然後調用Subject.login方法,将UsernamePasswordToken傳入,經行驗證
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLx0kaNFTWq5ENNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0ETN2QzNxQTM4EjMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
原有的UsernamePasswordToken隻有name password remember host等字段,我們建立自定義的UsernamePasswordToken繼承shiro的UsernamePasswordToken 增加一個密碼類型 int passwdType 1為密碼認證.0為随機碼認證
public class UsernamePasswordToken extends org.apache.shiro.authc.UsernamePasswordToken {
//密碼類型 1為密碼 0位随機碼
private int passwdTyppe;
public int getPasswdTyppe() {
return passwdTyppe;
}
public void setPasswdTyppe(int passwdTyppe) {
this.passwdTyppe = passwdTyppe;
}
public UsernamePasswordToken(String username, String password,int passwdTyppe) {
super(username,password);
this.passwdTyppe=passwdTyppe;
}
public Object getPasswdCode() {
return this.getPasswdTyppe();
}
}
此時再登入方法中直接将name password passwdType 封裝成我們自己的UsernamePasswordToken,在自定義realm的
doGetAuthenticationInfo發中稍作改造如下,采用aes加密
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
LOG.info("開始進行密碼密碼驗證");
//效驗使用者
String userName= (String)authenticationToken.getPrincipal();
UserToken user=shiroService.getUserTokenByUserName(userName);
if(authenticationToken instanceof UsernamePasswordToken){
int passwdTyppe = ((UsernamePasswordToken) authenticationToken).getPasswdTyppe();
user.setPwdAuthType(passwdTyppe);
}
if(user==null){
LOG.error("該{}使用者不存在.禁止登陸",userName);
throw new AuthenticationException("使用者名或者密碼錯誤");
}
if(user.getExpirationDate().getTime()<new Date().getTime()){
throw new ExpiredCredentialsException("使用者密碼已經過期,請聯系中國電信集團統一賬号認證平台");
}
String plianPasswd =(String) authenticationToken.getCredentials();
if(plianPasswd==null||plianPasswd.length()<1){
LOG.error("該{}使用者輸入密碼為空.禁止登陸",userName);
throw new AuthenticationException("使用者名或者密碼錯誤");
}
//驗證使用者密碼
return new SimpleAuthenticationInfo(user, user.getPasswd(),getName());
}
由于我們還要将密碼管理器進行改造.我們繼承shiro的密碼管理器SimpleCredentialsMatcher重寫doCredentialsMatch
public class AesCredentialsMatcher extends SimpleCredentialsMatcher {
private static final Logger log= LoggerFactory.getLogger(AesCredentialsMatcher.class);
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken passwordToken = (UsernamePasswordToken) token;
String plainPasswd = String.valueOf(passwordToken.getPassword());
int pwdAuthType = passwordToken.getPasswdTyppe();
//如果是AES則進行加密驗證
if(pwdAuthType==1){
return AESUtil.getInstance().validatePassword(plainPasswd, String.valueOf(getCredentials(info)));
}else {
log.warn("========進行随機碼認證,對應随機碼為:{},傳過來得随機碼:{}",
CacheUtil.getInstanceCache(CacheGroupIdCode.GROUPID_APP_RANDOM)
.get((String)passwordToken.getPrincipal()),plainPasswd);
//如果是随機碼則直接從緩存中擷取進行對比
return CacheUtil.getInstanceCache(CacheGroupIdCode.GROUPID_APP_RANDOM)
.get((String)passwordToken.getPrincipal()).equals(plainPasswd);
}
}
}
此時運作發現直接報 ClassCastException轉型異常了,我們雖然繼承了shiro的UsernamepasswordToken,并沒有去執行個體化它,這才是引起問題的根源,問題發現了要如何解決它呢?我們在配置的使用并并未制定表單過濾器,此時預設使用的是FormAuthenticationFilter,而Token預設使用的也是UsernamePasswordToken,于是我們打開FormAuthenticationFiter源碼,找到它的所有方法發現其父類AuthenticatingFilter中有有createToken方法如下:
打開次方法的源碼如圖:
protected AuthenticationToken createToken(String username, String password, boolean rememberMe, String host) {
return new UsernamePasswordToken(username, password, rememberMe, host);
}
終于找到了,就在這裡執行個體化的UsernamePasswordToken.我們隻有重寫此方法了,建立自己的LoginAuthenticationFilter去繼承FormAuthenticationFilter
public class LoginAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected AuthenticationToken createToken(String username, String password,ServletRequest request, ServletResponse response) {
return new UsernamePasswordToken(username,password,Integer.valueOf(request.getParameter("passwdTyppe")));
}
}
将我麼自定義的過濾器交給shiro過濾器的FactoryBean管理.在啟動,我們前台傳入密碼類型為短信随機碼,此時可以正常認證通過了.至此我們shiro擴充短信随機碼認證成功了.
shiro的預設過濾器有11個之多.我們今天利用的是其中的一個,後續給大家講解其他過濾器的作用,有錯誤之處還望指正