• 在前后端分离项目中使用SpringBoot集成Shiro


    前言

           这次在处理一个小项目时用到了前后端分离,服务端使用springboot2.x。权限验证使用了Shiro。前后端分离首先需要解决的是跨域问题,POST接口跨域时会预发送一个OPTIONS请求,浏览器收到响应后会继续执行POST请求。 前后端分离后为了保持会话状态使用session持久化插件shiro-redis,持久化session可以持久化到关系型数据库,也可以持久化到非关系型数据库(主要是重写SessionDao)。Shiro已提供了SessionDao接口和抽象类。如果项目中用到Swagger的话,还需要把swagger相关url放行。

        

    搭建依赖

    <dependency>
        <!--session持久化插件-->
    	<groupId>org.crazycake</groupId>
    	<artifactId>shiro-redis</artifactId>
    	<version>3.2.3</version>
    </dependency>
    <dependency>
        <!--spring shiro依赖-->
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.1</version>
    </dependency>
    

     Shiro权限配置

    1、ShiroConfig。这里主要是shiro核心配置。比如SecurityManager、SessionManager、CacheManager。

    public class ShiroConfig {
    
        @Value("${spring.redis.shiro.host}")
        private String host;
        @Value("${spring.redis.shiro.port}")
        private int port;
        @Value("${spring.redis.shiro.timeout}")
        private int timeout;
        @Value("${spring.redis.shiro.password}")
        private String password;
    
    
        /**
         * 权限规则配置
         **/
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
            filters.put("authc", new MyFormAuthorizationFilter());
    
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    
            //swagger资源不拦截
            filterChainDefinitionMap.put("/swagger-ui.html", "anon");
            filterChainDefinitionMap.put("/swagger-resources/**/**", "anon");
            filterChainDefinitionMap.put("/v2/api-docs", "anon");
            filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
            filterChainDefinitionMap.put("/configuration/security", "anon");
            filterChainDefinitionMap.put("/configuration/ui", "anon");
    
            filterChainDefinitionMap.put("/login/ajaxLogin", "anon");
            filterChainDefinitionMap.put("/login/unauth", "anon");
            filterChainDefinitionMap.put("/login/logout", "anon");
            filterChainDefinitionMap.put("/login/register","anon");
            filterChainDefinitionMap.put("/**", "authc");
    
            shiroFilterFactoryBean.setLoginUrl("/login/unauth");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
    
        /**
         * shiro安全管理器(权限验证核心配置)
         **/
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myShiroRealm());
            securityManager.setSessionManager(sessionManager());
            securityManager.setCacheManager(cacheManager());
    
            return securityManager;
        }
    
        /**
         * 会话管理
         **/
        @Bean
        public SessionManager sessionManager() {
            MySessionManager sessionManager = new MySessionManager();
            sessionManager.setSessionIdUrlRewritingEnabled(false); //取消登陆跳转URL后面的jsessionid参数
            sessionManager.setSessionDAO(sessionDAO());
            sessionManager.setGlobalSessionTimeout(-1);//不过期
            return sessionManager;
        }
    
        /**
         * 使用的是shiro-redis开源插件 缓存依赖
         **/
        @Bean
        public RedisManager redisManager() {
            RedisManager redisManager = new RedisManager();
            redisManager.setHost(host+":"+port);
            redisManager.setTimeout(timeout);
            redisManager.setPassword(password);
            return redisManager;
        }
    
        /**
         * 使用的是shiro-redis开源插件 session持久化
         **/
        public RedisSessionDAO sessionDAO() {
            RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
            redisSessionDAO.setRedisManager(redisManager());
            return redisSessionDAO;
        }
    
    
        /**
         * 缓存管理
         **/
        @Bean
        public CacheManager cacheManager() {
            RedisCacheManager redisCacheManager = new RedisCacheManager();
            redisCacheManager.setRedisManager(redisManager());
            return redisCacheManager;
        }
    
    
        /**
         * 权限管理
         **/
        @Bean
        public MyShiroRealm myShiroRealm() {
    
            return new MyShiroRealm();
        }
    }
    

     2、MyShiroRealm 用户身份验证、自定义权限。

    public class MyShiroRealm extends AuthorizingRealm {
    
        private Logger logger= LoggerFactory.getLogger(MyShiroRealm.class);
    
        @Resource
        UserDao userDao;
    
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            logger.info("===================权限验证==================");
            return null;
        }
    
    
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
            UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
            User currentUser=userDao.findUser(token.getUsername());
            if(null == currentUser){
                throw new AuthenticationException("账户不存在");
            }
    
            if(!currentUser.getPassword().equals(new String(token.getPassword()))){
                throw new IncorrectCredentialsException("账户密码不正确");
            }
    
            if(currentUser.getIsdel()==1){
                throw new LockedAccountException("账户已冻结");
            }
    
            Subject subject = SecurityUtils.getSubject();
    
            BIUser biUser=new BIUser();
            biUser.setUserId(currentUser.getUserId());
            biUser.setOrgId(currentUser.getOrgid());
            biUser.setUserName(currentUser.getUsername());
            biUser.setPassword(currentUser.getPassword());
            biUser.setSessionId(subject.getSession().getId().toString());
            biUser.setIsdel(currentUser.getIsdel());
            biUser.setCreateTime(currentUser.getCreatetime());
    
            logger.info("======已授权"+biUser.toString()+"====");
    
            return new SimpleAuthenticationInfo(biUser,biUser.getPassword(),biUser.getUserName());
        }
    }
    

     3、MySessionManager。shiro权限验证是根据客户端Cookie中的JSESSIONID值来确定身份是否合格。前后端分离后这个地方需要处理。客户端调用服务端登陆接口,验证通过后返回给客户端一个token值(这里我放的是sessionid)。客户端保存token值,然后调用其他接口时把token值放在header中。对前端来说也就是放在ajax的headers参数中。

    public class MySessionManager extends DefaultWebSessionManager {
    
        private static final String AUTHORIZATION = "Authorization";
    
        private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    
        public MySessionManager() {
        }
    
        @Override
        protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
            //从前端ajax headers中获取这个参数用来判断授权
            String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
            if (StringUtils.hasLength(id)) {
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
                return id;
            } else {
                //从前端的cookie中取值
                return super.getSessionId(request, response);
            }
    
        }
    }
    

     4、MyFormAuthorizationFilter。对于跨域的POST请求,浏览器发起POST请求前都会发送一个OPTIONS请求已确定服务器是否可用,OPTIONS请求通过后继续执行POST请求,而shiro自带的权限验证是无法处理OPTIONS请求的,所以这里需要重写isAccessAllowed方法。

    public class MyFormAuthorizationFilter extends FormAuthenticationFilter {
        protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
            HttpServletRequest httpServletRequest = WebUtils.toHttp(servletRequest);
            if ("OPTIONS".equals(httpServletRequest.getMethod())) {
                return true;
            }
            return super.isAccessAllowed(servletRequest, servletResponse, o);
        }
    
    }
    

    5、处理跨域

     @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowedMethods("PUT", "DELETE", "GET", "POST")
                    .allowedHeaders("*")
                    .exposedHeaders("access-control-allow-headers", "access-control-allow-methods", "access-control-allow" +
                            "-origin", "access-control-max-age", "X-Frame-Options","Authorization")
                    .allowCredentials(false).maxAge(3600);
    
        }
    

        

  • 相关阅读:
    android学习日记08--Paint画笔
    android学习日记07--Canvas画布
    android学习日记06--SurfaceView视图
    android学习日记06--View视图
    android学习日记05--Activity间的跳转Intent实现
    android学习日记04--开发中的通用细节
    android学习日记03--常用控件Dialog
    android学习日记03--常用控件ListView
    android学习日记03--常用控件tabSpec/tabHost
    android学习日记03--常用控件progressbar/seekbar
  • 原文地址:https://www.cnblogs.com/sword-successful/p/11093803.html
Copyright © 2020-2023  润新知