zoukankan      html  css  js  c++  java
  • 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>
  • 相关阅读:
    toj 2975 Encription
    poj 1797 Heavy Transportation
    toj 2971 Rotating Numbers
    zoj 2281 Way to Freedom
    toj 2483 Nasty Hacks
    toj 2972 MOVING DHAKA
    toj 2696 Collecting Beepers
    toj 2970 Hackle Number
    toj 2485 Card Tric
    js页面定位,相关几个属性
  • 原文地址:https://www.cnblogs.com/songl/p/14025916.html
Copyright © 2011-2022 走看看