前言
在之前我们已经对于Shiro这个安全管理框架有了一定的认识,同时也已经在SSM框架环境下对于Shiro进行了配置,下面我们来看看如何具体使用Shiro进行开发。
SSM框架
这里简单的说一下,因为这里使用的是Spring+Spring MVC+Mybatis框架,我是之前就搭好的。可能有些同学喜欢用Spring Boot来整合,这也是没有问题的,后面有空会把Spring Boot的整合放上来。回到SSM框架上,这个应该算是java中比较经典的一个框架组合,会的同学不用说自己可以搭建,如果有不太清楚SSM的后面准备再写一篇SSM框架的,大家一起学习一下。
下面是我项目的总体结构
还是中规中矩的Web层-Serivce层-Dao层,spring配置文件我根据不同的功能分开放置这样更加清晰。
然后是Shiro的两个文件,一个是自定义的Realm,用于验证用户和授予权限的;另一个是进行加密的。
开发中先写一个注册的功能用于添加用户,简单一点的话就是需要用户名,密码(密码这里用MD5+盐)。这两点是基本的。
下面是代码的一个片段,这里我是通过UUID生成了一个盐对原密码加密,然后就用到我们之前的加密用的文件
User user = new User();
String salt = UUIDUtil.createUUID().toString();
Date createTime = new Date();
Integer state = 0;
user.setUserName(userName);
user.setPassword(password);
user.setSalt(salt);
user.setCreateTime(createTime);
user.setState(state);
User newUser = passWordHelper.encryptPassword(user);
@Component
public class PassWordHelper {
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
@Value("MD5")
private String algorithmName;
@Value("2")
private int hashIterations;
public void setRandomNumberGenerator(RandomNumberGenerator randomNumberGenerator) {
this.randomNumberGenerator = randomNumberGenerator;
}
public void setAlgorithmName(String algorithmName) {
this.algorithmName = algorithmName;
}
public void setHashIterations(int hashIterations) {
this.hashIterations = hashIterations;
}
public User encryptPassword(User user){
if(user.getSalt() == null || "".equals(user.getSalt())){
user.setSalt(randomNumberGenerator.nextBytes().toHex());
}
String newPassword = new SimpleHash(algorithmName,user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),hashIterations).toHex();
user.setPassword(newPassword);
return user;
}
}
这里引用了加密方式和加密次数两个参数,然后通过SimpleHash进行加密,得到一个加密后的密码放入user对象中,然后在Serivce层中将新的user传入Dao层添加到数据库。ok,现在我们已经有一个用户了,你也可以用别的方法,只要保证先添加一个用户进来就对了。
下面来看一下Realm文件
public class myRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
/**
* @description 为当前登录的用户授予角色和权限
* @author zhou
* @created 2018/10/17 15:15
* @param
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户名
String userName = (String)principalCollection.getPrimaryPrincipal();
Session session = SecurityUtils.getSubject().getSession();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//查询用户的role
Set<String> roles = roleService.findRoleByUserName(userName);
authorizationInfo.setRoles(roles);
//查询用户的permission
Set<String> permissions = permissionService.findPermissionByUserName(userName);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
/**
* @description 验证当前登录的用户
* @author zhou
* @created 2018/10/17 15:16
* @param
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws
AuthenticationException {
//获得用户信息
String userName = (String)authenticationToken.getPrincipal();
//从数据库中查找
User user = userService.findByUserName(userName);
if(user==null){
//账号不存在
throw new UnknownAccountException();
}else if(user.getState() == 1){
//账号锁定
throw new LockedAccountException();
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUserName(),
user.getPassword(), ByteSource.Util.bytes(user.getSalt()),getName());
return simpleAuthenticationInfo;
}
}
首先新建一个自定义的Realm,然后继承AuthorizingRealm,重写其中的doGetAuthorizationInfo和doGetAuthenticationInfo两个方法。
首先看一下授权的方法,这里是通过用户名去我们的数据库中查询该用户的角色和权限,这里用set集合去存放角色和权限。然后把这两个信息存到authorizationInfo中,这样在用户被授权后,如果用户要执行什么操作,可以从中获取数据来判断用户是否具有对应的权限。
然后是登录验证的方法,这个和平时写的javaWeb的验证方式比较类似,首先获得用户名去数据库中查找用户信息,然后做一些简单的校验比如是否存在用户和用户是否被锁定等等。然后将用户名,密码,盐和Realm的名称。然后在登录的时候会用到这些。下面是我的登录代码
@RequestMapping(value = "/login")
public WebResponse userLogin(@RequestParam("userName") String userName,@RequestParam("password") String password,
@RequestParam(value = "rememberMe",defaultValue = "false") boolean rememberMe,
HttpServletRequest request){
if(isEmpty(userName)||isEmpty(password)){
log.error("用户名或密码为空");
return new WebResponse().error(401,null,"用户名或密码为空");
}
//查询角色
Set<String> roleList = roleService.findRoleByUserName(userName);
//查询权限
Set<String> permissionList = permissionService.findPermissionByUserName(userName);
//获得主体
Subject currentUser = SecurityUtils.getSubject();
//判断用户是否登陆
if(!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try {
//token.setRememberMe(rememberMe);
//存入参数
request.getSession().setAttribute("token",userName);
request.getSession().setAttribute("role",roleList);
request.getSession().setAttribute("permission",permissionList);
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.error("账号不存在");
return new WebResponse().error(402, null, "账号不存在");
} catch (IncorrectCredentialsException ice) {
log.error("密码错误,请重试");
return new WebResponse().error(403, null, "密码错误,请重试");
} catch (LockedAccountException lae) {
log.error("该账号已被禁用,无法登陆");
return new WebResponse().error(404, null, "该账号已被禁用,无法登陆");
} catch (AuthenticationException ae) {
log.error("未知错误");
return new WebResponse().error(405, null, "未知错误");
}
}
HashMap<String,Object> userMap = new HashMap<>();
userMap.put("msg",userName + "登陆成功");
userMap.put("role",roleList);
userMap.put("permission",permissionList);
return new WebResponse().ok(userMap);
}
这里主要看这个login()方法,当程序判定该用户未被认证时,将用户名和密码放入Token中然后调用login,此时就会用到之前realm中用户验证的方法,将用户输入的信息和数据库中查到的信息进行比对校验。
后面是一些捕获异常比如账号不存在,密码错误,账号锁定等等。
以上就是Shiro权限登录的一个流程和整合过程中所需要的文件。
不过就个人觉得由于Shiro框架封装程度比较高,感觉看似随意调用了几个方法就实现了验证和授权,其实底层做的工作和javaWeb中做的是类似的。后面有机会会从源码的角度再次剖析一下Shiro的Realm的工作原理。