做的项目与第三方OA对接,需要将待办推送至OA形成xx系统待办,点击办理跳转回我们系统进行任务办理。我们项目使用SSM框架,认证使用shiro框架,外部无法获悉密码明文,导致验证不通过,直接跳转到了登录页面,所以采用免密登录的方式解决此问题。
1、创建登录类型枚举类
/**
* @Type LoginType.java
* @Desc
* @author
* @date 2020/05/29
* @version
*/
public enum LoginType {
PASSWORD("password"), NOPASSWORD("nopassword");
private String code;
private LoginType(String password) {
this.code = code;
}
public String getCode(){
return code;
}
}
2、自定义CustomeToken,继承UsernamePasswordToken,通过构造方法区分密码登录和免密登录
/**
* @Type CustomeToken.java
* @Desc
* @author 王宇飞
* @date 2020/05/29
* @version
*/
public class CustomeToken extends UsernamePasswordToken {
private LoginType type;
public CustomeToken(String userName, String password, LoginType type, boolean rememberMe, String host) {
super(userName, password, rememberMe, host);
this.type = type;
}
/**
* 免密登录构造方法
* @param userName
*/
public CustomeToken(String userName) {
super(userName, "", false, null);
this.type = LoginType.NOPASSWORD;
}
/**
* 用户名密码登录构造方法
* @param userName
* @param password
*/
public CustomeToken(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、修改自定义ShiroDbRelam,这个类继承了AuthorizingRelam类。将UsernamePasswordToken token = (UsernamePasswordToken)authcToken;改为CustomeToken token = (CustomeToken) authcToken;
/**
* Shiro登录认证(原理:用户提交 用户名和密码 --- shiro 封装令牌 ---- realm 通过用户名将密码查询返回 ---- shiro 自动去比较查询出密码和用户输入密码是否一致---- 进行登陆控制 )
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
LOGGER.info("Shiro开始登录认证");
CustomeToken token = (CustomeToken) authcToken;
Account accountVO = new Account();
accountVO.setLoginName(token.getUsername());
List<Account> list = accountService.selectByLoginName(accountVO);
// 账号不存在
if (CollectionUtils.isEmpty(list)) {
return null;
}
Account account = list.get(0);
// 账号未启用
if (account.getStatus() == 1) {
return null;
}
// 读取用户的url和角色
Map<String, Set<String>> resourceMap = roleService.selectResourceMapByUserId(account.getId());
Set<String> urls = resourceMap.get("urls");
Set<String> roles = resourceMap.get("roles");
ShiroUser shiroUser = new ShiroUser(account.getId(), account.getLoginName(), account.getName(), urls);
shiroUser.setRoles(roles);
// 认证缓存信息
return new SimpleAuthenticationInfo(shiroUser, account.getPassword().toCharArray(),
ShiroByteSource.of(account.getSalt()), getName());
}
4、自定义RetryLimitCredentialsMatcher类继承HashedCredentialsMatcher类,覆写其中的doCredentialsMatch方法,将token强转为自定义token,若loginType是免密登录,则直接返回true,否则执行父类比对。
@Override
public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
//增加免密登录功能,使用自定义token
CustomeToken userToken = (CustomeToken) authcToken;
//如果登录类型为免密登录,不需要校验密码
if(LoginType.NOPASSWORD.equals(userToken.getType())){
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次,锁定半小时!");
}
boolean matches = super.doCredentialsMatch(authcToken, info);
if(matches) {
//clear retry data
passwordRetryCache.remove(username);
}
return matches;
}
5、LoginController调用
1)密码登录
Subject subject = SecurityUtils.getSubject();
CustomToken token = new CustomToken(username,password);
subject.login(token);
2)免密登录
Subject subject = SecurityUtils.getSubject();
//增加免密登录功能,使用自定义token
CustomToken token = new CustomToken(username);
subject.login(token);
6、调整shiro配置文件
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache/ehcache-shiro.xml"/>
</bean>
<bean id="credentialsMatcher"
class="com.xxfamly.service.security.credentials.CustomCredentialsMatch">
<constructor-arg ref="cacheManager" />
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="3" />
<property name="storedCredentialsHexEncoded" value="true" />
</bean>
<!-- Realm实现 -->
<bean id="userRealm" class="com.xxfamly.service.security.UserRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<property name="cachingEnabled" value="false"/>
</bean>
到此为止,可以成功实现免密登录了。