在做微信授权登陆的时候,由于使用shiro框架进行认证登陆,没有认证授权无法建立会话。
数据库中的密码经过md5加密,通过openid查询出账号密码也无法进行授权登陆,所以需要实现Shiro免密授权登陆功能,以防后续踩坑。
1.整体思路
自定义token,加入免密标识符,在进行授权时判断是密码登陆或无密码登陆,自定义密码认证方法,即写一个方法继承HashedCredentialsMatcher,重写其中的doCredentialsMatch,将其中的token改写为自定义的token,最后将自己写的密码认证方法注入shiro
2.源码分析
在shiro的配置文件中有个配置credentialsMatcher,该方法为重写方法,自定义登陆模式,可设置密码错误锁定功能,我们需要的就是改写这个方法
1)在ShiroDbRealm中,认证使用的是UserPasswordToken
其默认使用username,password进行认证
网上有方法另写token继承UsernamePasswordToken重写getCredentials方法修改getPassword() 为 return null。此方法过于简单粗暴,顺带连密码认证也给删除了,不建议使用。
2)Shiro的密码认证在org.apache.shiro.authc.credential包下HashedCredentialsMatcher.class方法内
其中密码认证:doCredentialsMatch方法
3.免密登陆方法
1)创建一个枚举类(enum )LoginType
public enum LoginType {
PASSWORD("password"), // 密码登录
NOPASSWD("nopassword"); // 免密登录
private String code;// 状态值
private LoginType(String code) {
this.code = code;
}
public String getCode () {
return code;
}
}
2)自定义token 继承UsernamePasswordToken,通过构造方法来区分 账号密码登陆(password) 和 免密登陆(nopassword)
package com.pasic.commons.shiro;
import org.apache.shiro.authc.UsernamePasswordToken;
public class EasyTypeToken extends UsernamePasswordToken {
private static final long serialVersionUID = -2564928913725078138L;
private LoginType type;
public EasyTypeToken() {
super();
}
public EasyTypeToken(String username, String password, LoginType type, boolean rememberMe, String host) {
super(username, password, rememberMe, host);
this.type = type;
}
/**免密登录*/
public EasyTypeToken(String username) {
super(username, "", false, null);
this.type = LoginType.NOPASSWD;
}
/**账号密码登录*/
public EasyTypeToken(String username, String password) {
super(username, password, false, null);
this.type = LoginType.PASSWORD;
}
public LoginType getType() {
return type;
}
public void setType(LoginType type) {
this.type = type;
}
}
3)修改ShiroDbRealm:将UsernamePasswordToen修改为自定义token
4)找到RetryLimitCredentialsMatcher重写doCredentialsMatch,将token强转为自定义token,如果是免密登陆则直接返回 true
@Override
public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
EasyTypeToken tk = (EasyTypeToken) authcToken;
if(tk.getType().equals(LoginType.NOPASSWD)){
return true;
}
String username = (String) authcToken.getPrincipal();
//retry count + 1
AtomicInteger retryCount = passwordRetryCache.get(username);
if(retryCount == null) {
retryCount = new AtomicInteger(0);
passwordRetryCache.put(username, retryCount);
}
if(retryCount.incrementAndGet() > 5) {
//if retry count > 5 throw
logger.warn("username: " + username + " tried to login more than 5 times in period");
throw new ExcessiveAttemptsException("用户名: " + username + " 密码连续输入错误超过5次,锁定半小时!");
} else {
passwordRetryCache.put(username, retryCount);
}
boolean matches = super.doCredentialsMatch(authcToken, info);
if(matches) {
//clear retry data
passwordRetryCache.remove(username);
}
return matches;
}
就此配置完成
Login调用
1)账号密码登陆
Subject user = SecurityUtils.getSubject();
EasyTypeToken token = new EasyTypeToken(username, password);
user.login(token);
2)免密登陆
Subject user = SecurityUtils.getSubject();
EasyTypeToken token = new EasyTypeToken(username);
user.login(token);
通过不同的构造函数生成不同的LoginType,从而实现账号密码登陆与免密登陆分离,完成。