Code Monkey home page Code Monkey logo

spring-shiro-spark's People

Contributors

zhuxs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spring-shiro-spark's Issues

前后端分离下的用户认证和鉴权实践(一) 概述

为什么要前后端分离

传统后端MVC架构的弊端

  • 前端代码越来越复杂

    • js和css依赖与后台返回的Html
    • 代码之间充满了各种约定,前后端协作困难
    • 部分业务逻辑散落在前端
  • 前后端高度耦合

    • 前端依赖后端的开发环境
    • 在后端的View层高度耦合
    • 职责不清晰
  • 分工问题

    • 沟通和维护的成本上升
    • 无法快速正确的相应变化

前后端分离实践

前后端的划分不应是从硬件上划分,

image

而是应该在职责上进行划分,如

image

后端负责

  • 提供接口
  • 封装业务逻辑
  • 维护数据

前端负责

  • 用户交互,渲染界面
  • 转发数据,串接服务
  • 路由设计,控制逻辑

前后端分离给权限管理带来的挑战

前端要控制页面的跳转和渲染

  • 在一个页面中,用户只能看到其权限相关的组件和菜单项。
  • 依照用户的权限对用户的路由跳转加以控制,当前用户无权访问的url不能暴露给用户。
  • 要避免用户可以访问界面,在调用后端接口时却返回无权限的情况。

后端要控制每一个接口的访问权限

  • 用户登录系统需要认证和授权
  • 每一个接口的访问都应该有相应的权限控制
  • 保持用户的认证和授权状态
  • 单点登录

前后端交互

  • 前端调用后端接口要携带权限信息
  • 后端要对前端请求进行权限验证

权限粒度的设计

主要技术选型

  • 后端

    • Spring Boot
    • Shiro
    • Hibernate
    • Mysql
    • Redis
  • 前端

    • Vue
    • Vue-Router
    • VueX
  • 标准

    • RestFul

前后端分离下的用户认证和鉴权实践(三) Shiro简介

Shiro

  • Apache Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro 要简单的多,可以与Spring进行无缝集成。
    image

Shiro的几个关键概念

  • Subject:一个安全术语,意指“当前用户”,这里的用户可以指人、第三方进程等其他事物。一旦获得Subject,你就可以立即获得你希望用Shiro为当前用户做的90%的事情,如登录、登出、访问会话、执行授权检查等 -。这里的关键点是Shiro的API非常直观,因为它反映了开发者以‘每个用户’思考安全控制的自然趋势。同时,在代码的任何地方都能很轻松地访问Subject,允许在任何需要的地方进行安全操作。
    Subject subject = SecurityUtils.getSubject();
    subject.login(token);
  • SecurityManager:shiro框架的核心,管理所有用户更的安全操作,引用了多个内部嵌套安全组件(如Session的管理器,Realm等)
    @Bean(name = "securityManager")
     public DefaultWebSecurityManager securityManager(){
         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
         //设置Realm
         securityManager.setRealm(shiroRealm());
         //设置session管理器
         securityManager.setSessionManager(sessionManager());
         return securityManager;
     }
  • Realm:Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”,也就是说,当与用户帐户这类安全相关数据进行交互,执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找很多内容。
    Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
    Realm有两个关键方法doGetAuthenticationInfo(AuthenticationToken authenticationToken)执行认证逻辑;doGetAuthorizationInfo(PrincipalCollection principalCollection)执行授权逻辑。
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //赋予角色和权限
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) 
    throws AuthenticationException {
       //用户认证
    }
  • Filter链:Shiro通过其创新的URL过滤器链功能支持安全特定的过滤规则,它允许你为任何匹配的URL模式指定非正式的过滤器链。
    filterChainDefinitionManager.put("/userInfo","authc");  //需要登录
    filterChainDefinitionManager.put("/jobs/**","perms[JOB:CREATE]");  //需要[JOB:CREATE]权限
    filterChainDefinitionManager.put("/admin/**","roles[Admin]");  //需要Admin角色

相关资料

前后端分离下的用户认证和鉴权实践(四) 基于shiro的后端认证和鉴权

对于访问后端服务器的所有请求,都要进行认证和鉴权

  • 认证:当前用户是否登录,是否为系统的合法用户
  • 鉴权:当前用户是否具有调用当前接口的权限

