在这之前,Shiro的具体验证过程 链接:
https://blog.csdn.net/qq_37767455/article/details/99771312
和
https://blog.csdn.net/qq_37767455/article/details/91351791
下面完整代码地址:
https://gitee.com/never_enough/jxc_system
t_user:
t_role:
t_user_role:
管理员role_id 为1,管理员拥有最高权限,即所有菜单,下面是t_role_menu:
各表之间关联:
若当前登录用户拥有一个角色,那么登录成功直接跳到主界面,否则让先让用户选择一个角色,然后再进入主界面,当然主界面的菜单是根据选中的角色来加载的。
1.用户通过shiro验证后,也即是登录合法,然后将currentUser和用户拥有的角色存起来:
2.回到登录页面 判断角色个数
/**
* 这里获取的是一层的菜单
* 根据父节点和用户角色Id查询菜单
* @param parentId
* @param roleId
* @return
*/
public JsonArray getMenuByParentId(Integer parentId,Integer roleId) {
List<Menu> menuList=menuService.findByParentIdAndRoleId(parentId, roleId);
JsonArray jsonArray=new JsonArray();
for(Menu menu:menuList) {
JsonObject jsonObject=new JsonObject();
jsonObject.addProperty("id", menu.getId());//节点id 作为标识
jsonObject.addProperty("text", menu.getName());//节点名字
if(menu.getState()==1) {
jsonObject.addProperty("state", "closed"); //状态为1说明是根结点
}else {
jsonObject.addProperty("state", "open");//叶子结点
}
jsonObject.addProperty("iconCls", menu.getIcon());//节点图标
JsonObject attributeObject=new JsonObject(); //扩展属性
attributeObject.addProperty("url", menu.getUrl());// 菜单请求地址
jsonObject.add("attributes", attributeObject);
jsonArray.add(jsonObject);
}
return jsonArray;
}
持久层:
/**
* 根据父节点以及用户角色id查询子节点
* @param id
* @return
*/
@Query(value="SELECT * FROM t_menu WHERE p_id=?1 AND id IN (SELECT menu_id FROM t_role_menu WHERE role_id=?2)",nativeQuery=true)
public List<Menu> findByParentIdAndRoleId(int parentId,int roleId);
界面展示效果:
由于当前登录用户是tony,选的 角色是总经理,角色对应的权限不多,所以菜单就展示了这么多。
发出请求获取客户列表:
下面重点:
由于请求要访问的方法上面加上了@RequiresPermissions注解,程序要执行到下面这里:
用户登录进来 访问带shiro注解的方法时 要到这里 进行授权(给当前主体授权)
package com.java1234.realm;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import com.java1234.entity.Menu;
import com.java1234.entity.Role;
import com.java1234.entity.User;
import com.java1234.repository.MenuRepository;
import com.java1234.repository.RoleRepository;
import com.java1234.repository.UserRepository;
/**
* 自定义Realm
* @author Administrator
*
*/
public class MyRealm extends AuthorizingRealm{
@Resource
private UserRepository userRepository;
@Resource
private RoleRepository roleRepository;
@Resource
private MenuRepository menuRepository;
/**
* 授权 :是什么用户就给他什么角色 什么菜单权限
* 用户登录进来 访问带shiro注解的方法时 要到这里 进行授权(给当前主体授权)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName=(String) SecurityUtils.getSubject().getPrincipal();
User user=userRepository.findByUserName(userName);
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
List<Role> roleList=roleRepository.findByUserId(user.getId());
Set<String> roles=new HashSet<String>();
for(Role role:roleList) {
roles.add(role.getName());
List<Menu> menuList=menuRepository.findByRoleId(role.getId());
for(Menu menu:menuList) {
info.addStringPermission(menu.getName());
}
}
info.setRoles(roles);
return info;
}
/**
* 身份认证:登录的时候判断权限 是否有权限 判断用户名和密码
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName=(String) token.getPrincipal();
User user=userRepository.findByUserName(userName); //获取首体、当事人
if(user!=null) { //封装成AuthenticationInfo然后返回 它内部进行判断密码是否正确(输入的用户名对应的用户存在,继续判断该用户密码是否正确)
AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(),"xxx");
return authcInfo;
}else {
return null; //用户不存在 return Null
}
}
}
package com.java1234.config;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import com.java1234.realm.MyRealm;
/**
* Shiro配置类
*/
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是会报错的,因为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
* Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//设置登录界面 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/drawImage", "anon");//验证码生成的请求也不需要验证即可访问
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
/*
* 退出后 shiro内置currentUser 被自动清掉 但我们自己设置的currentUser还在
* 因此还需在 controller的logout方法中手动清理
*/
filterChainDefinitionMap.put("/logout", "logout");
// :这是一个坑呢,一不小心代码就不好使了;
//
filterChainDefinitionMap.put("/**", "authc");//除了上面那些,其他的请求都需要认证
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myRealm());
return securityManager;
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*
* @return
*/
@Bean //返回一个Bean
public MyRealm myRealm() {
MyRealm myRealm = new MyRealm();
return myRealm;
}
/**
* Shiro生命周期处理器 (固定写法)
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
* @return
*
*
* Controller里面有方法,方法里面要进行权限认证
* @RequiresPermissions:比如需要有自己的菜单权限,否则访问会报错
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}