shiro之二(Springboot+mybatis+shiro+thymeleaf)

1、搭建
  • 对于搭建Springboot+mybatis+shiro+thymeleaf这里就不多说,首先看引用的pom文件吧
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>
<!--常用工具类 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
<!-- shiro依赖 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
<!-- thymeleaf整合shiro标签 -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
<!-- redis依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- FastJson依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.46</version>
</dependency>
2、核心配置
  • 其次对于使用shiro,要进行核心配置,代码如下
@Configuration
public class ShiroConfig {

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
     * 3、部分过滤器可指定参数,如perms,roles
     *
     *
     * anno:无需认证(登陆)可以访问
     * authc:必须认证才能访问
     * user:如果使用rememberMe的功能可以直接访问
     * perms:该资源必须得到资源权限可以访问
     *  role:该资源必须得到角色权限才能访问
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        //访问的是后端url地址为 /login的接口
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/login");
        // 拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不会被拦截的链接 顺序判断

        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/bootstrap3/**", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/layui/**", "anon");
        filterChainDefinitionMap.put("/vue/**", "anon");

        filterChainDefinitionMap.put("/dl", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/register", "anon");
        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        //filterChainDefinitionMap.put("/logout", "logout");

        //配置某个url需要某个权限码
        //filterChainDefinitionMap.put("/hello", "perms[how_are_you]");
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问;user:remember me的可以访问-->
        filterChainDefinitionMap.put("/**", "user");      // 其他路径均需要身份认证,一般位于最下面,优先级最低
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        System.out.println("Shiro拦截器工厂类注入成功");
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(myShiroRealm());
        //将缓存注入安全管理器,就不会反复执行  realm的授权方法了;只要实现了shiro的cache接口、CacheManager接口就可以用来注入安全管理器
        //shiro自带的一个内存缓存,本质是hashmap,MemoryConstrainedCacheManager(),试验没问题,非常轻,简单的登录用这个
        //securityManager.setCacheManager(new MemoryConstrainedCacheManager());
        return securityManager;
    }
    /**
     * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
     * @return
     */
    @Bean
    public LoginShiroReaml myShiroRealm() {
        LoginShiroReaml loginShiroReaml=new LoginShiroReaml();
        loginShiroReaml.setCredentialsMatcher(hashedCredentialsMatcher());
        return loginShiroReaml;
    }

    //设置默认加密方式
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 使用md5 算法进行加密
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 设置散列次数: 意为加密几次
        hashedCredentialsMatcher.setHashIterations(3);
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }

    /**
     * cookie管理对象;
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){

        System.out.println("ShiroConfiguration.rememberMeManager()");
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        // cookieRememberMeManager.setCipherKey(org.apache.shiro.codec.Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }

    @Bean
    public SimpleCookie rememberMeCookie(){
        System.out.println("ShiroConfiguration.rememberMeCookie()");
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(259200);
        simpleCookie.setHttpOnly(true);
        return simpleCookie;
    }

    /**
     * Shiro生命周期处理器
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * @return
     */
    @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;
    }

    //配置ShiroDialect:用于thymeleaf和shiro标签配合使用
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }

}
3、登录页面
  • 登录页面的html文件就不展示了,以下是登录的js
function loginAjax() {
    $.post("/dl", {
        loginname : $('#loginname').val(),
        loginpass : $('#loginpass').val()
    }, function(data) {
        if (data == "0") {
            layer.msg('登录失败,账号已被注销!', {
                icon : 2,
                time : 2000
            });
        } else if (data == "1") {
            layer.msg('登录失败,账号已被锁定,请联系管理员!', {
                icon : 2,
                time : 2000
            });
        } else if (data == "2") {
            window.location.replace(learn + "/index");
        } else {
            layer.msg('登录失败,请重新登录!', {
                icon : 2,
                time : 2000
            });
        }
    });
}
4、登录的控制层
  • 登录的控制层(LoginController.java),@SystemLog为系统日志的注解,此注解为自定义,如果没有日志可省略,此注解在最后会说明