对于登录到系统的用户,首先要进行认证和授权。

  • 根据用户输入的用户名密码进行身份验证
  • 查找该用户所具有的Role和Permission,并将其赋给当前用户
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
          //获取当前用户
           UserDto user = (UserDto) SecurityUtils.getSubject().getSession().getAttribute("user");
    
          //把principals放session中,key=userId value=principals
          SecurityUtils.getSubject().getSession().setAttribute(String.valueOf(user.getId()),SecurityUtils.getSubject().getPrincipals());
    
          SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
          //赋予角色
          for(RoleDto role:user.getRoles()){
              info.addRole(role.getName());
          }
          //赋予权限
          for(PermissionDto permission:user.getPermissions()){
              //System.out.println(permission.getName());
              info.addStringPermission(permission.getName());
          }
          return info;
      }
    
      @Override
      protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
          UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
          String userName = token.getUsername();
          User user = userDao.findUserByUsername(userName);
          UserDto userDto = convertToDto(user);
          if(user != null){
              //登陆成功
              Session session = SecurityUtils.getSubject().getSession();
              session.setAttribute("user",userDto);
                          return new SimpleAuthenticationInfo(
                      userName, //用户
                      user.getPassword(), //密码
                      getName() //realm name
              );
          } else {
              throw new UnknownAccountException();
          }
      }

服务器要配置Filter链以进行认证和鉴权,对用户的访问和重定向等进行限制

  • 认证失败、鉴权失败如何重定向
  • 哪些接口需要哪些权限和角色才能够访问
    Bean(name = "shiroFilter")
      public ShiroFilterFactoryBean shiroFilterFactoryBean(){
          ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
          shiroFilterFactoryBean.setSecurityManager(securityManager());
    
          Map<String, Filter> filters = new LinkedHashMap<String,Filter>();
          LogoutFilter logoutFilter = new LogoutFilter();
          logoutFilter.setRedirectUrl("/login");
          shiroFilterFactoryBean.setFilters(filters);
          shiroFilterFactoryBean.setLoginUrl("/notAuthc");
    
          Map<String,String> filterChainDefinitionManager = new LinkedHashMap<String,String>();
          filterChainDefinitionManager.put("/logout","logout");
          filterChainDefinitionManager.put("/userInfo","authc");
          filterChainDefinitionManager.put("/jobs/**","perms[WORDCOUNT:CREATE]");
          filterChainDefinitionManager.put("/admin/**","roles[Admin]");
          shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);
    
          shiroFilterFactoryBean.setSuccessUrl("/");
          shiroFilterFactoryBean.setUnauthorizedUrl("/notAuthz");
          return shiroFilterFactoryBean;
      }

一个用户访问后端接口的完整过程

  • 登录请求
  • 认证
  • 授权
  • 访问接口
  • 认证、鉴权
    • 成功则允许调用
    • 不成功则重定向请求到相应url

前后端分离下的用户认证和鉴权实践(六) 前端的路由控制和动态渲染

前端要根据用户权限进行路由控制,并对组件进行动态渲染

  • 在一个页面中,用户只能看到其权限相关的组件和菜单项。
  • 依照用户的权限对用户的路由跳转加以控制,当前用户无权访问的url不能暴露给用户。
  • 要避免用户可以访问界面,在调用后端接口时却返回无权限的情况。

挑战

  • 由于前后端分离,后端无法对前端的路由进行控制
  • 前端页面刷新后,数据无法保持
  • 不同的用户需要根据其权限动态挂载不同的路由

解决方案

  • 利用Vuex维护用户的Role、Permission等相关用户信息,方便在不同的组件之间进行状态共享管理
    const getters = {
      status: state => state.user.status,
      username: state => state.user.username,
      name: state => state.user.name,
      roles: state => state.user.roles,
      addRouters: state => state.permission.addRouters
    }
  • 根据路由中的meta字段,结合用户的权限信息,过滤该用户有权限的路由表
    {
          path: "/admin",
          component: _import('admin/index'),
          //redirect: '/dashboard',
          meta:{
              role: ['Admin']
          },
          name: "admin"
    }
  • 利用VueRouter的addRoutes()新特性,动态挂载路由
  
  function filterAsyncRouter(asyncRouterMap,roles) {
      const accessedRouters = asyncRouterMap.filter(route => {
          if(hasPermission(roles,route)){
              if(route.children && route.children.length){
                  route.children = filterAsyncRouter(route.children,roles)
              }
              return true
          }
          return false
      })
      return accessedRouters
  }

总结

  • 预先定义所有的路由,包括通用路由和权限限制的路由,在有权限限制的路由的meta字段中添加权限信息
  • 用户登录后获取用户的权限信息并保存
  • 根据用户的权限信息过滤路由
  • 挂载过滤得到的路由

前后端分离下的用户认证和鉴权实践(五) 用户登录状态的保持和单点登录

用户登录状态的保持和单点登录

  • 服务器应该对用户的登录状态有保持机制。即在用户登录成功之后的一段时间内,用户无需重复登录即可访问权限允许的接口
  • 在有多个应用服务器的情况下,用户的登录状态能够在多个互相信任的服务器之间共享(即单点登录)

挑战

  • Http请求是无状态的,协议对于事务处理没有记忆能力,人生只若初见
  • 多台硬件服务器之间的缓存无法共享
  • 用户的权限和角色信息如何体现在请求中

