1.前端页面登录,根据路径进入后端执行方法
2.到后端会先通过gaeway代理找到指定的服务器,通过config后,进入方法
3.然后进入判断是用户还是会员,然后进入对应的类执行方法
根据注解@Qualifier(“userAuthService”)里面的名称判断是用户还是会员
描述:
1.进入目标类后通过自定义的CustomUsernamePasswordToken类 再次验证用户还是会员并且收集相应的信息,
2.再通过SecurityUtils.getSubject()工具获取主体信息,也就是用户信息,
3.下面根据subject.login(customUsernamePasswordToken)登陆进入相应realm类认证方法里面,在认证方法里面会收集用户名,用于判断这个用户是否存在,成功后将查出整个用户的所有信息,然后再存到user实体类里面,最后加密用户信息和密码返回
4.认证成功后通过subject.getPrincipal()可以直接获取登陆用户存在实体类里面的所有信息
5.生成token,这个token其实就是一个可在整个后端存在的一个类似session一样的信息,这边是先通过用户名+随机字符串设置一个可随机变化的键,也就是可以根据这个值通过redisTemplate.opsForValue().get(accessToken)查询查token
里面的值,也就是用户信息,但是查之前要注入private RedisTemplate redisTemplate;模板,设置底层存储为字符串序列化redisTemplate.setKeySerializer(new StringRedisSerializer());才能获取token里面的用户实体类信息
6.设置底层存储为字符串序列化redisTemplate.setKeySerializer(new StringRedisSerializer());
7.存储新登录的token前,要删除以前的token,先找到所有的key键,Set keys = redisTemplate.keys(userName + “*”);
然后for循环根据键名删除所有的信息。
8.以accessToken为键以user实体类信息为值存入到redis中
9.使用获取的IP和生成的token进行加密运算,
Sha512Hash sha512Hash = new Sha512Hash(remoteIp,accessToken,10);
以accessToken+"CheckToken"为键,以加密后得到的sha512Hash为值存入redsi中
10.最后以封装工具类Result,将accessToken,也就是token的键名返回,方便根据这个键名去查用户信息,主要用作权限
4.登陆成功,前端获取token键值,然后把值存入session,因为每个前端的方法都要带上这个键值才可以访问后台方法sessionStorage.setItem(“token”,response.data.data);,然后进入主页面去查询菜单
5.前端携带accesToken到后台,先经过getway这个代理找到对应方法
6.进入service层执行真正的业务,前面controller不过是代理类
描述:
1.先注入redis模板@Resource
private RedisTemplate redisTemplate;
2. 通过设置底层存储为字符串序列化redisTemplate.setKeySerializer(new StringRedisSerializer());,获取全局通用的token,根据键值获得实体类信息对象User o = (User)redisTemplate.opsForValue().get(accessToken);
3. 根据用户id执行查询菜单的sql获取所有菜单,封装到list里面
4. 定义一个返回的结果集,用于存放数据,通过递归按级别把数据放入结果集
7.查询所有菜单级别的递归方法
描述:
1.先查出为0的一个节点
2.遍历所有的菜单,用每个值父id和这个值对比,循环结束,将所有父id和改该值对应的数据放到一个集合中,并存入这个父节点里面,直到所有父为0的查完
3.循环明显是二级菜单
private void findAndBindChild(TreeNode currentTreeNode, List<TreeNode> treeNodeList){
//再次循环查出的所有menu集合
for (TreeNode treeNode : treeNodeList) {
//判断循环节点的父节点id是否和当前节点的id相等,如果相等,说明当前循环节点是当前节点的孩子
if(currentTreeNode.getId()==treeNode.getParentId()){
//是孩子,就要添加到当前节点的孩子集合
//1,获取当前节点的孩子集合
List<TreeNode> children = currentTreeNode.getChildren();
//2,一定要判断孩子集合是否为空,如果是第一个孩子,孩子集合肯定为空,如果使用肯定空指针
if(children==null){
//3,如果为空,一定要实例化
children = new ArrayList<>();
}
// 4,根据上面判断,循环节点的父ID等于当前节点ID说明是孩子,加入
children.add(treeNode);
// 5,重新设置孩子集合
currentTreeNode.setChildren(children);
// 为了提高效率,每找到一个孩子,就从集合中删除,再次查找集合会变小,效率会变高 理论上可行,但是ArrayList不允许一边遍历一边删除元素
// treeNodeList.remove(treeNode);
//6,递归自己调用自己,再次判断当前循环有没有孩子,如果有执行相同操作
findAndBindChild(treeNode,treeNodeList);
}
}
}
8.shiro用到的几个类
1.判断是单realm还是双realm
配合realm里面的自定义认证器使用
public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
// 判断getRealms()是否返回为空
assertRealmsConfigured();
// 强制转换回自定义的CustomizedToken 多态 向下转型
CustomUsernamePasswordToken customizedToken = (CustomUsernamePasswordToken) authenticationToken;
// 登录类型
String loginType = customizedToken.getLoginType();
// 所有Realm
Collection<Realm> realms = getRealms();
System.out.println(realms+"..realm对象..");
// 登录类型对应的所有Realm
Collection<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
if (realm.getName().contains(loginType)){
typeRealms.add(realm);
}
}
// 判断是单Realm还是多Realm
if (typeRealms.size() == 1){
//单realm认证
return doSingleRealmAuthentication(typeRealms.iterator().next(), customizedToken);
}
else{
//多realm认证
return doMultiRealmAuthentication(typeRealms, customizedToken);
}
}
2.区分用户还是会员,单双realm认证和登陆前收集信息和验证都有用到
public class CustomUsernamePasswordToken extends UsernamePasswordToken {
private String loginType;
/**
* 自定CustomUsernamePasswordToken 区分是用户还是会员
* @param username
* @param password
* @param loginType
*/
public CustomUsernamePasswordToken(String username, String password, String loginType) {
super(username, password);
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}