如何实现登录、URL 和页面按钮的访问控制

点击上方 "编程技术圈"关注, 星标或置顶一起成长

后台回复“大礼包”有惊喜礼包!

日英文

The best way to escape from the past is not to avoid or forget it, but to accept and forgive it. 

摆脱过去并不是逃避或者忘记,只是学着去承受和宽恕。

每日掏心话

我多么希望,有一个门口,早晨,阳光照在草上。我们站着,扶着自己的门窗,门很低,但太阳是明亮的。草在结它的种子,风在摇它的叶子,我们站着,不说话,就十分美好。

责编:乐乐 | 来自:社会主义接班人链接:cnblogs.com/5ishare/p/10461073.html

编程技术圈(ID:study_tech)第 1230 次推文

往日回顾:我为什么要放弃RESTful,选择拥抱GraphQL

     

   正文   

一、引入依赖
二、增加 Shiro 配置
三、自定义 Realm
四、登录认证
五、Controller 层访问控制
六、前端页面层访问控制
七、小结
用户权限管理一般是对用户页面、按钮的访问权限管理。Shiro 框架是一个强大且易用的 Java 安全框架,执行身份验证、授权、密码和会话管理,对于 Shiro 的介绍这里就不多说。本篇博客主要是了解 Shiro 的基础使用方法,在权限管理系统中集成 Shiro 实现登录、url 和页面按钮的访问控制。
一、引入依赖使用 SpringBoot 集成 Shiro 时,在 pom.xml 中可以引入 shiro-spring-boot-web-starter。由于使用的是 thymeleaf 框架,thymeleaf 与 Shiro 结合需要 引入 thymeleaf-extras-shiro。
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
二、增加 Shiro 配置有哪些 url 是需要拦截的,哪些是不需要拦截的,登录页面、登录成功页面的 url、自定义的 Realm 等这些信息需要设置到 Shiro 中,所以创建 Configuration 文件 ShiroConfig。
package com.example.config;


import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

import java.util.LinkedHashMap;
import java.util.Map;


@Configuration
public class ShiroConfig {
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");

        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean()    //创建DefaultWebSecurityManager
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")MyShiroRealm userRealm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(userRealm);
        return defaultWebSecurityManager;

    }
    //创建Realm
    @Bean()
    public MyShiroRealm getUserRealm(){
        return new MyShiroRealm();

    }

    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}
ShiroDialect 这个 bean 对象是在 thymeleaf 与 Shiro 结合,前端 html 访问 Shiro 时使用。
三、自定义 Realm在自定义的 Realm 中继承了 AuthorizingRealm 抽象类,重写了两个方法:doGetAuthorizationInfo 和 doGetAuthenticationInfo。doGetAuthorizationInfo 主要是用来处理权限配置,doGetAuthenticationInfo 主要处理身份认证。
搜索公众号后端架构师后台回复“架构整洁”,获取一份惊喜礼包。
这里在 doGetAuthorizationInfo 中,将 role 表的 id 和 permission 表的 code 分别设置到 SimpleAuthorizationInfo 对象中的 role 和 permission 中。
还有一个地方需要注意:@Component("authorizer"),刚开始我没设置,但报错提示需要一个 authorizer 的 bean,查看 AuthorizingRealm 可以发现它 implements 了 Authorizer,所以在自定义的 realm 上添加 @Component("authorizer") 就可以了。
package com.example.config;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.pojo.Permission;
import com.example.pojo.Role;
import com.example.pojo.User;
import com.example.service.RoleService;
import com.example.service.UserService;

@Component("authorizer")
public class MyShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        User user  = (User)principals.getPrimaryPrincipal();
        System.out.println("User:"+user.toString()+" roles count:"+user.getRoles().size());
        for(Role role:user.getRoles()){
            authorizationInfo.addRole(role.getId());
            role=roleService.getRoleById(role.getId());
            System.out.println("Role:"+role.toString());
            for(Permission p:role.getPermissions()){
                System.out.println("Permission:"+p.toString());
                authorizationInfo.addStringPermission(p.getCode());
            }
        }
        System.out.println("权限配置-->authorizationInfo"+authorizationInfo.toString());
        return authorizationInfo;
    }

    /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入的账号.
        String username = (String)token.getPrincipal();
        System.out.println(token.getCredentials());
        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        User user = userService.getUserById(username);
        System.out.println("----->>userInfo="+user);
        if(user == null){
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user, //用户名
                "123456", //密码
                getName()  //realm name
        );
        return authenticationInfo;
    }

}
四、登录认证登录页面
这里做了一个非常丑的登录页面,主要是自己懒,不想在网上复制粘贴找登录页面了。
<!DOCTYPE html>
<head>
    <meta charset="utf-8">
    <title></title>
    <meta >
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta >
    <meta >
    <meta >
    <meta >