// 用户登录
@SystemLog(log= "用户登录")
@RequestMapping("/dl")
@ResponseBody
public String dluser(@RequestParam("loginname") String loginname, @RequestParam("loginpass") String loginpass,
                     HttpServletRequest request) {
    String flag="10";
    //LearnLogin loginuser=loginService.findLogin(loginname,loginpass);//登录人传入账号和密码,并返回登录人信息
    //主体,当前状态为没有认证的状态“未认证”
    Subject subject = SecurityUtils.getSubject();
    // 登录后存放进shiro token
    UsernamePasswordToken token=new UsernamePasswordToken(loginname,loginpass);
    try {
        subject.login(token);
        LearnLogin loginuser = (LearnLogin)subject.getPrincipal();
        //subject.hasRole(loginname);//用于触发realm中的授权doGetAuthorizationInfo()方法
        if(loginuser!=null&&!"".equals(loginuser)&&loginuser.getLoginid()!=null&&!"".equals(loginuser.getLoginid())){
            if("2".equals(loginuser.getLogintype())){
                List<LearnRole> listrole = systemService.findLoginRole(loginuser.getLoginid());//查找该登录人的权限
                List<LearnMenu> listmenu = systemService.findLoginAllMenu(loginuser.getLoginid());//查找该登录人的以及菜单目录 顶级菜单系统默认id为0
                loginuser.setListRole(listrole);
                loginuser.setListMenu(listmenu);
                request.getSession().setAttribute(Common.LOGIN_USER, loginuser);
                flag="2";//登录成功 并将登录人信息存入session
            }else if("1".equals(loginuser.getLogintype())){
                flag="1";//登录失败  用户被锁定
            }else if("0".equals(loginuser.getLogintype())){
                flag="0";//登录失败  用户被注销
            }else{
                flag="3";//登录失败
            }
        }else{
            flag="3";//登录失败
        }
    }catch (UnknownAccountException ex) {
        flag="3";//登录失败
    }catch (Exception ex){
        flag="3";//登录失败
    }
    return flag;
}

// 登录成功后跳转主页面
@RequestMapping("/index")
@SystemLog(log= "登录成功")
public String index(Model model, HttpServletRequest request) {
    // 取身份信息
    LearnLogin loginuser = (LearnLogin) request.getSession().getAttribute(Common.LOGIN_USER);
    // 根据用户id取出菜单
    //List<SysMenu> menus = menuService.selectMenusByUser(user);
    request.getSession().setAttribute("menus", loginuser.getListMenu());
    //model.addAttribute("menus", loginuser.getListMenu());
    model.addAttribute("user", loginuser);
    return "index";
}
5、这里是重点
  • 如果运用shiro的话,需要继承AuthorizingRealm,重写里面的方法,代码如下
@Service("loginShiroReaml")
public class LoginShiroReaml extends AuthorizingRealm {
    
    @Resource(name = "loginService")
    private LoginService loginService;

    @Resource(name = "systemService")
    private SystemService systemService;
    
