zoukankan      html  css  js  c++  java
  • springboot+shiro

    项目背景:该项目用来为app项目提供数据,在项目中只返回接口数据,不返回视图,并禁用了session,自定义了过滤器和token。

    项目中使用的技术:springboot+maven+shiro。需要先了解一哈。

    首先对shiro框架做个说明:

      Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。其中有3个核心组件,Subject, SecurityManager 和 Realms。

      Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
      Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
      SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
      Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

      Realm是整个框架中为数不多的必须由设计者自行实现的模块。

    先新建一个springboot项目,创建后配置好maven,项目结构如下:

      

    其中的service,mapper等是用来整合mysql,本次项目就简暂不使用。

    项目pom.xml文件引用jar包配置如下

    <dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    
    		<!-- log4j -->
    		<dependency>
    			<groupId>org.slf4j</groupId>
    			<artifactId>slf4j-log4j12</artifactId>
    			<version>1.7.2</version>
    		</dependency>
    		<!-- shiro  -->
    		<dependency>
    			<groupId>org.apache.shiro</groupId>
    			<artifactId>shiro-spring-boot-starter</artifactId>
    			<version>1.4.0</version>
    		</dependency>
    		<!-- google  -->
    		<dependency>
    			<groupId>com.google.guava</groupId>
    			<artifactId>guava</artifactId>
    			<version>18.0</version>
    		</dependency>
    		<!-- mysql -->
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<scope>runtime</scope>
    		</dependency>
    
    		<dependency>
    			<groupId>com.alibaba</groupId>
    			<artifactId>fastjson</artifactId>
    			<version>1.2.47</version>
    		</dependency>
    
    	</dependencies>
    

      在shiro文件夹下新建JWTToken

    /**
     * 自定义shiro的token
     */
    public class JWTToken implements AuthenticationToken {
    
        /**
         * 密钥
         */
        private String token;
    
        public JWTToken(String token){
            this.token = token;
        }
    
        @Override
        public Object getPrincipal() {
            return token;
        }
    
        @Override
        public Object getCredentials() {
            return token;
        }
    }

    新建MyShiroRealm,定义权限,在app上,就如首页是无需用户登录的,即首页是用户登录不登录都是可以访问的,但是用户的个人信息就需要登录才能访问。

    /**
     * 自定义Realm <领域>
     */
    public class MyShiroRealm extends AuthorizingRealm{
    
        /**
         * 使用自定义token
         */
        @Override
        public boolean supports(AuthenticationToken token) {
            return token instanceof JWTToken;
        }
    
        /**
         * 权限配置
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            System.out.println("判断用户是否有权限访问此连接");
            String url = request.getRequestURI();
            String method = request.getMethod();
            System.out.println(url+"------------"+method);
            //设计的验证权限的逻辑内容
            return authorizationInfo;
        }
    
        /**
         * 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            // 获取用户的token
            String authorization = String.valueOf(token.getPrincipal());
            //这里验证token和simpleAuthenticationInfo的信息
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(authorization, authorization, getName());
         //暂定密码为111,应该根据信息查询用户并判断用户的身份
    if (!authorization.equals("111")){ throw new AuthenticationException("密码错误!"); } return authenticationInfo; } }

    新建StatelessDefaultSubjectFactory

    public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
    
        @Override
        public Subject createSubject(SubjectContext context) {
            // 不创建session
            context.setSessionCreationEnabled(false);
            return super.createSubject(context);
        }
    
    }

    新建JWTFilter,过滤器。

    /**
     * 自定义jwt过滤器
     */
    public class JWTFilter extends AccessControlFilter {
    
        private Logger LOGGER     = LoggerFactory.getLogger(this.getClass());
    
        //过滤器过滤用户是否有权限
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            LOGGER.info("当前用户访问url:" + httpRequest.getRequestURL().toString());
            //根据Header取出相关信息,之后放入Token
            String authorization = httpRequest.getHeader("authorization");
            try {
                if (StringUtils.isEmpty(authorization)) {
                    throw new AuthenticationException("请求错误!");
                }
                // 获取无状态Token
                JWTToken jwtToken = new JWTToken(authorization);
                // 委托给Realm进行登录,会跳转doGetAuthenticationInfo方法进行判断用户
                getSubject(request, response).login(jwtToken);
                boolean isContinue = true;
           //这里对需要用户登录但试无需访问权限的公共url进行了判断
    if (httpRequest.getRequestURI().indexOf("index") >= 0){ isContinue = false; } /************ 公用的配置跳过权限验证 end ****************/ if (isContinue) { // 通过isPermitted 才能调用doGetAuthorizationInfo方法获取权限信息 getSubject(request, response).isPermitted(httpRequest.getRequestURI()); } } catch (AuthenticationException e) { response401(httpRequest, response, e.getMessage()); return false; } return true; } //在访问被拒绝运行 @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { System.out.println("请求被拒绝"); return false; } /** * 将非法请求跳转到 /401 */ private void response401(ServletRequest req, ServletResponse resp, String message) { try { req.setAttribute("message", message); req.getRequestDispatcher("/401").forward(req, resp); } catch (IOException ex) { LOGGER.error(ex.getMessage()); } catch (ServletException e1) { LOGGER.error(e1.getMessage()); } return; } }

    新建GlobalExceptionResolver

    /**
     * 异常处理
     */
    public class GlobalExceptionResolver implements HandlerExceptionResolver {
    
        private static Logger logger = LoggerFactory.getLogger(GlobalExceptionResolver.class);
    
        @Override
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
                                             Exception ex) {
            ModelAndView mv;
            // 进行异常判断。如果捕获异常请求跳转。
            if (ex instanceof UnauthorizedException) {
                mv = new ModelAndView("/user/unauth");
                return mv;
            } else {
                mv = new ModelAndView();
                FastJsonJsonView view = new FastJsonJsonView();
                // ex.printStackTrace();
                logger.error("shiro错误!!", ex);
                Map<String, Object> map = new HashMap<>();
                String beanString = JSON.toJSONString("服务器异常");
                map = JSON.parseObject(beanString, Map.class);
                view.setAttributesMap(map);
                mv.setView(view);
                return mv;
            }
    
        }
    }

    新建ShiroConfig

    @Configuration
    public class ShiroConfig {
    
        private final static Logger LOGGER = LoggerFactory.getLogger(ShiroConfig.class);
    
        //将自己的验证方式加入容器
        @Bean(name = "customRealm")
        public MyShiroRealm customRealm() {
            MyShiroRealm myShiroRealm = new MyShiroRealm();
            return myShiroRealm;
        }
    
        //权限管理,配置主要是Realm的管理认证
        @Bean(name = "securityManager")
        public DefaultWebSecurityManager defaultWebSecurityManager(MyShiroRealm customRealm) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    
            StatelessDefaultSubjectFactory statelessDefaultSubjectFactory = new StatelessDefaultSubjectFactory();
            securityManager.setSubjectFactory(statelessDefaultSubjectFactory);
    
            /*
             * 关闭shiro自带的session,详情见文档
             */
            DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
            DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
            defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
            subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
            securityManager.setSubjectDAO(subjectDAO);
         //将自定义的realm让其管理使用
            securityManager.setRealm(customRealm);
            securityManager.setSessionManager(defaultSessionManager());
            return securityManager;
        }
    
        /**
         * 会话管理类 禁用session
         */
        @Bean
        public DefaultSessionManager defaultSessionManager() {
            LOGGER.info("=============ShiroConfig.getDefaultSessionManager()======");
            DefaultSessionManager manager = new DefaultSessionManager();
            manager.setSessionValidationSchedulerEnabled(false);
            return manager;
        }
    
        //Filter工厂,设置对应的过滤条件和跳转条件
        @Bean(name = "shiroFilter")
        public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
            LOGGER.info("===========init shiroFilterFactory============");
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            // 添加自己的过滤器并且取名为jwt
            Map<String, Filter> filterMap = Maps.newHashMap();
            filterMap.put("jwtFilter", new JWTFilter());
            shiroFilterFactoryBean.setFilters(filterMap);
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            Map<String, String> filterChainDefinitionMap = Maps.newLinkedHashMap();
            // 注意过滤器配置顺序 不能颠倒
            // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
            filterChainDefinitionMap.put("/401", "anon");
    
            // 配置不会被拦截的链接 顺序判断
    //        filterChainDefinitionMap.put("/static/**", "anon");
            filterChainDefinitionMap.put("/logout", "anon");
            filterChainDefinitionMap.put("/login", "anon");
         //除放行的url外,其他的url都让JWTFilter进行拦截过滤 filterChainDefinitionMap.put(
    "/**","jwtFilter"); // 配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据 // shiroFilterFactoryBean.setLoginUrl("/logout"); //错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/401"); // 登录成功后要跳转的链接 // shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授权界面 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } //加入注解的使用,不加入这个注解不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 注册全局异常处理 * * @return */ @Bean(name = "exceptionHandler") public HandlerExceptionResolver handlerExceptionResolver() { return new GlobalExceptionResolver(); } }

    新建UserController

    @RestController
    @RequestMapping("/user")
    public class UserController{
    
        @GetMapping("/insert")
        public String insert(@RequestParam(value = "username", required = false)String username,@RequestParam(value = "password", required = false)String password){
            System.out.println(username+":"+password);
            return "ok";
        }
    }

    启动项目,进行访问

    header中需要传anthorization参数并赋值为111,我们在isAccessAllowed方法中获取header并在doGetAuthenticationInfo方法中进行了密码效验。

    控制台输出

    可以在shiroFilterFactoryBean方法中进行放行,不再拦截过滤。

    再次访问,输出为

    可以看到没有对当前url进行拦截了。

  • 相关阅读:
    C++读写文件并排序
    我的vim配置---jeffy-vim-v2.2.tar
    vim 代码注释插件
    我的vim配置---jeffy-vim-v2.1.tar
    linux中screen命令的用法
    Install and Enable Telnet server in Ubuntu Linux
    Telnet环境变量
    Telnet窗口尺寸选项
    TELNET终端类型选项
    Telnet技术白皮书
  • 原文地址:https://www.cnblogs.com/yinduang/p/10101332.html
Copyright © 2011-2022 走看看