精华内容
下载资源
问答
  • 为什么在spring boot 项目中,只要加上shiro的配置类,前端就会显示说跨域。
  • 前后端分离的时候,我在服务器的controller...在vue.js中能得到数据,但是只要加了shiro他就会显示![图片说明](https://img-ask.csdn.net/upload/201808/15/1534335492_175068.png),请问前端或者后台还需要怎么配置吗
  • springboot shiro vue造成的跨域问题

    千次阅读 2019-04-24 10:00:44
    复杂请求 造成复杂请求的原因就是在请求头部添加了token等信息,浏览器会认为是复杂请求。复杂请求的执行过程是有两... 预请求直接被shiro拦截了,所以真正的请求永远也到不了后台,这个就是问题的关键。此时需要...

    复杂请求

               造成复杂请求的原因就是在请求头部添加了token等信息,浏览器会认为是复杂请求。复杂请求的执行过程是有两步的,浏览器会提前发送一个探针请求(也叫预请求)到服务端,这个请求通过以后才会将真正的请求带着header的信息发送出去

    预请求被拦截

                  预请求直接被shiro拦截了,所以真正的请求永远也到不了后台,这个就是问题的关键。此时需要自定义一个拦截器来处理预请求的问题

    package com.**.common.shiro.filter;
    
    import org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter;
    import org.apache.shiro.web.util.WebUtils;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class MyShiroAuthFilter extends PassThruAuthenticationFilter {
    
    
    	@Override
    	public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    		HttpServletRequest req = (HttpServletRequest)request;
    		if(req.getMethod().equals(RequestMethod.OPTIONS.name())){
    			return true;
    		}
    		return super.onPreHandle(request, response, mappedValue);
    	}
    	@Override
    	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    		HttpServletResponse httpResp = WebUtils.toHttp(response);
    		HttpServletRequest httpReq = WebUtils.toHttp(request);
    
    		/**系统重定向会默认把请求头清空,这里通过拦截器重新设置请求头,解决跨域问题*/
    		httpResp.addHeader("Access-Control-Allow-Origin", httpReq.getHeader("Origin"));
    		httpResp.addHeader("Access-Control-Allow-Headers", "*");
    		httpResp.addHeader("Access-Control-Allow-Methods", "*");
    		httpResp.addHeader("Access-Control-Allow-Credentials", "true");
    
    		WebUtils.toHttp(response).sendRedirect(httpReq.getContextPath()+"/user/unauth");
    		return false;
    	}
    
    }
    

     然后在shiro的配置文件中添加过滤器

     @Bean
        public ShiroFilterFactoryBean shirFilter(DefaultWebSecurityManager securityManager) {
    
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
    
            //注意过滤器配置顺序 不能颠倒
            //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
            filterChainDefinitionMap.put("/user/logout", "logout");
            shiroFilterFactoryBean.setLoginUrl("/user/unauth");
            filterChainDefinitionMap.put("/user/unauth", "anon");
            filterChainDefinitionMap.put("/user/login", "anon");
    
            /**swagger拦截配置*/
            filterChainDefinitionMap.put("/swagger-ui.html", "anon");
            filterChainDefinitionMap.put("/swagger-resources/**", "anon");
            filterChainDefinitionMap.put("/swagger-resources", "anon");
            filterChainDefinitionMap.put("/v2/api-docs", "anon");
            filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
    
            Map<String, Filter> filters = new HashMap<>();
            filters.put("authc",new MyShiroAuthFilter());
            shiroFilterFactoryBean.setFilters(filters);
            filterChainDefinitionMap.put("/**", "authc");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }

     

    展开全文
  • 系统采用jeeplus框架(ssm+redis+shiro+mongodb+redis),默认是做了JSP未做前后端分离,由于业务需要已经多终端使用的需求(H5、小程序等),需要实现前后端分离。但是由于需要同时保留原版的未做前后端分离的UI 和...

           系统采用jeeplus框架(ssm+redis+shiro+mongodb+redis),默认是做了JSP未做前后端分离,由于业务需要已经多终端使用的需求(H5、小程序等),需要实现前后端分离。但是由于需要同时保留原版的未做前后端分离的UI 和新版做了前后端分离的UI,所以采用单独提出一套接口做封装供前端VUE使用。

           原先使用时有一个业务场景及客户可以通过其它系统跳转到我们系统不需要登录,但是由于是其它客户系统不好做SSO加上工期紧,我们这次采用客户系统在登录前先发送一次请求同步用户信息到我们系统,然后我们进行模拟登录成功后产生一个token返回客户系统,系统拿到token后再次调用登录接口,我们系统验证token有效后完成用户登录。

           在前后端分离的框架前端采用了ant design vue版本,前端代码使用nginx做服务器,当客户系统需要登录时,有系统验证用户信息然后重定向到vue前端服务。遇到的问题是,当从后端tomcat容器重定向到nginx中,该死的url被添加了jessionid参数导致无法识别,显示404,返回参数如下:

    http://demo.zzxes.com.cn/ems/#/;JSESSIONID=46ded843b141471c930d95cac006e3d9#/?expoid=7e37cf45f131428ab6b6ed0dcc3f02d4&excompanyType=2
    

      vue router 默认把#后面的参数当了路由地址,所以无法访问,于是开始漫长的旅程。

           方案一:

      既然是路由的问题,第一想法是把默认的HASH模式修改为HISTORY模式,于是得到一下返回结构:

    http://demo.zzxes.com.cn/ems/;JSESSIONID=46ded843b141471c930d95cac006e3d9#/?expoid=7e37cf45f131428ab6b6ed0dcc3f02d4&excompanyType=2
    

      #的路由问题解决了,但是jessionid又被当成了请求地址,一样无法访问404,该死的404,方案一失败。

      方案二:

           有没有办法能把中间的那一坨jessionid去掉呢?这样就解决了问题了吗。通过百度、google终于发现nginx有rewrite这功能,于是修改配置文件,增加重写配置。

    rewrite ^(.*);JSESSIONID)(.*)$ $request_uri/ems/?$args redirect;
    

      reload nginx,测试成功,方案二可行。

     

    遗留问题,不确定方案二去掉jessionid是否会有其他隐藏问题,只能发现了在解决了。

           

     

    转载于:https://www.cnblogs.com/rememberme/p/vue_ngix.html

    展开全文
  • shiro-vue 快速搭建前后端分离的权限管理系统 后台基于 Springboot JPA Swagger Shiro 前端基于 VUE ElementUI 后台代码: https://github.com/FENGZHIJIE1998/shiro-auth 前端代码: ...
  • Spring Boot-Shiro-Vue 提供一套基于SpringBoot-shiro-vue的权限管理思路. 前后端都加以控制,做到按钮/接口级别的权限 DEMO 测试地址 admin/123456 管理员身份登录,可以新增用户,角色. 角色可以分配权限 控制菜单...
  • Spring Boot-Shiro-Vue 提供一套基于SpringBoot-shiro-vue的权限管理指南。 前一级都对准控制,做到按钮/接口等级的权限 演示 admin / 123456管理员身份登录,可以添加用户,角色。 角色可以分配权限 控制菜单是否...
  • SpringBoot-Shiro-Vue_2.7z

    2020-03-12 17:04:36
    代码完成,前端后端分离,SpringBoot-Shiro-Vue 比springboot helloworld 复杂,没有接触过spring生态的应该毕竟难上手
  • vue每次请求加头部(shiro+vue)    前后台分离,全局请求加头部 设置全局请求为ajax请求 _axios.interceptors.request.use( function(config) { var accessToken = localStorage.getItem('...

    vue每次请求加头部(shiro+vue)

     

     前后台分离,全局请求加头部

    设置全局请求为ajax请求

    _axios.interceptors.request.use(
      function(config) {
      var accessToken = localStorage.getItem('accessToken');
      if (accessToken && accessToken !== '') {
          config.headers.common['Authorization'] = accessToken;
      }
        return config;
      },
      function(error) {
        // Do something with request error
        return Promise.reject(error);
      }
    );
    // 响应拦截器获取 headers,设置(刷新) Token
    // Add a response interceptor
    _axios.interceptors.response.use(
      function(response) {
        // Do something with response data
            var accessToken = response.headers['authorization'];
            if (accessToken && accessToken !== '') {
                localStorage.setItem('accessToken',accessToken);
            }
            return response;
      },
      function(error) {
        // Do something with response error
        return Promise.reject(error);
      }
    );

     

    posted @ 2019-02-22 09:53 韦邦杠 阅读(...) 评论(...) 编辑 收藏
    展开全文
  • 前段日子写过一篇关于SpringBoot+Shiro的简单整合的例子,那个例子并不适用于我们目前的前后端分离开发的趋势。我之前写过一个项目也是用到了Shiro的前后端分离,某度了许久也没找到解决方案,什么去掉shiroFilter....

    目录

    前言

    Let's do it!!

    第一步:新建工程

    第二步:准备好要用的包包和类类

    第三步:编写登陆入口

    第四步:编写ShiroService中的方法

    第五步:编写ShiroConfig类

    第六步:实现自定义的AuthenticationToken。

    第七步:编写自己的Realm

    第八步:实现自定义AuthenticatingFilter。

    第九步:详解校验流程

    看看效果

    总结


    前言

    前段日子写过一篇关于SpringBoot+Shiro的简单整合的例子,那个例子并不适用于我们目前的前后端分离开发的趋势。我之前写过一个项目也是用到了Shiro的前后端分离,某度了许久也没找到解决方案,什么去掉shiroFilter.setLoginUrl();也阻止不了讨人厌的login.jsp的出现。直到我看到了renren-fast的源码...废话不多说,让我们来看看如何实现吧!

     前后端分离
    要实现前后端分离,需要考虑以下2个问题: 1. 项目不再基于session了,如何知道访问者是谁? 2. 如何确认访问者的权限?


    前后端分离,一般都是通过token实现,本项目也是一样;用户登录时,生成token及 token过期时间,token与用户是一一对应关系,调用接口的时候,把token放到header或 请求参数中,服务端就知道是谁在调用接口。

    代码已上传到Git:

    后台代码:https://github.com/FENGZHIJIE1998/shiro-auth 

    前端代码:https://github.com/FENGZHIJIE1998/shiro-vue

    觉得好用的记得点个Star哦

    Let's do it!!


    介绍:这次我们使用Shiro快速搭建前后端分离的权限管理系统 利用JPA帮我们管理数据库,Swagger Knife4j 帮我搭建Web测试环境;

    后台基于 Springboot JPA Knife4j Shiro

    前端基于 VUE ElementUI

    注意:主要观察token的使用方法!

    第一步:新建工程

    pom文件application.yml巴拉巴拉这里省略,这里贴出需要用到的依赖:

            <!--starter-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <!--  test-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!--web-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--validation-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-validation</artifactId>
            </dependency>
            <!--JPA-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <!--JDBC-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <!--lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <!-- shiro-->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.3.2</version>
            </dependency>
            <!--mysql-connector-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!-- druid-spring-boot-starter -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.10</version>
            </dependency>
            <!-- swagger -->
            <dependency>
                <groupId>com.spring4all</groupId>
                <artifactId>swagger-spring-boot-starter</artifactId>
                <version>1.8.0.RELEASE</version>
            </dependency>
            <!-- knife4j -->
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-spring-boot-starter</artifactId>
                <version>2.0.2</version>
            </dependency>
            <!-- commons-lang -->
            <dependency>
                <groupId>commons-lang</groupId>
                <artifactId>commons-lang</artifactId>
                <version>2.6</version>
            </dependency>

    第二步:准备好要用的包包和类类

    第三步:编写登陆入口

    为了方便这里不做密码加盐加密

    /**
     * @Author 大誌
     * @Date 2019/3/30 22:04
     * @Version 1.0
     */
    @RestController
    public class ShiroController {
    
        private final ShiroService shiroService;
    
        public ShiroController(ShiroService shiroService) {
            this.shiroService = shiroService;
        }
    
    
        /**
         * 登录
         */
        @ApiOperation(value = "登陆", notes = "参数:用户名 密码")
        @PostMapping("/sys/login")
        public Map<String, Object> login(@RequestBody @Validated LoginDTO loginDTO, BindingResult bindingResult) {
            Map<String, Object> result = new HashMap<>();
            if (bindingResult.hasErrors()) {
                result.put("status", 400);
                result.put("msg", bindingResult.getFieldError().getDefaultMessage());
                return result;
            }
    
            String username = loginDTO.getUsername();
            String password = loginDTO.getPassword();
            //用户信息
            User user = shiroService.findByUsername(username);
            //账号不存在、密码错误
            if (user == null || !user.getPassword().equals(password)) {
                result.put("status", 400);
                result.put("msg", "账号或密码有误");
            } else {
                //生成token,并保存到数据库
                result = shiroService.createToken(user.getUserId());
                result.put("status", 200);
                result.put("msg", "登陆成功");
            }
            return result;
        }
    
        /**
         * 退出
         */
        @ApiOperation(value = "登出", notes = "参数:token")
        @PostMapping("/sys/logout")
        public Map<String, Object> logout(@RequestHeader("token")String token) {
            Map<String, Object> result = new HashMap<>();
            shiroService.logout(token);
            result.put("status", 200);
            result.put("msg", "您已安全退出系统");
            return result;
        }
    }

    第四步:编写ShiroService中的方法

    主要是生成一个token返回给前端。

    /**
     * @Author 大誌
     * @Date 2019/3/30 22:18
     * @Version 1.0
     */
    @Service
    public class ShiroServiceImpl implements ShiroService {
    
    
        @Autowired
        private UserRepository userRepository;
        @Autowired
        private SysTokenRepository sysTokenRepository;
    
        /**
         * 根据username查找用户
         *
         * @param username
         * @return User
         */
        @Override
        public User findByUsername(String username) {
            User user = userRepository.findByUsername(username);
            return user;
        }
    
        //12小时后过期
        private final static int EXPIRE = 3600 * 12;
    
        @Override
        /**
         * 生成一个token
         *@param  [userId]
         *@return Result
         */
        public Map<String, Object> createToken(Integer userId) {
            Map<String, Object> result = new HashMap<>();
            //生成一个token
            String token = TokenGenerator.generateValue();
            //当前时间
            Date now = new Date();
            //过期时间
            Date expireTime = new Date(now.getTime() + EXPIRE * 1000);
            //判断是否生成过token
            SysToken tokenEntity = sysTokenRepository.findByUserId(userId);
            if (tokenEntity == null) {
                tokenEntity = new SysToken();
                tokenEntity.setUserId(userId);
                tokenEntity.setToken(token);
                tokenEntity.setUpdateTime(now);
                tokenEntity.setExpireTime(expireTime);
                //保存token
                sysTokenRepository.save(tokenEntity);
            } else {
                tokenEntity.setToken(token);
                tokenEntity.setUpdateTime(now);
                tokenEntity.setExpireTime(expireTime);
                //更新token
                sysTokenRepository.save(tokenEntity);
            }
            result.put("token", token);
            result.put("expire", EXPIRE);
            return result;
        }
    
        @Override
        public void logout(String token) {
            SysToken byToken = findByToken(token);
            //生成一个token
            token = TokenGenerator.generateValue();
            //修改token
            SysToken tokenEntity = new SysToken();
            tokenEntity.setUserId(byToken.getUserId());
            tokenEntity.setToken(token);
            sysTokenRepository.save(tokenEntity);
        }
    
        @Override
        public SysToken findByToken(String accessToken) {
            return sysTokenRepository.findByToken(accessToken);
    
        }
    
        @Override
        public User findByUserId(Integer userId) {
            return userRepository.findByUserId(userId);
        }
    }
    

    第五步:编写ShiroConfig类

    /**
     * @Author 大誌
     * @Date 2019/3/30 21:50
     * @Version 1.0
     */
    @Configuration
    public class ShiroConfig {
    
        @Bean("securityManager")
        public SecurityManager securityManager(AuthRealm authRealm) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(authRealm);
            securityManager.setRememberMeManager(null);
            return securityManager;
        }
    
        @Bean("shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            shiroFilter.setSecurityManager(securityManager);
            //oauth过滤
            Map<String, Filter> filters = new HashMap<>();
            filters.put("auth", new AuthFilter());
            shiroFilter.setFilters(filters);
            Map<String, String> filterMap = new LinkedHashMap<>();
            filterMap.put("/webjars/**", "anon");
            filterMap.put("/druid/**", "anon");
            filterMap.put("/sys/login", "anon");
            filterMap.put("/swagger/**", "anon");
            filterMap.put("/v2/api-docs", "anon");
            filterMap.put("/swagger-ui.html", "anon");
            filterMap.put("/swagger-resources/**", "anon");
            filterMap.put("/**", "auth");
            shiroFilter.setFilterChainDefinitionMap(filterMap);
    
            return shiroFilter;
        }
    
        @Bean("lifecycleBeanPostProcessor")
        public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
            return new LifecycleBeanPostProcessor();
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(securityManager);
            return advisor;
        }
    }
    

    第六步:实现自定义的AuthenticationToken。

    阅读AuthenticatingFilter抽象类中executeLogin方法,我们发现调用 了subject.login(token),这是shiro的登录方法,且需要token参数,我们自定义 AuthToken类,只要实现AuthenticationToken接口,就可以了。

     //AuthenticatingFilter中的executeLogin()
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
            AuthenticationToken token = createToken(request, response);
            if (token == null) {
                String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                        "must be created in order to execute a login attempt.";
                throw new IllegalStateException(msg);
            }
            try {
                Subject subject = getSubject(request, response);
                //重点!
                subject.login(token);
                return onLoginSuccess(token, subject, request, response);
            } catch (AuthenticationException e) {
                return onLoginFailure(token, e, request, response);
            }
        }
    /**
     * 自定义AuthenticationToken类
     * @Author 大誌
     * @Date 2019/3/31 10:58
     * @Version 1.0
     */
    public class AuthToken extends UsernamePasswordToken{
    
        private String token;
    
        public AuthToken(String token) {
            this.token = token;
        }
    
        @Override
        public Object getPrincipal() {
            return token;
        }
    
        @Override
        public Object getCredentials() {
            return token;
        }
    }

    这里我实现的时候出现了Token不匹配的Bug。DeBug下可以查到源头是代码是用UsernamePasswordToken.class和我自定义的AuthToken.class配对。按道理应该是true,却返回了false...于是我就把自定义的AuthToken不实现AuthenticationToken,转为继承UsernamePasswordToken,就可以了。(renren-fast中却可以,可能是版本的问题)

    2020/4/27修改: 为了避免误导,将上诉代码 AuthenticationToken 修改为 UsernamePasswordToken,并且走了一下源码,发现这个getAuthenticationTokenClass()实际上获取到的是UsernamePasswordToken.class

    再回头看看renren-fast中的源码,原来他重写了supports方法!

    第七步:编写自己的Realm

    发起请求时,接受传过来的token后,如何保证token有效及用户权限呢?调用接口时,接受传过来的token后,如何保证token有效及用户权限呢?其实,Shiro提供了AuthorizingRealm以及AuthenticatingFilter抽象类,继承AuthorizingRealm和AuthenticatingFilter抽象类重写方法即可。

    /**
     * @Author 大誌
     * @Date 2019/3/30 21:38
     * @Version 1.0
     */
    @Component
    public class AuthRealm extends AuthorizingRealm {
    
        @Autowired
        private ShiroService shiroService;
    
        @Override
        /**
         * 授权 获取用户的角色和权限
         *@param  [principals]
         *@return org.apache.shiro.authz.AuthorizationInfo
         */
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            //1. 从 PrincipalCollection 中来获取登录用户的信息
            User user = (User) principals.getPrimaryPrincipal();
            //Integer userId = user.getUserId();
            //2.添加角色和权限
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            for (Role role : user.getRoles()) {
                //2.1添加角色
                simpleAuthorizationInfo.addRole(role.getRoleName());
                for (Permission permission : role.getPermissions()) {
                    //2.1.1添加权限
                    simpleAuthorizationInfo.addStringPermission(permission.getPermission());
                }
            }
            return simpleAuthorizationInfo;
        }
    
        @Override
        /**
         * 认证 判断token的有效性
         *@param  [token]
         *@return org.apache.shiro.authc.AuthenticationInfo
         */
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //获取token,既前端传入的token
            String accessToken = (String) token.getPrincipal();
            //1. 根据accessToken,查询用户信息
            SysToken tokenEntity = shiroService.findByToken(accessToken);
            //2. token失效
            if (tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) {
                throw new IncorrectCredentialsException("token失效,请重新登录");
            }
            //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
            User user = shiroService.findByUserId(tokenEntity.getUserId());
            //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
            if (user == null) {
                throw new UnknownAccountException("用户不存在!");
            }
            //5. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, this.getName());
            return info;
        }
    }
    

    第八步:实现自定义AuthenticatingFilter。

    /**
     * Shiro自定义auth过滤器
     *
     * @Author 大誌
     * @Date 2019/3/31 10:38
     * @Version 1.0
     */
    @Component
    public class AuthFilter extends AuthenticatingFilter {
    
    
        // 定义jackson对象
        private static final ObjectMapper MAPPER = new ObjectMapper();
    
        /**
         * 生成自定义token
         *
         * @param request
         * @param response
         * @return
         * @throws Exception
         */
        @Override
        protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
            //获取请求token
            String token = TokenUtil.getRequestToken((HttpServletRequest) request);
    
            return new AuthToken(token);
        }
    
        /**
         * 步骤1.所有请求全部拒绝访问
         *
         * @param request
         * @param response
         * @param mappedValue
         * @return
         */
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
                return true;
            }
            return false;
        }
    
        /**
         * 步骤2,拒绝访问的请求,会调用onAccessDenied方法,onAccessDenied方法先获取 token,再调用executeLogin方法
         *
         * @param request
         * @param response
         * @return
         * @throws Exception
         */
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            //获取请求token,如果token不存在,直接返回
            String token = TokenUtil.getRequestToken((HttpServletRequest) request);
            if (StringUtils.isBlank(token)) {
                HttpServletResponse httpResponse = (HttpServletResponse) response;
                httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
                httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
                httpResponse.setCharacterEncoding("UTF-8");
                Map<String, Object> result = new HashMap<>();
                result.put("status", 400);
                result.put("msg", "请先登录");
                String json = MAPPER.writeValueAsString(result);
                httpResponse.getWriter().print(json);
                return false;
            }
            return executeLogin(request, response);
        }
    
        /**
         * token失效时候调用
         */
        @Override
        protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
            httpResponse.setCharacterEncoding("UTF-8");
            try {
                //处理登录失败的异常
                Throwable throwable = e.getCause() == null ? e : e.getCause();
                Map<String, Object> result = new HashMap<>();
                result.put("status", 400);
                result.put("msg", "登录凭证已失效,请重新登录");
                String json = MAPPER.writeValueAsString(result);
                httpResponse.getWriter().print(json);
            } catch (IOException e1) {
            }
            return false;
        }
    
    }
    

    第九步:详解校验流程

    先给你们上一个超级详细的流程图。

     

    接着我们打上断点按照代码走走,可能会有点啰嗦。

    1. 前端发起请求首先会进入AuthFilter的 isAccessAllowed(),除了OPTION方法,其余都拦截。

    2. 拦截之后进入AuthFilter的onAccessDenied(),这里获取token后判断token是否isBlank。如果是,代表请求未携带token,直接默认返回400,未登录给前端,流程就结束了。如果携带了token则进入第三步,继续流程。

    3. 接着进入AuthFilter的createToken,这里生成我们自定义的AuthToken对象。

    4. 接着就会来到AuthRealm中的doGetAuthenticationInfo(),在这个方法中继续token的有效性校验,例如过期、和数据库的token对不上(用户已退出)的情况。如果校验失败,进入第5步,否则进入第6步。

    5. token失效后回到AuthFilter中的onLoginFailure(),返回400以及msg,流程结束。

    6. Token校验成功后进入AuthRealm的doGetAuthorizationInfo(),进行获取当前用户拥有的权限,之后底层代码会进行权限验证。如果用户有权限则会进入请求方法,否则抛出异常。到这一步校验过程就结束了。

    看看效果

    终于熬完上面的步骤了,这时候总体的架构已经确立好了,下面让我们来看看效果如何

    DTO

    /**
     * 登录传输类
     */
    @Data
    public class LoginDTO {
        @NotBlank(message = "用户名不能为空")
        private String username;
        @NotBlank(message = "密码不能为空")
        private String password;
    }
    

    实体类

    @Getter
    @Setter
    @Entity
    public class User {
        @Id
        private Integer userId;
     
        private String username;
        private String password;
     
        @ManyToMany(fetch = FetchType.EAGER)
        @JoinTable(name = "user_role",
                joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "userId")},
                inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "roleId")})
        private Set<Role> roles;
     
    }
     
    @Getter
    @Setter
    @Entity
    public class Role {
     
        @Id
        private Integer roleId;
        private String roleName;
     
        @ManyToMany(fetch = FetchType.EAGER)
        @JoinTable(name = "role_permission",
                joinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "roleId")},
                inverseJoinColumns = {@JoinColumn(name = "PERMISSION_ID", referencedColumnName = "permissionId")})
        private Set<Permission> permissions;
    }
     
    @Getter
    @Setter
    @Entity
    public class Permission {
     
        @Id
        private Integer permissionId;
        private String permissionName;
        private String permission;
    }
    
    @Getter
    @Setter
    @Entity
    public class SysToken{
     
        @Id
        private Integer userId;
        private String token;
        private Date expireTime;
        private Date updateTime
    }

    以及给实体类附上权限:

    我定义了三个用户 

    用户 角色 权限
    Jack SVIP select;save;delete;update
    Rose VIP select;save;update
    Paul P select
    /*
    Navicat MySQL Data Transfer
    Source Server         : localhost
    Source Server Version : 50549
    Source Host           : localhost:3306
    Source Database       : shiro
    Target Server Type    : MYSQL
    Target Server Version : 50549
    File Encoding         : 65001
    Date: 2019-04-07 17:06:36
    */
     
    SET FOREIGN_KEY_CHECKS=0;
     
    -- ----------------------------
    -- Table structure for permission
    -- ----------------------------
    DROP TABLE IF EXISTS `permission`;
    CREATE TABLE `permission` (
      `permission_id` int(11) NOT NULL,
      `permission` varchar(255) DEFAULT NULL,
      `permission_name` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`permission_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
     
    -- ----------------------------
    -- Records of permission
    -- ----------------------------
    INSERT INTO `permission` VALUES ('1', 'select', '查看');
    INSERT INTO `permission` VALUES ('2', 'update', '更新');
    INSERT INTO `permission` VALUES ('3', 'delete', '删除');
    INSERT INTO `permission` VALUES ('4', 'save', '新增');
     
    -- ----------------------------
    -- Table structure for role
    -- ----------------------------
    DROP TABLE IF EXISTS `role`;
    CREATE TABLE `role` (
      `role_id` int(11) NOT NULL,
      `role_name` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`role_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
     
    -- ----------------------------
    -- Records of role
    -- ----------------------------
    INSERT INTO `role` VALUES ('1', 'svip');
    INSERT INTO `role` VALUES ('2', 'vip');
    INSERT INTO `role` VALUES ('3', 'p');
     
    -- ----------------------------
    -- Table structure for role_permission
    -- ----------------------------
    DROP TABLE IF EXISTS `role_permission`;
    CREATE TABLE `role_permission` (
      `role_id` int(11) NOT NULL,
      `permission_id` int(11) NOT NULL,
      PRIMARY KEY (`role_id`,`permission_id`),
      KEY `FKf8yllw1ecvwqy3ehyxawqa1qp` (`permission_id`),
      CONSTRAINT `FKa6jx8n8xkesmjmv6jqug6bg68` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`),
      CONSTRAINT `FKf8yllw1ecvwqy3ehyxawqa1qp` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`permission_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
     
    -- ----------------------------
    -- Records of role_permission
    -- ----------------------------
    INSERT INTO `role_permission` VALUES ('1', '1');
    INSERT INTO `role_permission` VALUES ('2', '1');
    INSERT INTO `role_permission` VALUES ('3', '1');
    INSERT INTO `role_permission` VALUES ('1', '2');
    INSERT INTO `role_permission` VALUES ('2', '2');
    INSERT INTO `role_permission` VALUES ('1', '3');
    INSERT INTO `role_permission` VALUES ('1', '4');
    INSERT INTO `role_permission` VALUES ('2', '4');
     
    -- ----------------------------
    -- Table structure for user
    -- ----------------------------
    DROP TABLE IF EXISTS `user`;
    CREATE TABLE `user` (
      `user_id` int(11) NOT NULL,
      `password` varchar(255) DEFAULT NULL,
      `username` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
     
    -- ----------------------------
    -- Records of user
    -- ----------------------------
    INSERT INTO `user` VALUES ('1', '123', 'Jack');
    INSERT INTO `user` VALUES ('2', '123', 'Rose');
    INSERT INTO `user` VALUES ('3', '123', 'Paul');
     
    -- ----------------------------
    -- Table structure for user_role
    -- ----------------------------
    DROP TABLE IF EXISTS `user_role`;
    CREATE TABLE `user_role` (
      `user_id` int(11) NOT NULL,
      `role_id` int(11) NOT NULL,
      PRIMARY KEY (`user_id`,`role_id`),
      KEY `FKa68196081fvovjhkek5m97n3y` (`role_id`),
      CONSTRAINT `FK859n2jvi8ivhui0rl0esws6o` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`),
      CONSTRAINT `FKa68196081fvovjhkek5m97n3y` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
     
    -- ----------------------------
    -- Table structure for sys_token
    -- ----------------------------
    CREATE TABLE `sys_token` (
      `user_id` int(11) NOT NULL,
      `expire_time` datetime DEFAULT NULL,
      `token` varchar(255) DEFAULT NULL,
      `update_time` datetime DEFAULT NULL,
      PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    
    
    -- ----------------------------
    -- Records of user_role
    -- ----------------------------
    INSERT INTO `user_role` VALUES ('1', '1');
    INSERT INTO `user_role` VALUES ('2', '2');
    INSERT INTO `user_role` VALUES ('3', '3');

    测试类:因为我是用Swagger来测试,所以为了方便就直接传递token参数。具体开发时候可由前端把接收到的token放入Header。

    /**
     * @Author 大誌
     * @Date 2019/4/7 15:20
     * @Version 1.0
     */
    @RestController
    public class TestController {
    
        @RequiresPermissions({"save"}) //没有的话 AuthorizationException
        @PostMapping("/save")
        public Map<String, Object> save(String token) {
            System.out.println("save");
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("status", 200);
            map.put("msg", "当前用户有save的权力");
            return map;
        }
    
        @RequiresPermissions({"delete"}) //没有的话 AuthorizationException
        @DeleteMapping("/delete")
        public Map<String, Object> delete(String token) {
            System.out.println("delete");
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("status", 200);
            map.put("msg", "当前用户有delete的权力");
            return map;
        }
    
        @RequiresPermissions({"update"}) //没有的话 AuthorizationException
        @PutMapping("update")
        public Map<String, Object> update(String token) {
            System.out.println("update");
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("status", 200);
            map.put("msg", "当前用户有update的权力");
            return map;
        }
    
        @RequiresPermissions({"select"}) //没有的话 AuthorizationException
        @GetMapping("select")
        public Map<String, Object> select(String token, HttpSession session) {
            System.out.println("select");
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("status", 200);
            map.put("msg", "当前用户有select的权力");
            return map;
        }
    
        @RequiresRoles({"vip"}) //没有的话 AuthorizationException
        @GetMapping("/vip")
        public Map<String, Object> vip(String token) {
            System.out.println("vip");
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("status", 200);
            map.put("msg", "当前用户有VIP角色");
            return map;
        }
        @RequiresRoles({"svip"}) //没有的话 AuthorizationException
        @GetMapping("/svip")
        public Map<String, Object> svip(String token) {
            System.out.println("svip");
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("status", 200);
            map.put("msg", "当前用户有SVIP角色");
            return map;
        }
        @RequiresRoles({"p"}) //没有的话 AuthorizationException
        @GetMapping("/p")
        public Map<String, Object> p(String token) {
            System.out.println("p");
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("status", 200);
            map.put("msg", "当前用户有P角色");
            return map;
        }
    }
    

    ExceptionHandler 异常处理器,用于捕获无权限时候的异常。

    @ControllerAdvice
    public class MyExceptionHandler {
    
        @ExceptionHandler(value = AuthorizationException.class)
        @ResponseBody
        public Map<String, String> handleException(AuthorizationException e) {
            //e.printStackTrace();
            Map<String, String> result = new HashMap<String, String>();
            result.put("status", "400");
            //获取错误中中括号的内容
            String message = e.getMessage();
            String msg=message.substring(message.indexOf("[")+1,message.indexOf("]"));
            //判断是角色错误还是权限错误
            if (message.contains("role")) {
                result.put("msg", "对不起,您没有" + msg + "角色");
            } else if (message.contains("permission")) {
                result.put("msg", "对不起,您没有" + msg + "权限");
            } else {
                result.put("msg", "对不起,您的权限有误");
            }
            return result;
        }
    }

    启动项目来看看效果: 访问 localhost:9090/shiro/doc.html

    登陆失败:

    登陆成功:

    登录成功后会返回token,记得带上token访问以下接口

    有某个角色时候:

    没有某个角色的时候:

    有某个权力时候:

    没有某个权力的时候:

    退出系统

    原本的token就失效了,我们再访问原本可以访问的接口看看

    至此就已经进入尾声了

    2020/3/27 新编写了VUE+Element前端页面

    正常访问:

    非法访问:

    重点:当未登录时候访问项目内部页面,由前端控制路由返回登录页,并不会出现可恶的login.jsp,这里我们故意改变数据库token来展示效果。

    总结

    至于最后没有权利或角色返回的json字符串是因为他抛出AuthorizationException。可以自定义全局异常处理器进行处理。通过这种token达到即可达到前后端分离开发。各位客官,点个赞吧qaq。

    2019/11/26日修改:在后续开发中,发现shiro如果使用ShiroConfig中shiroFiltet的map进行权限或角色拦截,会出现只走登陆认证,不走授权认证的情况。这是个巨坑!后续再写一篇文章深究一下。解决方法:使用注解@RequiresRoles() 以及@RequiresPermissions()进行权限和角色拦截

    
        @Bean("shiroFilter")
        public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            shiroFilter.setSecurityManager(securityManager);
            //自定义过滤(关键)
            Map<String, Filter> filters = new HashMap<>();
            filters.put("auth", new AuthFilter());
            shiroFilter.setFilters(filters);
            Map<String, String> filterMap = new LinkedHashMap<>();
            //主要是这部分: 不要用这种方法,最好用注解的方法
            filterMap.put("/add", "roles[admin]");
            filterMap.put("/list", "roles[admin,user]");
            filterMap.put("/delete", "perms[admin:delete]");
    
            filterMap.put("/**", "auth");
            shiroFilter.setFilterChainDefinitionMap(filterMap);
    
            return shiroFilter;
        }
    

    2020/3/25补充,修改了部分不符合规范的代码,添加了全局异常捕获器。同时补充了校验流程。同时提示两句,因为token频繁在客户端和服务器端传输,因此可能会造成token劫持攻击(既黑客捕获你的token之后就可以代替你为所欲为),如果对这方面有安全隐患的担忧,可以采取每访问一次接口,更新一次token。并且我这里处于方便的原因是采用了mysql存储token,具体开发中应该用redis缓存来存储。


    有什么问题可以评论或者私信我,每日在线解(LIAO)疑(SAO)。

    我是大誌,一位准备996的卑微码农🐶,觉得好用记得点赞收藏!!!

    展开全文
  • springboot-整合shirovue的验证登录和权限

    万次阅读 热门讨论 2018-11-20 01:03:52
    springboot-整合shirovue的验证登录和权限 文章目录springboot-整合shirovue的验证登录和权限1.配置1.1数据表这里权限控制有5张表,详情见代码中的test.sql文件1.2工程说明:1.3springboot下的shiro配置1.4...
  • 前后端分离的权限管理综合demo,包含技术点:springboot、mybatis(使用tkmapper)、shirovue、bootstrap、axios、sweetalert、webpack
  • springboot+shiro+vue实现权限管理一 功能简介:1 介绍:二 开始接入:1 数据库设计2 导入依赖3 ShiroConfiguration : 设置拦截4 MyShiroRealm: 用户权限认证5 LoginService: 用户登录/退出6 权限使用7 权限...
  • spring boot + shiro + vue 登录

    千次阅读 2019-03-25 15:14:53
    目录 参考 axios使用 axios安装 axios请求使用 axios拦截器 springboot前后端分离 vue前端 项目目录结构 修改主配置参数 添加控制页面 ...spring boot 整合shiro 登录参考:spring bo...
  • springboot + shiro + vue 前后分离打包部署: 前端打包,放入后端resources下的static中,后端打包部署服务器就行 部署完成后,发现一个坑,就是页面无法访问,网上找了很多,最后发现是集成shiro的原因。...
  • Spring Boot-Shiro-Vue实现 github开源地址及系统演示地址: 由于头条提示不让发这些网址,有需要的朋友关注我私信回复【链接】获取吧!,觉得不错的朋友可以转发支持下! Spring Boot-Shiro-Vue 提供...
  • 最近在用springBoot,shirovue做一个前后端分离的学生管理系统,在此记录一下做的过程,以及遇到的问题。 GitHub代码后端地址:https://github.com/qiuxinfa/springboot-shiro-vue-stu GitHub代码前端地址:...
  • Shiro+Vue+SpringBoot实现权限管理系统

    千次阅读 2019-07-10 17:20:41
    1.Shiro+Vue+SpringBoot前后端分离实现权限管理系统。 用户模块 2.角色模块 3.菜单列表 4.视频连接地址https://edu.csdn.net/course/detail/24806 5.学习交流可以加qq群 6.目前还有一点需要收尾处理,...
  • SpringBoot+shiro+mybatis+Thymeleaf实现权限登录系统记录一下,学习shiro的一个小Demo:1.首先是底层数据库:-- ------------------------------ Table structure for role-- ----------------------------CREATE ...
  • 提供一套基于Spring Boot-Shiro-Vue的权限管理思路.前后端都加以控制,做到按钮/接口级别的权限,做了自己的实现,练了下手。前文见:前后端分离项目中权限控制的思考,以及实现过程中的瞎折腾:SpringBoot+Shiro瞎...
  • 是基于Spring Boot、ShiroVue等技术进行开发,主要是对后台管理系统常用模块前后端整体抽取构建成通用模板。也是集中对权限管理进行再次封装,以至于简单操作,便于二次开发代码重复。主要实现管理员管理、角色...
  • 包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、...
  • 前端部分:https://github.com/Wisdom-Bao/shiro-vue.git 一、登录注册 关键代码: @RequestMapping("login") @ResponseBody public String login(String username,String password){ Subject subject = ...
  • GitHub代码后端地址:https://github.com/qiuxinfa/springboot-shiro-vue-stu GitHub代码前端地址:https://github.com/qiuxinfa/boot-shiro-vue-stu-client 这里主要讲讲利用AOP实现请求拦截,并进行日志记录和...
  • 再搞权限系统的时候,权限控制到菜单很容易,但是很多情况要控制到...Spring Boot-Shiro-Vue实现github开源地址:https://github.com/Heeexy/SpringBoot-Shiro-Vue系统演示地址:http://g.heeexy.com/Spring Boot-...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,161
精华内容 464
关键字:

shirovue

vue 订阅