目录
所做项目与第三方合作,系统间存在接口调用,需要做授权登录。我们的项目整体使用SSM框架,认证登陆采用了shiro框架,密码在数据库中经过盐值(salt)+Md5加密,外部无法获知密码明文,导致无法验证通过,所以想到了免密登录的方式解决。
在网上查阅了一些贴子,套路基本一样,照搬了全部代码,发现在强转AuthenticationToken为自定义Token时报错,父类无法强转为子类。经过研究源码,只需要再多复写一个类就可以解决。具体过程如下:
1.千篇一律
1)创建枚举类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类CustomeToken,继承UsernamePasswordToken类,通过构造方法区分密码登录和免密登录。
/**
* 自定义token 继承UsernamePasswordToken,
* 账号密码登陆(password) 和 免密登陆(nopassword)
*/
public class CustomeToken extends UsernamePasswordToken {
private static final long serialVersionUID = -2564928913725078138L;
private LoginType type;
public CustomeToken() {
super();
}
public CustomeToken(String username, String password, LoginType type, boolean rememberMe, String host) {
super(username, password, rememberMe, host);
this.type = type;
}
/**
* 免密登录
*/
public CustomeToken(String username) {
super(username, "", false, null);
this.type = LoginType.NOPASSWD;
}
/**
* 账号密码登录
*/
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)修改自定义的UserRealm,这个类继承了AuthorizingRealm类。将UsernamePasswordToken usertoken = (UsernamePasswordToken)token;修改为CostomToken usertoken = (CostomToken)token;
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
String loginId = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
//增加免密登录功能,使用自定义token
CostomToken token = (CostomToken) authcToken;
String loginId = (String)token.getPrincipal();
User user = userService.findUserByLoginId(loginId);
if(user == null) {
throw new UnknownAccountException();
}
if(user.getState() != 0) {
throw new LockedAccountException();
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getLoginId(),
user.getPassword(),
ByteSource.Util.bytes(user.getLoginId() + user.getSalt()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
// return null;
}
}
4)自定义CostomCredentialsMatch类继承HashedCredentialsMatcher类,覆写其中的doCredentialsMatch方法,将token强转为自定义token,若loginType是免密登录,则直接返回true,否则执行父类比对。
public class CustomCredentialsMatch extends HashedCredentialsMatcher {
private Cache<String, AtomicInteger> passwordRetryCache;
public CustomCredentialsMatch (CacheManager cacheManager) {
this.passwordRetryCache = cacheManager.getCache("passwordRetryCache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//增加免密登录功能,使用自定义token
CustomToken usertoken = (CustomToken) token;
//免密登录,不验证密码
if (LoginType.NOPASSWD.equals(usertoken.getType())){
return true;
}
String loginID = usertoken.getUsername();
AtomicInteger retryTimes = passwordRetryCache.get(loginID);
if (retryTimes == null) {
retryTimes = new AtomicInteger(0);
passwordRetryCache.put(loginID, retryTimes);
}
if (retryTimes.incrementAndGet() > 5) {
throw new ExcessiveAttemptsException();
}
boolean matches = super.doCredentialsMatch(token, info);
if (matches)
passwordRetryCache.remove(loginID);
return matches;
}
}
5)LoginContoller调用
- 密码登录
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
subject.login(token);
- 免密登录
Subject subject = SecurityUtils.getSubject();
//增加免密登录功能,使用自定义token
CustomToken token = new CustomToken(username);
subject.login(token);
6)SSM框架下的配置文件spring/spring-client-shiro.xml的部分调整
<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>
到这为止,网上的贴子基本如此,有人可能成功实现免密登录了,但是我没有成功,在认证的时候报强转失败,那么我们来看接下来怎么做。
2.点睛之笔
1)分析源码
shiro集成到springMVC中的方法,大家可以去百度中查阅,其中几个关键点这里提一下。
1.1)web.xml中配置拦截器
1.2)spring/spring-client-shiro.xml中配置各种过滤器
1.3)看FormAuthenticationFilter类源码
2)覆写FormAuthenticationFilter类的父类AuthenticatingFilter中的createToken方法
- 原写法
- 新方法
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected AuthenticationToken createToken(String username, String password,
boolean rememberMe, String host) {
//增加免密登录功能,使用自定义token
return new CustomToken(username, password, LoginType.PASSWORD, rememberMe, host);
}
}
3)配置文件修改
<!--增加免密登录功能,使用自定义token-->
<!--<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">-->
<bean id="formAuthenticationFilter" class="com.xxfamly.service.security.filter.CustomFormAuthenticationFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="rememberMeParam" value="rememberMe"/>
</bean>
至此,彻底完成了免密登录的改造。