9.fir项目中使用
1.管理员登录
1.AdminUserController
/**
* 管理员登入
* @param httpServletRequest
* @param string 包含用户名(username)和 密码(password) 的 字符串
* @return
*/
@PostMapping({ "/login" })
public Object login( HttpServletRequest httpServletRequest, @RequestBody String string) {
String username = JacksonUtil.parseString(string, "username");
String password = JacksonUtil.parseString(string, "password");
if (StringUtils.isEmpty(JacksonUtil.parseString(string, "code")) || StringUtils.isEmpty(username) || StringUtils.isEmpty(string3)) {
return ResponseUtil.badArgument();
}
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));//进行登录认证 ,进入到UserAuthorizingRealm中的doGetAuthenticationInfo方法
}
catch (UnknownAccountException ex) {
return ResponseUtil.fail(ResponseCodeEnum.BADREQUEST.getCode(), ex.getMessage());
}
catch (LockedAccountException ex2) {
return ResponseUtil.fail(ResponseCodeEnum.USERLOCKED.getCode(), ex2.getMessage());
}
catch (AuthenticationException ex3) {
return ResponseUtil.fail(ResponseCodeEnum.BADREQUEST.getCode(), ex3.getMessage());
}
AdminUser adminUser;
(adminUser = (AdminUser)subject.getPrincipal()).setLastlogintime(new Date());
adminUser.setLoginfailed(Integer.valueOf(0));
adminUser.setStatus(Integer.valueOf(AdminUserStatusEnum.NORMAL.getCode()));
this.userService.updateById( adminUser);
subject.getSession().setTimeout((this.sessiontTimeout * 1000));
return ResponseUtil.ok(subject.getSession().getId());
}
2.UserAuthorizingRealm
public class extends AuthorizingRealm
{
private Logger log;
@Autowired
private AdminUserService adminUserService;
@Value("${login.max-fail-times}")
public int MAX_FAIL_TIMES;
@Value("${login.lock-time}")
private int LOCK_TIME;
public UserAuthorizingRealm() {
this.log = LoggerFactory.getLogger(this.getClass());
this.MAX_FAIL_TIMES = 5;
this.LOCK_TIME = 1800;
}
//权限校验
protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection collection) {
if (collection == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
AdminUser adminUser = (AdminUser)collection.getPrimaryPrincipal();
Set queryRolesByUser = this.adminUserService.queryRolesByUser(adminUser);//查询该用户的角色信息
Set queryPermissionsByUser = this.adminUserService.queryPermissionsByUser(adminUser);//查询该用户的权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo;
(simpleAuthorizationInfo = new SimpleAuthorizationInfo()).setRoles(queryRolesByUser);//设置角色
simpleAuthorizationInfo.setStringPermissions(queryPermissionsByUser);//设置权限
return simpleAuthorizationInfo;//如果权限存在,则正常访问接口。否则,将抛出异常信息”无权限”
}
//身份认证 ,验证用户输入的账号和密码是否正确。
protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken;
String username = (usernamePasswordToken = (UsernamePasswordToken)authenticationToken).getUsername();
String s = new String(usernamePasswordToken.getPassword());
if (StringUtils.isEmpty(username)) {
throw new AccountException("用户名不能为空");
}
if (StringUtils.isEmpty(s)) {
throw new AccountException("密码不能为空");
}
Optional byUsername;
if (!(byUsername = this.adminUserService.findByUsername(username)).isPresent()) {
throw new UnknownAccountException("用户名不存在");
}
AdminUser adminUser = (AdminUser) byUsername.get();
if (this.userStopped(adminUser)) {
throw new LockedAccountException("用户帐号被禁用");
}
if (this.userLocked(adminUser)) {
throw new LockedAccountException("用户帐号处于锁定中");
} //MD5加盐的方式来验证密码是否正确,在外面做密码比对,在最后返回的认证信息中,密码设置为页面进来的密码,盐为空,自己比自己必然通过。
if (!new SimpleHash("md5", s, adminUser.getSalt(), 1).toHex().equals(adminUser.getPwd())) {
int n = (adminUser.getLoginfailed() == null) ? 0 : adminUser.getLoginfailed();
int max = Math.max(this.MAX_FAIL_TIMES - n - 1, 0);
adminUser.setLastfailedtime(new Date());
adminUser.setLoginfailed(Integer.valueOf(n + 1)); //如果错误次数<=0,则锁定账号
if (max <= 0) {
adminUser.setStatus(Integer.valueOf(AdminUserStatusEnum.LOCK.getCode()));
}
this.adminUserService.updateById( adminUser);
throw new AuthenticationException((max > 0) ? ("密码错误, 还剩" + max + "次") : "密码错误,账号被锁定");
}
AdminOrganization organization;
if ((organization = adminUser.getOrganization()) != null) {
organization.getCode();
}
AdminUser creator;
if ((creator = adminUser.getCreator()) != null) {
creator.getId();
}
AdminUser editor;
if ((editor = adminUser.getEditor()) != null) {
editor.getId();
}
List roles;
if ((roles = adminUser.getRoles()) != null) {
roles.size();
} //进行身份和密码验证,验证成功进入首页,失败则返回登录页面
return new SimpleAuthenticationInfo(adminUser, s, this.getName());
}
private boolean userStopped( AdminUser adminUser) {
return adminUser.getStatus() == AdminUserStatusEnum.STOP.getCode();
}
private boolean userLocked( AdminUser adminUser) {
boolean b = adminUser.getStatus() == AdminUserStatusEnum.LOCK.getCode();
boolean b2 = System.currentTimeMillis() - ((adminUser.getLastfailedtime() == null) ? 0L : adminUser.getLastfailedtime().getTime()) < this.LOCK_TIME * 1000;
return b && b2;
}
}
具体流程请查看第3章用户认证和第4章用户授权。
2.用户登录
登录流程和授权流程和管理员登录流程大概一致,用户登录做了登录用户的唯一性,也就是保证了同一个用户的session唯一性,根据每次登录成功为存入的session记录判断当前登录用户是否唯一并作出删除操作。如下所示。
3.会话管理
在登录时进入到doGetAuthenticationInfo方法中,在最后返回的return new SimpleAuthenticationInfo(adminUser, s, this.getName())认证凭证中进行用户的信息的认证(密码匹配),,如果认证成功会把(PRINCIPALS_SESSION_KEY,principal)、(AUTHENTICATED_SESSION_KEY)赋值到session中,然后将shiro:session:sessionId(随机生成)为key,session为值保存在redis中。会话时间默认存储30分钟,项目中设置的是24小时。具体流程请查看第5章会话管理。
4.缓存管理
在第一次进行用户认证和用户授权的请求时,首先会判断用户的认证缓存和权限缓存是否开启,如果开启会通过CacheManager获取cacahe,通过key获得对应的缓存数据,如果缓存数据不为空则直接返回查询到的缓存数据,如果查询到的数据为null则通过Realm会执行doGetAuthorizationInfo()方法和doGetAuthenticationInfo方法,操作数据库查询相关的认证信息和权限去获取用户信息。判断全局缓存和用户的认证缓存或者权限缓存是否开启,如果通过缓存管理器会把每次请求的认证或者是权限的缓存信息存入redis中。当再次发起相同的请求时不会走数据库,而是通过缓存管理器读取redis中的数据。这是缓存的一个基本流程。具体流程请查看第6章缓存管理。
AdminRemoveCacheUtils是一个移除用户权限信息的工具类,主要应用于管理员信息的编辑,编辑角色功能,删除角色功能,修改研究员角色信息,删除研究员角色信息,修改研究员角色信息。
public class AdminRemoveCacheUtils
{
private static String PERMISSION_CACHE_PREFIX = "shiro:cache:com.ikw.fir.admin.util.shiro.UserAuthorizingRealm.authorizationCache:";
//根据多个用户id删除redsi中的用户权限缓存,主要应用于编辑角色功能,删除角色功能
public static int removePermissionCacheByUids( StringRedisTemplate stringRedisTemplate, List<Long> list) {
if (CollectionUtils.isEmpty(list)) {
return 0;
}
HashSet<String> set = new HashSet<String>();
Iterator<Long> iterator = list.iterator();
while (iterator.hasNext()) {
Long n;
if ((n = iterator.next()) != null) {
set.add(PERMISSION_CACHE_PREFIX + n);//拼接redis中多个用户的权限缓存key
}
}
return stringRedisTemplate.delete(set).intValue();
}
//根据修改的管理员id删除redis中的用户权限缓存
public static boolean removePermissionCacheByUid( StringRedisTemplate stringRedisTemplate, Long id) {
if (id == null) {
return false;
}
String str = PERMISSION_CACHE_PREFIX + id;
return stringRedisTemplate.delete(str).booleanValue();
}
fir项目中的具体应用如下所示:
更改管理员信息
角色列表中编辑角色功能
在访问以上接口是,之所以删除用户缓存是因为ShiroFilterFactoryBean是在系统启动的时候初始化的,它的角色列表在系统启动的时候就已经写到shiro里面去了,我在后面给角色配置的权限虽然已经写进数据库了,但是shiro不会重新读取。只有删除相关用户的权限缓存才会再次查询数据库。
5.管理员和用户的登出
执行登出后,会通过SessionDao中的delete方法删除redis中的会话缓存和用户认证和权限缓存,先删除认证和权限缓存再删除会话缓存。具体流程请查看第7章用户登出。