• springboot-shiro 使用


    功能:解决web站点的登录,权限验证,授权等功能

    优点:在不影响站点业务代码,可以权限的授权与验证横切到业务中

    比较:shiro比Security 划分更细化

    项目使用思考:

    1:授权中用户的权限。变动不大,可以存在缓存中,不用每次都去数据库中查一次

    2:ShiroFilterFactoryBean 中要验证的权限,由于只在程序初始化的时候执行。后期对角色或权限修改,还要重启项目。(是否要在更新或修改权限的时候,动态更新问题)

    依赖包:

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring-boot-starter</artifactId>
                <version>1.6.0</version>
            </dependency>
            <!--thymeleaf整合shiro-->
            <dependency>
                <groupId>com.github.theborakompanioni</groupId>
                <artifactId>thymeleaf-extras-shiro</artifactId>
                <version>2.0.0</version>
            </dependency>

    PS: 用户的授权信息,最好存入缓存中,不用每次都从缓存中取。本实例中未用缓存

    public class MyRealm extends AuthorizingRealm {
    
        @Autowired
        private UserService userService;
    
        @Autowired
        private RoleService roleservice;
    
        @Autowired
        private PermissionService permissionService;
    
        /**
         * 授权(为当前登录成功的用户授予权限和分配角色)
         *
         * @param principals
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            // 1、获取用户
            User user = (User) principals.getPrimaryPrincipal();
            if (user == null) {
                log.warn("授权失败,用户信息异常,用户{}", this.getClass().getName());
                return authorizationInfo;
            }
    
            // 2、根据用户名去数据库中查询用户信息(查询该认证用户下角色/权限信息)
            List<Role> roleList = roleservice.getRoleListByUserName(user.getUserName());
            List<Permission> permissionList=null;
            if(roleList.size()>0){
                for(Role role:roleList){
                    //添加角色
                    authorizationInfo.addRole(role.getRole());//添加角色名称(要唯一)
                    permissionList=permissionService.getPermissionListByRoleId(role.getRoleId());
                    for(Permission permission : permissionList){
                        //添加权限
                        authorizationInfo.addStringPermission(permission.getPermission());//添加权限(user:add)
                    }
                }
            }
            log.info("====doGetAuthorizationInfo注册完成====");
            return authorizationInfo;
        }
    
        /**
         * 认证、登录(用来验证当前登录的用户,获取认证信息)
         *
         * @param token
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //1.把AuthenticationToken类型的token转换为UsernamePasswordToken(封装了从前端传递的用户名和密码(用户输入))
            UsernamePasswordToken userToken=(UsernamePasswordToken)token;
    
            //2.通过UsernamePasswordToken获取username
            String username = userToken.getUsername();
            if(!StringUtils.hasText(username)){
                return null;
            }
            // 3.通过username查询数据库,且判断数据库中的用户状态
            User user= userService.findByUserName(username);
            if(user==null){
                throw new UnknownAccountException("该用户不存在");
            }
            if(user.getState()==0){
                throw new AuthenticationException("创建未认证");
            }
            if(user.getState()==2){
                throw new LockedAccountException("该用户被锁定");
            }
    
            // 4.传入用户名和密码进行身份认证,并返回认证信息(盐值可以不传【加盐后相同密码加密后不一致】)
            // 认证的第一个参数 可以是Username也可以是User实体类对象
            // 如果传的参数为username,那么在授权阶段,使用principals.getPrimaryPrincipal();获取到的就是Username
            // 如果传的参数为user对象,那么在授权阶段,获取到的就是user对象
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(
                    user,
                    user.getPassword(),
                    ByteSource.Util.bytes("1"),
                    this.getClass().getName()
            );
            log.info("====doGetAuthenticationInfo注册完成====");
            return authcInfo;
        }
    
     
    }

    PS:filterChainMap.put("/user/**", "authc"); //过滤链定义,从上向下顺序执行,一般将 /**放在最为下边(否则可能导致 授权不会执行)

    @Configuration
    @Slf4j
    public class ShiroConfig {
    
        @Value("${spring.redis.host}")
        private String host;
        @Value("${spring.redis.port}")
        private int port;
        @Value("${spring.redis.password}")
        private String password;
        @Value("${spring.redis.timeout}")
        private int timeout;
    
    
        /**
         * 注入自定义的 Realm [将自己的验证方式加入容器]
         *
         * @return
         */
        @Bean
        public MyRealm myAuthRealm() {
            MyRealm myRealm = new MyRealm();//配置自定义密码比较器
            myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            return myRealm;
        }
    
        /**
         * 注入安全管理器(配置 SecurityManager 时,需要将上面自定义 Realm 添加进来,这样 Shiro 才可访问该 Realm)
         *
         * @return
         */
        @Bean
        public SecurityManager securityManager() {
            // 将自定义 Realm 加进来
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
            log.info("====securityManager注册完成====");
            //PS:DefaultWebSecurityManager无法转SecurityManager,需要手动导入此包(import org.apache.shiro.mgt.SecurityManager;)
    
            return securityManager;
        }
        @Bean
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, PermissionService permissionService) {
            // 定义 shiroFactoryBean
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
            shiroFilterFactoryBean.setLoginUrl("/user/login");
            // 登录成功后要跳转的链接
            shiroFilterFactoryBean.setSuccessUrl("/user/index");
            // 未授权界面(认证不通过跳转);
            shiroFilterFactoryBean.setUnauthorizedUrl("/user/403");
    
            // 拦截器(LinkedHashMap 是有序的,进行顺序拦截器配置)
            Map<String, String> filterChainMap = new LinkedHashMap<>();
            // 配置不会被拦截的链接 顺序判断【anon表示放行】
            filterChainMap.put("/static/**", "anon");
            filterChainMap.put("/public", "anon");
            // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
            filterChainMap.put("/user/logout", "logout");
    
            //添加要检查的权限(如果改动在数据库中添加或删除权限,不重启程序,权限是不会生效的。需要动态配置)
            List<Permission> permissionList = permissionService.getAllPermissionList();
            for (Permission permission : permissionList) {
                filterChainMap.put(permission.getUrl(), "perms[" + permission.getPermission() + "]");
            }
            //filterChainMap.put("/user/userAdd", "perms[user:add]");
            //filterChainMap.put("/user/userDel", "perms[user:del]");
    
    
            // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
            // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
            filterChainMap.put("/user/**", "authc");
            // 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
    
            log.info("====shiroFilterFactoryBean注册完成====");
    
            return shiroFilterFactoryBean;
        }/**
         * 开启shiro 注解模式
         * 可以在controller中的方法前加上注解
         * 如 @RequiresPermissions("userInfo:add")
         *
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }

    /**
         * 凭证匹配器(密码校验交给Shiro的SimpleAuthenticationInfo进行处理)
         *
         * @return
         */
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            //使用MD5加密
    //        Md5Hash md5 = new Md5Hash("admin", "1", 2);
    //        log.info("====求得密码:"+md5+"====");
    
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            // 加密方式
            hashedCredentialsMatcher.setHashAlgorithmName("md5");
            // 加密两次
            hashedCredentialsMatcher.setHashIterations(2);
            // 是否存储为16进制
            //hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
            return hashedCredentialsMatcher;
        }

    }

    PS:注意权限更新后,动态更新权限(ShiroFilterFactoryBean 只在初始化的时候执行,新增或修角色改权限时,需要动态更新)

    @Controller
    @Slf4j
    @RequestMapping("/user")
    public class UserController {
    
    //获取数据空的权限 @Autowired ShiroService shiroService;
    /** * 修改数据库中的权限后,动态更新权限 * @return */ @RequestMapping("/updatePermission") public String updatePermission(){ shiroService.updatePermission(); return "redirect:/user/userList"; } @RequestMapping("/index2") public String index2(){ return "/shiro/index"; } @RequestMapping("/index") public String index(){ return "/shiro/index"; } @RequestMapping("/403") public String unauthorized(){ return "/shiro/unauthorized"; } @RequestMapping("/logout") public String logout(){ // 获取 subject 认证主体 Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/user/login"; } @RequestMapping("/login") public String login(){ return "/shiro/login"; } @RequestMapping(value = "/login",method = RequestMethod.POST) public String login(User user, HttpServletRequest request, RedirectAttributes redirectAttributes, Model model){ //RedirectAttributes : 用于重定向带参数 if (user.getUserName()== null || user.getUserName().isEmpty()) { redirectAttributes.addFlashAttribute("message", "用戶名不能爲空"); return "redirect:/user/login"; } // 获取 subject 认证主体 Subject currentUser = SecurityUtils.getSubject(); //currentUser.isAuthenticated()当前用户是否被认证 //如果当前用户被认证了,说明用户还是处于登录状态 if(!currentUser.isAuthenticated()) { // 把需要登录的用户名和密码存入shiro提供的令牌中 UsernamePasswordToken token=new UsernamePasswordToken(user.getUserName(),user.getPassword()); try { // subject调用login方法来进行匹配用户是否可以登录成功 // login方法的参数需要接收shiro的UsernamePasswordToken类型 currentUser.login(token); //Session session = currentUser.getSession(); // 设置会话session //session.setAttribute("userName", user.getUserName()); //登录成功写入Session等其他操作 //request.getSession().setAttribute("user",user); return "/shiro/index"; } catch(UnknownAccountException uae){ log.info("对用户[" + user.getUserName() + "]进行登录验证..验证未通过,未知账户"); redirectAttributes.addFlashAttribute("message", "未知账户"); }catch(IncorrectCredentialsException ice){ log.info("对用户[" + user.getUserName() + "]进行登录验证..验证未通过,错误的凭证"); redirectAttributes.addFlashAttribute("message", "密码不正确1"); }catch(LockedAccountException lae){ log.info("对用户[" + user.getUserName() + "]进行登录验证..验证未通过,账户已锁定"); redirectAttributes.addFlashAttribute("message", "账户已锁定"); }catch(AuthenticationException ae){ //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 log.info("对用户[" + user.getUserName() + "]进行登录验证..验证未通过,堆栈轨迹如下"); ae.printStackTrace(); redirectAttributes.addFlashAttribute("message", "用户名或密码不正确"); } return "redirect:/user/login"; } //Tis:return 中 redirect:后面是控制器的路由地址,不是页面地址 return "redirect:/user/index"; //return "/shiro/index"; } }
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
    xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

    <h3 shiro:principal>登录人</h3> <shiro:hasPermission name="user:add"> <a th:href="@{/add}" >添加</a> </shiro:hasPermission> <shiro:hasPermission name="user:del"> <a th:href="@{/del}" >删除</a> </shiro:hasPermission>
  • 相关阅读:
    好吧,左小波出山了——ie8兼容indexOf问题
    jmeter负载机运行/添加压力机/分布式
    jmeter操作数据库
    Charles手机抓包设置&无法打开火狐网页设置
    python学习-Day1-接口测试
    动态SQL
    MyBatis缓存
    正则表达式
    MyBatis配置文件的配置说明
    几种数据源的配置
  • 原文地址:https://www.cnblogs.com/songl/p/14025916.html
Copyright © 2020-2023  润新知