    /**
     * 用户登录进行认证
     *
     * 1.和授权方法一样,AuthenticatingRealm的getAuthenticationInfo,先判断缓存是否有认证信息,没有就调用
     * 但试验,登录之后,再次登录,发现还是调用了认证方法,说明第一次认证登录时,没有将认证信息存到缓存中。不像授权信息,
     * 将缓存注入安全管理器,就自动保存了授权信息。 难道无法 防止故意多次登录 ,按理说不应该啊?
     * 2  可以在登录controller简单用session是否有key 判断是否登录?
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("进来验证了");
        //验证账号密码
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        //实际上这里在查询数据库是直接根据token.getUsername()来进行查询,从而获取user里面的password
        LearnLogin loginuser = loginService.findLogin(token.getUsername());
        if(loginuser==null){
            return null;
        }

        //盐值
        ByteSource credentialsSalt = ByteSource.Util.bytes("Storm");
        //封装用户信息,构建AuthenticationInfo对象并返回
        AuthenticationInfo info = new SimpleAuthenticationInfo(loginuser, loginuser.getLoginpass(),
                credentialsSalt, this.getClass().getSimpleName());

        return info;
    }
    

    /**
     * 1.根据用户user->2.获取角色id->3.根据角色id获取权限permission
     *
     * 1.授权方法,在请求需要操作码的接口时会执行此方法。不需要操作码的接口不会执行
     * 2.实际上是 先执行 AuthorizingRealm,自定义realm的父类中的 getAuthorizationInfo方法,
     * 逻辑是先判断缓存中是否有用户的授权信息(用户拥有的操作码),如果有 就直返回不调用自定义 realm的授权方法了,
     * 如果没缓存,再调用自定义realm,去数据库查询。
     * 用库查询一次过后,如果 在安全管理器中注入了 缓存,授权信息就会自动保存在缓存中,下一次调用需要操作码的接口时,
     * 就肯定不会再调用自定义realm授权方法了。   网上有分析AuthorizingRealm,shiro使用缓存的过程
     * 3.AuthorizingRealm 有多个实现类realm,推测可能是把 自定义realm注入了安全管理器,所以才调用自定义的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // TODO Auto-generated method stub
        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
        LearnLogin user = (LearnLogin)principals.getPrimaryPrincipal();
        //这应该把查出来的无序菜单手动放到缓存
        List<LearnMenu> resourceList=new ArrayList<LearnMenu>();
        // 功能列表
        Set<String> menus = new HashSet<String>();
        if (user.isAdmin()){
            simpleAuthorInfo.addRole("admin");
            simpleAuthorInfo.addStringPermission("*:*:*");
        }else{
            resourceList = systemService.getResourceByUserId(user.getLoginid());
            List<String> resourceIds=new ArrayList<>();
            if(resourceList!=null &&!resourceList.isEmpty()){
                for(LearnMenu reousrce:resourceList){
                    resourceIds.add(reousrce.getMid());
                }
            }
            menus = systemService.getPermsByUserId(user.getLoginid());
            // 角色加入AuthorizationInfo认证对象
            simpleAuthorInfo.addRoles(resourceIds);
            // 权限加入AuthorizationInfo认证对象
            simpleAuthorInfo.setStringPermissions(menus);
        }
        return simpleAuthorInfo;
    }

}
6、权限分配
  • 对于权限分配页面的展示,这里使用shiro+thymeleaf结合,代码如下
这里只贴出相关的权限按钮,其余就不展示

<div class="layui-btn-group">
	<button type="button" class="layui-btn layui-btn-sm" onclick="adduser()" shiro:hasPermission="system:user:add" >增加</button>
	<button type="button" id="bjyh" class="layui-btn layui-btn-sm layui-btn-normal" onclick="edituser()" shiro:hasPermission="system:user:edit">编辑</button>
	<button type="button" class="layui-btn layui-btn-sm layui-btn-danger" onclick="removeuser()" shiro:hasPermission="system:user:remove">删除</button>
</div>

<div class="layui-btn-group">
	<button type="button" class="layui-btn layui-btn-sm layui-btn-warm" onclick="importuser()" shiro:hasPermission="system:user:import">导入</button>
	<button type="button" class="layui-btn layui-btn-sm layui-btn-primary" onclick="exportuser()" shiro:hasPermission="system:user:export">导出</button>
</div>

这里在html中使用shiro:hasPermission,需要在html头部引入包

xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

而对于shiro:hasPermission,如果该用户有这个权限或者角色则页面就展示这个按钮,若没有响应的权限和角色,页面就不展示该对于的按钮,

此处页面只是数据,需要在pom文件中引入响应的依赖,在1处已贴出注释,并且需要在ShiroConfig 的核心配置中配置响应的方法,在2处最后也以贴出注释

7、最后

对于shiro的整个权限分配大致就是这样,仅供参考

8.截图

image.png
image.png
image.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值