</head>

<form action="/login" method="post">
      <label>用户名:</label><input type="text"  ><br>
      <label >密码:</label><input type="text"  ><br>
      <button type="submit">登录</button><button type="reset">取消</button>
</form>
</body>
</html>
处理登录请求
在 LoginController 中通过登录名、密码获取到 token 实现登录。
package com.example.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class LoginController {

    //退出的时候是get请求,主要是用于退出
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String login(){
        return "login";
    }

    //post登录
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public String login(Model model,String id,String pwd){
        //添加用户认证信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                id,
                "123456");
        try {
                subject.login(usernamePasswordToken);
                return "home";
            }
        catch (UnknownAccountException e) {
            //用户名不存在
            model.addAttribute("msg","用户名不存在");
            return "login";
            }catch (IncorrectCredentialsException e) {
                //密码错误
                model.addAttribute("msg","密码错误");
                return "login";
                }

    }
    @RequestMapping(value = "/index")
    public String index(){
        return "home";
    }

}
五、Controller 层访问控制首先来数据库的数据,两张图是用户角色、和角色权限的数据。


设置权限
这里在用户页面点击编辑按钮时设置需要有 id=002 的角色,在点击选择角色按钮时需要有 code=002 的权限。
@RequestMapping(value = "/edit",method = RequestMethod.GET)
    @RequiresRoles("002")//权限管理;
    public String editGet(Model model,@RequestParam(value="id") String id) {
        model.addAttribute("id", id);
        return "/user/edit";
    }
@RequestMapping(value = "/selrole",method = RequestMethod.GET)
    @RequiresPermissions("002")//权限管理;
    public String selctRole(Model model,@RequestParam("id") String id,@RequestParam("type") Integer type) {
        model.addAttribute("id",id);
        model.addAttribute("type", type);
        return "/user/selrole";
    }
当使用用户 001 登录时,点击编辑,弹出框如下,提示没有 002 的角色

点击选择角色按钮时提示没有 002 的权限。

当使用用户 002 登录时,点击编辑按钮,显示正常,点击选择角色也是提示没 002 的权限,因为权限只有 001。
六、前端页面层访问控制有时为了不想像上面那样弹出错误页面,需要在按钮显示上进行不可见,这样用户也不会点击到。前面已经引入了依赖并配置了 bean,这里测试下在 html 中使用 shiro。
搜索公众号顶级架构师后台回复“offer”,获取一份惊喜礼包。
首先设置 html 标签引入 shiro
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">


控制按钮可见
这里使用 shiro:hasAnyRoles="002,003" 判断用户角色是否是 002 或 003,是则显示不是则不显示。
    <div class="layui-inline">
        <a shiro:hasAnyRoles="002,003" class="layui-btn layui-btn-normal newsAdd_btn" onclick="addUser('')">添加用户</a>
    </div>
    <div class="layui-inline">
        <a shiro:hasAnyRoles="002,003" class="layui-btn layui-btn-danger batchDel" onclick="getDatas();">批量删除</a>
    </div>


当 001 用户登录时,添加用户、批量删除按钮都不显示,只显示查询按钮。

当 002 用户登录时,添加用户、批量删除按钮都显示

七、小结这里只是实现了 Shiro 的简单的功能,Shiro 还有很多很强大的功能,比如 session 管理等,而且目前权限管理模块还有很多需要优化的功能,左侧导航栏的动态加载和权限控制、Shiro 与 Redis 结合实现 session 共享、Shiro 与 Cas 结合实现单点登录等。后续可以把项目做为开源项目,慢慢完善集成更多模块例如:Swagger2、Redis、Druid、RabbitMQ 等供初学者参考。
PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。


版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

欢迎加入后端架构师交流群,在后台回复“学习”即可。

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。在这里,我为大家准备了一份2021年最新最全BAT等大厂Java面试经验总结。
别找了,想获取史上最简单的Java大厂面试题学习资料
扫下方二维码回复「面试」就好了


猜你还想看
阿里、腾讯、百度、华为、京东最新面试题汇集
我差点信了......

永别了,91网站!宣布永久关闭

12 个非常适合做外包项目的开源后台管理系统

嘿,你在看吗?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值