解决方案

  • 利用Session机制,浏览器端发送的请求携带一个SessionId
  • 将用户的权限和角色信息存储到session之中
  • Session缓存到Redis中,在服务器集群之间共享

实现

  • shiro简介中提到,SecurityManager引用了多个安全管理组件,其中就包括SessionManager,即会话管理器
  • SessionManager要设置相应SessionFactory和SessionDao
    @Bean(name = "sessionManager")
        public DefaultWebSessionManager sessionManager(){
            DefaultWebSessionManager manager = new DefaultWebSessionManager();
            //manager.setCacheManager(cacheManager);// 加入缓存管理器
            manager.setSessionFactory(shiroSessionFactory());//设置sessionFactory
            manager.setSessionDAO(shiroSessionDao());// 设置SessionDao
            manager.setDeleteInvalidSessions(true);// 删除过期的session
            manager.setGlobalSessionTimeout(shiroSessionDao().getExpireTime());// 设置全局session超时
    时间
            manager.setSessionValidationSchedulerEnabled(true);// 是否定时检查session
            return manager;
        }
  • SessionFactory负责生成自定义的Session
  • SessionDao中规定了session的CRUD操作,也正是在这里将Session存储到Redis中
    @Override
      protected Serializable doCreate(Session session) {
          Serializable sessionId = this.generateSessionId(session);
          assignSessionId(session,sessionId);
          Jedis jedis = null;
          try{
              jedis = jedisPool.getResource();
              //session由Redis缓存失效决定,这里作简单标识
              session.setTimeout(expireTime);
              jedis.setex(prefix + sessionId, expireTime, SerializeUtils.serializaToString((ShiroSession) session));
              logger.info("sessionId {} name {} 被创建", sessionId, session.getClass().getName());
          }catch (Exception e){
              logger.warn("创建session失败",e);
          }finally {
              jedis.close();
          }
          return sessionId;
      }
    • doUpdate(),更新session 时执行
    • doDelete(),删除Session时执行
    • doReadSession,读取Session时执行

优势

  • 用户不必重复登录以获取权限
  • 用户的Session在多个服务器之间共享,实现单点登录

Spring Boot与Spark的集成实践

Spring Boot与Spark的集成实践

Spark程序的入口

Spark与Spring Boot集成的关键是能够在Spring的容器中提供一个Spark的入口。

SparkContext是Spark的入口,是程序与Spark集群交互的桥梁。在Spark 2.0引入了SparkSession,为用户提供了一个更加统一的切入点来使用Spark的各项功能,并且允许用户通过调用DataFrame和DataSet相关API来编写Spark程序,在本例中我们采用SparkSession来作为Spark的入口。

SparkSession依赖于SparkContext,而SparkContext依赖于SparkConf。因此我们创建这三个对象,并以Bean的形式存放到Spring的容器之中。
以SparkSession为例

@Bean
public SparkSession sparkSession(){
        return SparkSession
                .builder()
                .sparkContext(javaSparkContext().sc())
                .appName("Java Spark SQL basic example")
                .getOrCreate();
}

调用Spark Api

Spark提供了面向Java的Api,所以我们可以直接拿来用。

我们在SpringBoot的Configuration Class声明SparkSession、SparkContext、SparkConf这三个Bean,使得容器在启动时创建这三个对象。

我们在Service层注入SparkSession,就可以顺利使用Spark的各项功能了。

@Autowired
private SparkSession sparkSession;

WordCount示例

 List<Word> wordList = Arrays.stream(tempWords).map(Word::new).collect(Collectors.toList());
            dataFrame = sparkSession.createDataFrame(wordList,Word.class);
            dataFrame.show();

创建DataFrame

RelationalGroupedDataset groupedDataset = dataFrame.groupBy(col("word"));
            List<Row> rows = groupedDataset.count().collectAsList();
return rows.stream().map(new Function<Row, Count>() {
                @Override
                public Count apply(Row row) {
                    return new Count(row.getString(0),row.getLong(1));
                }
            }).sorted(new CountComparator()).collect(Collectors.toList());

Count

详情请见WordCount

项目地址

前后端分离下的用户认证和鉴权实践(二) 权限粒度的设计

我们为什么要区分用户权限的粒度

在不同的应用场景中,我们需要要对用户进行不同粒度的权限控制,如

  • 我们要区分对不同种类资源的权限。对于User和Job这两种不同资源的操作显然需要不同的权限
  • 我们要区分对同一种类不同实体资源的权限。对于Project1和Project2这两个不同的实体,需要加以不同的权限控制
  • 我们要区分对同一种类同一实体不同操作(读、删、改、创)的权限。对于Project这一种资源类型,创建操作(Post)和读操作(Read)需要不同的权限;对于同一个实体,更新操作(Put)和读操作(Read)也需要不同的权限

批量控制权限

