一般用户是有密码的,有时候为了简便所以就给一些用户没设置密码了,导致数据库中他们那一批为null值,但此时我们又需要用Shiro验证登录,
思路就是判断用户输入密码或者没输入密码用啥验证方式,如果没输入密码过来,我们就伪造一个假密码登录成功,实际上数据库并不存在那个假密码。
前期准备工作首先你得先引入shiro的依赖,
控制层org.apache.shiro shiro-spring 1.5.3
思路大部分都写注解里了,核心步骤就是subject.login(…)然后触发我们下面自定义写的验证类CustomRealm,然后确保验证无误
@RequestMapping("/login")
public Result login(@RequestBody User user) {
Map obj = new HashMap<>();
Boolean success;
String returnCode;
String message = "";
if (user.getPassword().isEmpty()){ //判断用户是否有无输入密码
User userByName = userService.getUserByName(user.getUsername()); //若没输入密码,则去数据库查询该用户是否存在
if (org.springframework.util.StringUtils.isEmpty(userByName)){ //判断是否是用户名输错,密码没输入的情况
success = Constant.SUCCESS_FALSE;
returnCode = Constant.STATUS_CODE_500;
message = "账号或密码错误!";
return Result.build(success, returnCode, message);
}else{
if (StringUtils.isEmpty(userByName.getPassword())){ //判断该用户是否是真的数据库里面没密码还是有密码
// 进行免密登录
Subject subject = SecurityUtils.getSubject();
String password = "无需密码直接登录";
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUsername(), password);
//此处会调用我们自己写的验证类里面的doGetAuthenticationInfo方法进行判断是否校验成功
subject.login(usernamePasswordToken );
success = Constant.SUCCESS_TRUE;
returnCode = Constant.STATUS_CODE_200;
message = "登录成功";
//登录成功---可以存一些基本信息,这里我省略我的一些存入基本信息的操作,例如存入的token
return Result.build(success, returnCode, obj, message);
}else{
success = Constant.SUCCESS_FALSE;
returnCode = Constant.STATUS_CODE_500;
message = "账号或密码错误!";
return Result.build(success, returnCode, message);
}
}
}else{
//添加用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUsername(),
user.getPassword()
);
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
success = Constant.SUCCESS_TRUE;
returnCode = Constant.STATUS_CODE_200;
message = "登录成功";
} catch (AuthenticationException e) {
// e.printStackTrace();
success = Constant.SUCCESS_FALSE;
returnCode = Constant.STATUS_CODE_500;
message = "账号或密码错误!";
return Result.build(success, returnCode, message);
} catch (AuthorizationException e) {
success = Constant.SUCCESS_FALSE;
returnCode = Constant.STATUS_CODE_500;
//e.printStackTrace();
message = "没有权限";
}
//登录成功--可以存一些基本信息,这里我省略我的一些存入基本信息的操作,例如存入的token
return Result.build(success, returnCode, obj, message);
}
}
准备创建一个ShiroConfig配置类
//这里代码过多,就挑几个关键点的,注意此处不全!!!这里代码过多,就挑几个关键点的,注意此处不全!!!这里代码过多,就挑几个关键点的,注意此处不全!!!
@Bean
public CredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(MD5.hashAlgorithmName);// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(MD5.hashIterations);// 散列的次数,比如散列两次,相当于
// md5(md5(""));
return hashedCredentialsMatcher;
}
public DefaultHeaderSessionManager SessionManager() {
DefaultHeaderSessionManager sessionManager = new DefaultHeaderSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setGlobalSessionTimeout(1000*60*60*24);//单位:毫秒 秒 分 小时 天
return sessionManager;
}
//将自己的验证方式加入容器************此处比较重要
@Bean
public CustomRealm myShiroRealm() {
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
//权限管理,配置主要是Realm的管理认证
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(SessionManager());
return securityManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map map = new HashMap<>();
//登出
map.put("/logout", "logout");
//对所有用户认证
// map.put("/**", "authc");
map.put("/**", "anon");
//登录
shiroFilterFactoryBean.setLoginUrl("/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//这里代码过多,就挑几个关键点的,注意此处不全!!!这里代码过多,就挑几个关键点的,注意此处不全!!!这里代码过多,就挑几个关键点的,注意此处不全!!!
准备创建自己的Shiro登录验证方式,创建一个CustomRealm类,
public class CustomRealm extends AuthorizingRealm {
@Reference
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户信息(登录验证成功时存入的)
User user = (User) principalCollection.getPrimaryPrincipal();
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Role role : user.getRoles()) {
//添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
}
for (Auth auth : user.getAuths()) {
simpleAuthorizationInfo.addStringPermission(auth.getPath());
}
return simpleAuthorizationInfo;
}
//无密码登录的核心是这块**********************************************************************
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//加这一步的目的是在Post请求的时候会先进认证,然后在到请求
if (authenticationToken.getPrincipal() == null) {
return null;
}
// 1. 把AuthenticationToken转换为UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
// 2. 从UsernamePasswordToken中获取staffId
String name = upToken.getUsername();
// System.out.println("密码"+upToken.getPassword());
//查看UsernamePasswordToken可知,getCredentials()方法的返回值是char []类型的,所以不能直接转化成string。
char [] ch = (char[]) authenticationToken.getCredentials();
//接收输入的密码
String password = new String(ch);
// 3. 若用户不存在,抛出UnknownAccountException异常
User user= userService.getUserByName(name);
if (user == null) {
throw new UnknownAccountException("用户不存在!");
}
// 根据用户的情况,来构建AuthenticationInfo对象并返回,通常使用的实现类为SimpleAuthenticationInfo
// 判断是否是免密登录。
// 在控制器里,正常的登录(绑定)逻辑是用户输入工号和密码,直接登录是直接把密码赋值为:无需密码直接登录
if ("无需密码直接登录".equals(password)){
//MD5 10次加密后
String passwordMD5 = "3b34283253b91a7d7ad1713fafde85cc";
String realmName = getName();
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, passwordMD5, null,realmName);
return simpleAuthenticationInfo;
}else {
User userInfo = userService.getAuth(user.getUserId());
ByteSource salt = ByteSource.Util.bytes(userInfo.getUserId());
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
userInfo,
user.getPassword(),
salt,
getName());
return simpleAuthenticationInfo;
}
}
}
注意上面那个字符串passwordMD5 是用“无需密码直接登录”进行MD510次加密后的结果,推荐创一个工具类MD5,代码如下
public class MD5 {
//加密方式
public final static String hashAlgorithmName = "MD5";
//加密次数
public final static int hashIterations = 10;
//带盐值加密
public static final String md5(String password, String salt) {
//盐:为了即使相同的密码不同的盐加密后的结果也不同
ByteSource byteSalt = ByteSource.Util.bytes(salt);
//密码
Object source = password;
SimpleHash result = new SimpleHash(hashAlgorithmName, source, byteSalt, hashIterations);
return result.toString();
}
//单纯密码加密
public static final String md52(String password) {
//密码
Object source = password;
SimpleHash result = new SimpleHash(hashAlgorithmName, source, null, hashIterations);
return result.toString();
}
public static void main(String[] args) {
String pwd = "无需密码直接登录";//加密前密码
String password = md52(pwd);
System.out.println(password);
}
}
总结
为了让大家能更看明白些,以上代码有所删减,如有不懂可以留言,看到即回



