利用shiro可以十分方便,有效的实现接口权限控制功能。
但是在开发中,会遇到了这样一种场景:
在微信公众号开发里,用户在公众号里输入用户名和密码绑定项目,绑定完成后,下次进入项目跳过登录 直接进入项目的首页。在这个过程中,用户第二次登录时是使用微信提供的openId直接登录,这是免密登录的。
由于shiro登录是默认需要账号密码才能生成token,进而进行登录验证功能的,所以需要改写。
注意,此时应该是密码和免密同时存在的,而不是直接粗暴的去掉密码验证。
查找了网上的资料后处理方式有2种
第一种:重写token,验证时发现为免密类型时直接返回true
整体思路:
自定义token,加入免密标识符,在进行授权时判断是密码登陆或无密码登陆,自定义密码认证方法,即写一个方法继承HashedCredentialsMatcher,重写其中的doCredentialsMatch,将其中的token改写为自定义的token,最后将自己写的密码认证方法注入shiro
1.shiro的配置文件里面有一个是<bean id="credentialsMatcher" class="xxxxx.xxxMatcher">这需要一个继承HashedCredentialsMatcher的子类,重写doCredentialsMatch方法
2.自定义一个token,继承自UsernamePasswordToken,添加一个标识符表名是否免密登录。
3.Subject subject = SecurityUtils.getSubject();
EasyTypeToken token = new EasyTypeToken(loginName);
subject.login(token);
4.回到第一步 重写方法的第一行 直接强转 token 获取标识符 如果为免密登录 直接返回true
--------来自https://blog.csdn.net/Sil_sunday/article/details/81986025?utm_source=copy
他在这篇文章里有实现代码,但是我在实验的过程中发现 AuthenticationToken不能直接转换成EasyTypeToken类型,博主可能写掉了 重写createToken部分,我自己尝试重写,但是始终报错类型转换失败,这种方式我还是没有实现。但是这个思路是可行的。
第二种:在免密登录时,在后台写一个默认的登录密码
由于第一种方法的问题迟迟没有解决,就觉得考虑其他方法,后来发现有一种非常简单的方法:
在免密登录的情况下,写一个固定的密码生成token,并将它的MD5加密后的内容放到 SimpleAuthenticationInfo里,这样就直接可以登录成功了。
实现代码如下:
这是reaml文件 doGetAuthenticationInfo方法的代码
/**
* Authentication(身份验证):简称为“登录/绑定”,即证明用户是谁。
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1. 把AuthenticationToken转换为UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 2. 从UsernamePasswordToken中获取staffId
String staffId = upToken.getUsername();
//查看UsernamePasswordToken可知,getCredentials()方法的返回值是char []类型的,所以不能直接转化成string。
char [] ch = (char[]) token.getCredentials();
//接收输入的密码
String password = new String(ch);
// 3. 若用户不存在,抛出UnknownAccountException异常
ShiroUser shiroUser = shiroUserService.selectByStaffId(staffId);
if (shiroUser == null) {
throw new UnknownAccountException("用户不存在!");
}
// 根据用户的情况,来构建AuthenticationInfo对象并返回,通常使用的实现类为SimpleAuthenticationInfo
//判断是否是免密登录。
// 在控制器里,正常的登录(绑定)逻辑是用户输入工号和密码,直接登录是直接把密码赋值为:无需密码直接登录
if ("无需密码直接登录".equals(password)){
//MD5 2次加密后
String passwordMD5 = "fde9e13a5544700bf59ca24b11ffdc81";
String realmName = getName();
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(staffId, passwordMD5, null,
realmName);
return info;
}else {
// (2)credentials:密码
Object credentials = shiroUser.getPassword();
String realmName = getName();
// (4)盐值:取用户信息中唯一的字段来生成盐值,避免由于两个用户原始密码相同,加密后的密码也相同,这里没有加盐
//ByteSource credentialsSalt = ByteSource.Util.bytes(staffId);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(staffId, credentials, null,
realmName);
return info;
}
}
这是控制器的写法
@RequestMapping
public void checkBind(HttpServletResponse response, HttpSession session) {
try {
String openId = "xxx";
session.setAttribute("openId", openId);
String staffId = userOpenIdService.selectStaffIdByOpenId(openId);
//如果已绑定就跳到langya的首页
if (staffId != null) {
//如果已绑定 进行免密登录
Subject subject = SecurityUtils.getSubject();
String password = "无需密码直接登录";
UsernamePasswordToken token = new UsernamePasswordToken(staffId, password);
token.setRememberMe(false);
subject.login(token);
session.setAttribute("staffId", staffId);
}
//如果没有绑定就跳到绑定页
} catch (IOException e) {
e.printStackTrace();
}
}
shiro配置文件里:
<!-- 6. 配置ShiroFilter -->
<!-- 6.1 id必须和web.xml中配置的DelegatingFilterProxy的<filter-name>一致。 如果不一致,会抛出NoSuchBeanDefinitionException异常,因为shiro会在IOC容器中查找名称和<filter-name>
值一致的filter bean -->
<bean id="myAuthorizationFilter" class="com.wolwo.shiro.MyAuthorizationFilter"/>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- 配置哪些页面需要受保护,以及访问这些页面需要的权限 -->
<property name="filters">
<map>
<!--<entry key="authc" value-ref="myLoginFilter"/>-->
<entry key="myAuthorizationFilter" value-ref="myAuthorizationFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<value>
<!-- 静态资源 -->
/images/** = anon
/js/** = anon
/css/**= anon
/res/** = anon
/res/** = anon
<!-- 第一次匹配优先的原则 -->
/checkBind = anon
/bind = anon
/login = anon
/user/login = anon
/logout = logout
<!--/** = authc-->
/** = myAuthorizationFilter
</value>
</property>
这样,就可以很简单的实现免密登录功能了。