我们需要对于权限进行批量的操作。想象这样一种应用场景,你是QQ的不锈钢贵族,你拥有10个不同的权限,同样的不锈钢贵族还有很多。难道QQ对每一个不锈钢贵族的用户都要维护其与10个权限的关联关系么,显然不是这样。这样会造成一定的数据冗余,因为每一个不锈钢贵族与权限的关联关系都是相同的,没有必要维护多次,冗余度会随着不锈钢贵族对应的权限数量的增长而变得更加严重;除此之外,还会对用户的授权和撤权造成一定的麻烦。

因此我们需要引入Role(角色)的概念,Role与Permission建立关联,User再与Role建立关联。

权限粒度设计

综上,有关权限管理,共需要三种Entity

  • User
  • Role
  • Permission
    image

User 和 Role、Permission均是多对多的关系

  • User可以拥有多种 Role,一个Role也可以赋给多个用户
  • User可以拥有多种Permission,一个Permission可以赋给多个用户

Role 和 Permission之间是多对多的关系

  • Role可以拥有多个Permission,一个Permission也可以赋给多种Role

Permission 的设计

image

  • type:资源的类型
  • id:特定资源的实体id
  • action:操作类型(GET,DELETE,PUT,POST)
  • resource:type:id,如Project:1
  • name:resource:action,如Project:1:GET,表示对id为1的Project的读权限;Job:Post,表示创建Job 的权限。

实现

使用Hibernate建立实体和实体之间的关联关系

注意三者之间的多对多的关联关系

  • User.java
    @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
    @JoinTable(name = "user_role",
            joinColumns = {@JoinColumn(name = "userId",referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "roleId",referencedColumnName = "id")})
    private List<Role> roles = new ArrayList<>();

    @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
    @JoinTable(name = "user_permission",
            joinColumns = {@JoinColumn(name = "userId",referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "permissionId",referencedColumnName = "id")})
    private List<Permission> permissions = new ArrayList<>(); 
  • Role.java
    @ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
    private List<User> users = new ArrayList<>();

    @ManyToMany(cascade = CascadeType.MERGE,fetch = FetchType.LAZY)
    @JoinTable(name = "role_permission",
            joinColumns = {@JoinColumn(name = "roleId",referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "permissionId",referencedColumnName = "id")})
    @CollectionId(columns = @Column(name = "id"),
            type = @Type(type = "long"),
            generator = "identity")
    private List<Permission> permissions = new ArrayList<>();
  • Permission.java
    @ManyToMany(mappedBy = "permissions",fetch = FetchType.LAZY)
    private List<Role> roles = new ArrayList<>();

    @ManyToMany(mappedBy = "permissions",fetch = FetchType.LAZY)
    private List<User> users = new ArrayList<>();

前后端分离下的用户认证和鉴权实践(三) shiro简介

Shiro

  • Apache Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro 要简单的多,可以与Spring进行无缝集成。
    image

Shiro的几个关键概念

  • Subject:一个安全术语,意指“当前用户”,这里的用户可以指人、第三方进程等其他事物。一旦获得Subject,你就可以立即获得你希望用Shiro为当前用户做的90%的事情,如登录、登出、访问会话、执行授权检查等 -。这里的关键点是Shiro的API非常直观,因为它反映了开发者以‘每个用户’思考安全控制的自然趋势。同时,在代码的任何地方都能很轻松地访问Subject,允许在任何需要的地方进行安全操作。
    Subject subject = SecurityUtils.getSubject();
    subject.login(token);
  • SecurityManager:shiro框架的核心,管理所有用户更的安全操作,引用了多个内部嵌套安全组件(如Session的管理器,Realm等)
    @Bean(name = "securityManager")
     public DefaultWebSecurityManager securityManager(){
         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
         //设置Realm
         securityManager.setRealm(shiroRealm());
         //设置session管理器
         securityManager.setSessionManager(sessionManager());
         return securityManager;
     }
  • Realm:Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”,也就是说,当与用户帐户这类安全相关数据进行交互,执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找很多内容。
    Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
    Realm有两个关键方法doGetAuthenticationInfo(AuthenticationToken authenticationToken)执行认证逻辑;doGetAuthorizationInfo(PrincipalCollection principalCollection)执行授权逻辑。
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //赋予角色和权限
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) 
    throws AuthenticationException {
       //用户认证
    }
  • Filter链:Shiro通过其创新的URL过滤器链功能支持安全特定的过滤规则,它允许你为任何匹配的URL模式指定非正式的过滤器链。
    filterChainDefinitionManager.put("/userInfo","authc");  //需要登录
    filterChainDefinitionManager.put("/jobs/**","perms[JOB:CREATE]");  //需要[JOB:CREATE]权限
    filterChainDefinitionManager.put("/admin/**","roles[Admin]");  //需要Admin角色

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.