Springboot整合Shiro(包含记住我功能)

虽然都2020年了,但我确实还不大会用shiro,所以利用空闲时间学习了一下基础的用法,参考网上的资料,总结一下自己的理解,也方便自己偶尔看看0.0。

Shiro是什么?

Shiro是一个轻量级的安全认证框架,他可以完成认证,授权,会话管理,缓存等一系列功能。

这是从百度百科kiang来的结构图:

Springboot整合Shiro(包含记住我功能)_第1张图片

我个人对于这些结构及概念性的文字很头疼,一看就容易云里雾里,但是概念确实还是非常重要的,所以我大部分时候还是结合着代码理解概念。

怎么操作?(springboot整合Shiro)

  1. 首先,创建一个普通的springboot项目,除springboot之外的依赖

    <dependency>
    	<groupId>org.projectlombokgroupId>
    	<artifactId>lombokartifactId>
    	<optional>trueoptional>
    dependency>
    <dependency>
    	<groupId>org.apache.shirogroupId>
    	<artifactId>shiro-springartifactId>
    	<version>1.4.0version>
    dependency>
    

    我这里用了lombok插件,可以理解成是一个使用注解给实体类生成get/set,toString。。这些方法的插件,很好用。

  2. 三个实体类

    @Data 会生成各属性get/set,重写equals(),hashCode(),toString()方法

    @AllArgsConstructor 会生成全参构造方法

    @NoArgsConstructor 即为无参构造方法

    User.java(用户信息)

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    
        private int id;
        private String username;
        private String password;
        private Set<Role> roles;
    }
    

    Role.java (角色)

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Role {
    
        private int id;
        private String roleName;
        private Set<Permission> permissions;
    }
    

    Permission.java (权限)

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Permission {
    
        private int id;
        private String permissionName;
    
    }
    
  3. 草率的业务实现类

    假装我们是从持久层拿到的数据,我一共准备了两个用户(zhangsan,lisi),两个角色(admin,user),两个权限字符(query,add)。

    zhangsan 为 admin 拥有 query,add 两个权限。

    lisi 为 user 拥有 query 权限。

    @Service
    public class LoginServiceImpl implements LoginService {
    
        /**
         * 模拟数据库查询用户信息
         *
         * @param name
         * @return
         */
        @Override
        public User getUserByName(String name) {
            //一共两个权限 query和add 管理员拥有两个权限 用户只拥有query
            Permission p1 = new Permission(1,"query");
            Permission p2 = new Permission(2,"add");
            Set<Permission> ap = new HashSet<>();
            ap.add(p1);
            ap.add(p2);
            Set<Permission> up = new HashSet<>();
            up.add(p1);
    
            Role r1 = new Role(1,"admin",ap);
            Role r2 = new Role(2,"user",up);
            Set<Role> ar = new HashSet<>();
            ar.add(r1);
            Set<Role> ur = new HashSet<>();
            ur.add(r2);
    
            //张三为管理员角色 李四为普通用户角色
            User u1 = new User(1,"zhangsan","123456",ar);
            User u2 = new User(2,"lisi","123456",ur);
    
            Map<String,User> map = new HashMap<>();
            map.put("zhangsan",u1);
            map.put("lisi",u2);
    
            return map.get(name);
        }
    }
    
  4. 自定义Realm用于查询用户的角色和授权信息

    需要继承 AuthorizingRealm 并重写他的两个方法,上面的是授权,下面的是登录认证。

    /**
     * 自定义Realm用于查询用户的角色和权限信息并保存到权限管理器
     */
    public class CustomRealm extends AuthorizingRealm {
        
        @Autowired
        LoginService loginService;
    
        /**
         * 授权
         *
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //获取用户名
            String name = (String) principalCollection.getPrimaryPrincipal();
            //获取用户
            User user = loginService.getUserByName(name);
            //添加角色和权限
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            for (Role r : user.getRoles()) {
                //添加角色
                simpleAuthorizationInfo.addRole(r.getRoleName());
                //添加权限
                for (Permission p : r.getPermissions()) {
                    simpleAuthorizationInfo.addStringPermission(p.getPermissionName());
                }
            }
            return simpleAuthorizationInfo;
        }
    
        /**
         * 登录验证
         *
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            if (null == authenticationToken.getPrincipal()) return null;
            //获取用户
            String name = authenticationToken.getPrincipal().toString();
            User user = loginService.getUserByName(name);
            if (null == user) return null;
    
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword(), getName());
            return simpleAuthenticationInfo;
        }
    }
    
  5. 把一系列东西放入spring容器

    把自定义的Realm和实现记住我功能的 cookieRememberMeManager 放进 SecurityManager,再把 SecurityManager 放入spring容器。

    再配置Shiro的过滤器。

    tips:map.put("/**",“user”); 这个是指放行登录认证成功或记住我的用户。

    /**
     * 把CustomRealm和SecurityManager等加入到spring容器
     */
    @Configuration
    public class ShiroConfig {
    
        @Bean
        @ConditionalOnMissingBean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
            DefaultAdvisorAutoProxyCreator defaultAAPC = new DefaultAdvisorAutoProxyCreator();
            defaultAAPC.setProxyTargetClass(true);
            return defaultAAPC;
        }
    
        //自定义的验证方式
        @Bean
        public CustomRealm myShiroRealm(){
            CustomRealm cr = new CustomRealm();
            return cr;
        }
    
        //配置cookie基础属性
        public SimpleCookie simpleCookie(){
            SimpleCookie sc = new SimpleCookie("rememberMe");
            //cookie有效时间 单位秒 以下设置了30天
            sc.setMaxAge(30 * 24 * 60 * 60);
            return sc;
        }
    
        //cookie管理器
        public CookieRememberMeManager cookieRememberMeManager(){
            CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
            cookieRememberMeManager.setCookie(simpleCookie());
            // cookieRememberMeManager.setCipherKey用来设置加密的Key,参数类型byte[],字节数组长度要求16
            cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
            return cookieRememberMeManager;
        }
    
        //权限管理
        @Bean
        public SecurityManager securityManager(){
            DefaultWebSecurityManager dsm = new DefaultWebSecurityManager();
            //自定义验证方式
            dsm.setRealm(myShiroRealm());
            //记住我
            dsm.setRememberMeManager(cookieRememberMeManager());
            return dsm;
        }
    
        //Filter工厂,设置对应的过滤条件和跳转条件
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            Map<String,String> map = new HashMap<>();
            //登出
            map.put("/logout", "logout");
            //对所有用户认证
            map.put("/**","user");
            //登录
            shiroFilterFactoryBean.setLoginUrl("/login");
            //首页 登录成功跳转
            shiroFilterFactoryBean.setSuccessUrl("/index");
            //验证失败跳转
            shiroFilterFactoryBean.setUnauthorizedUrl("/error");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }
    
  6. 登录控制器

    重点是学校Shiro,所以就不写页面了,直接返回字符串。

    @RestController
    public class LoginController {
    
        @RequestMapping("/login")
        public String login(String username, String password, boolean rememberMe) {
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken upt = new UsernamePasswordToken(username, password);
    
            try {
                upt.setRememberMe(rememberMe);
                subject.login(upt);
                //可以在逻辑中检查用户有没有相应角色或权限 没有的话会抛出AuthorizationException异常
    //            subject.checkRole("admin");
    //            subject.checkPermission("add");
            } catch (AuthenticationException e) {
                //不同的操作错误会抛出不同的异常
                e.printStackTrace();
                return "账号或密码错误";
            } catch (AuthorizationException e) {
                e.printStackTrace();
                return "无权限";
            }
            return "login success";
        }
    
        /**
         * 注解验证
         */
        @RequiresRoles("admin")
        @RequiresPermissions("add")
        @RequestMapping("/index")
        public String index() {
            return "index";
        }
    }
    
    
  7. 由于注解方式不方便捕获异常,所以增加了一个类拦截异常

    /**
     * 通过注解的方式无法捕捉到抛出的异常,也就没有办法很好的给前端反馈
     * 所以使用这个类拦截注解方式抛出的无权限异常
     *
     */
    @ControllerAdvice
    @Slf4j
    public class MyExpectHandle {
        @ExceptionHandler
        @ResponseBody
        public String ErrorHandle(AuthorizationException e){
            log.error("未通过权限验证",e);
            return "使用注解方式拦截下的未通过权限验证异常";
        }
    }
    
  8. 启动

    访问: http://localhost:8080/login?username=zhangsan&password=123456&rememberMe=1

    提示 login success 也就是登录成功了

    再访问: http://localhost:8080/index

    ok 没问题 证明登录成功,且权限没问题

    重启浏览器

    再访问 : http://localhost:8080/index

    ok 还是没问题,证明记住我功能生效了

    检测权限,访问: http://localhost:8080/login?username=lisi&password=123456&rememberMe=0

    返回 login success 后再访问:http://localhost:8080/index

    提示没有权限,ok,权限判断生效了

    重启浏览器

    再访问:http://localhost:8080/index

    需要重新登录,ok

参考: https://www.jianshu.com/p/7f724bec3dc3

感谢大佬!

你可能感兴趣的:(Shiro,springboot)