整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Springboot+Shiro 基于URL 动态控

Springboot+Shiro 基于URL 动态控制权限

言:

权限控制有 注解的方式,jsp shiro标签的方式,还有url 动态控制的方式。这里我使用最后一种方式来控制权限

思路:

0.利用 PathMatchingFilter 拦截器

1.根据用户名 来查询角色,

2.根据角色查询权限

3.获取请求的url

4判断 根据用户名查询的权限 是否包括 请求的url

5.如果包括 则 放行,不包括重定向到 未授权界面

package com.example.springboot.shiro.core.shiro.filter;

import com.example.springboot.shiro.common.utils.SpringContextUtil;
import com.example.springboot.shiro.core.shiro.token.TokenManager;
import com.example.springboot.shiro.user.entity.Upermission;
import com.example.springboot.shiro.user.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.List;

/**
 *  权限 拦截策略
 */
public class URLPathMatchingFilter extends PathMatchingFilter {
    @Autowired
    LoginService loginService;

    @Override
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {

          if (loginService==null){
              loginService=SpringContextUtil.getContext().getBean(LoginService.class);
          }
        //请求的url
        String requestURL=getPathWithinApplication(request);
        System.out.println("请求的url :"+requestURL);
        Subject subject=SecurityUtils.getSubject();
        if (!subject.isAuthenticated()){
            // 如果没有登录, 直接返回true 进入登录流程
            return  true;
        }
        String email=TokenManager.getEmail();
        List<Upermission> permissions=loginService. upermissions(email);

        boolean hasPermission=false;
        for (Upermission url : permissions) {

              if (url.getUrl().equals(requestURL)){
                  hasPermission=true;
                  break;
              }
        }
        if (hasPermission){
            return true;
        }else {
            UnauthorizedException ex=new UnauthorizedException("当前用户没有访问路径" + requestURL + "的权限");
            subject.getSession().setAttribute("ex",ex);
            WebUtils.issueRedirect(request, response, "/unauthorized");
            return false;

        }

    }
}

配置 : ShiroConfiguration(shiro 配置类)

package com.example.springboot.shiro.core.shiro.config;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import com.example.springboot.shiro.core.shiro.filter.KickoutSessionControlFilter;
import com.example.springboot.shiro.core.shiro.filter.SessionControlInterceptor;
import com.example.springboot.shiro.core.shiro.filter.SessionFilter;
import com.example.springboot.shiro.core.shiro.filter.URLPathMatchingFilter;
import com.example.springboot.shiro.core.shiro.token.SampleRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.crazycake.shiro.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;

import javax.servlet.Filter;

@Configuration    //Shiro配置类
public class ShiroConfiguration {
    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")

    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;

    @Bean
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }


    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     * <p>
     * Filter Chain定义说明
     * 1、一个URL可以配置多个Filter,使用逗号分隔
     * 2、当设置多个过滤器时,全部验证通过,才视为通过
     * 3、部分过滤器可指定参数,如perms,roles
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接(没用,在js中跳转了)
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授权界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        //拦截器.
        Map<String, String> filterChainDefinitionMap=new LinkedHashMap<String, String>();
        //自定义拦截器
        Map<String, Filter> filtersMap=new LinkedHashMap<String, Filter>();
        //限制同一帐号同时在线的个数。
        filtersMap.put("kickout", kickoutSessionControlFilter());
        //访问权限配置
        filtersMap.put("requestURL", getURLPathMatchingFilter());

        shiroFilterFactoryBean.setFilters(filtersMap);
       /* 配置映射关系*/
        //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/index", "authc");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/updateSelf", "authc");
        filterChainDefinitionMap.put("/updatePswd", "authc");
        filterChainDefinitionMap.put("/mypermission", "authc");
        filterChainDefinitionMap.put("/kickout", "anon");
        filterChainDefinitionMap.put("/list", "authc");
        filterChainDefinitionMap.put("/online", "authc");
        filterChainDefinitionMap.put("/role", "authc");
        filterChainDefinitionMap.put("/Roleassignment", "authc");
        filterChainDefinitionMap.put("/permissionlist", "authc");
        filterChainDefinitionMap.put("/PermissionAssignment", "authc");

        /*加入自定义过滤器*/
        filterChainDefinitionMap.put("/**", "kickout");
        //下面的配置路径 都需要在上面配置 authc 否则访问不到filter
        filterChainDefinitionMap.put("/online","requestURL");
        filterChainDefinitionMap.put("/list", "requestURL");
        filterChainDefinitionMap.put("/role", "requestURL");
        filterChainDefinitionMap.put("/Roleassignment", "requestURL");
        filterChainDefinitionMap.put("/permissionlist", "requestURL");
        filterChainDefinitionMap.put("/PermissionAssignment", "requestURL");


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;

    }

    /**
     *   访问 权限 拦截器
     * @return
     */
    public URLPathMatchingFilter getURLPathMatchingFilter() {
        return new URLPathMatchingFilter();
    }

    /**
     * 自定义域
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //设置realm.
        securityManager.setRealm(getDatabaseRealm());

        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        securityManager.setSessionManager(sessionManager());
        //注入记住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    /**
     * 授权&认证
     *
     * @return
     */
    @Bean
    public SampleRealm getDatabaseRealm() {
        SampleRealm myShiroRealm=new SampleRealm();
        System.out.println("myShiroRealm");
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }


    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * 所以我们需要修改下doGetAuthenticationInfo中的代码;
     * )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher();

        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        //  hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));

        return hashedCredentialsMatcher;
    }


    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager=new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }


    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    public RedisManager redisManager() {
        RedisManager redisManager=new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setExpire(1800);// 配置缓存过期时间
        redisManager.setTimeout(timeout);
        // redisManager.setPassword(password);
        return redisManager;
    }

    /**
     * Session Manager
     * 使用的是shiro-redis开源插件
     */
    @Bean(name="sessionManager")
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO=new RedisSessionDAO();

        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * cookie管理对象;记住我功能
     *
     * @return
     */
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }

    /**
     * cookie对象;
     *
     * @return
     */
    public SimpleCookie rememberMeCookie() {
        //这个参数是cookie的名称,对应前端的checkbox的name=rememberMe
        SimpleCookie simpleCookie=new SimpleCookie("rememberMe");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }

    /**
     * 限制同一账号登录同时登录人数控制
     *
     * @return
     */
    public KickoutSessionControlFilter kickoutSessionControlFilter() {
        KickoutSessionControlFilter kickoutSessionControlFilter=new KickoutSessionControlFilter();
        //使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;
        //这里我们还是用之前shiro使用的redisManager()实现的cacheManager()缓存管理
        //也可以重新另写一个,重新配置缓存时间之类的自定义缓存属性
        kickoutSessionControlFilter.setCacheManager(cacheManager());
        //用于根据会话ID,获取会话进行踢出操作的;
        kickoutSessionControlFilter.setSessionManager(sessionManager());
        //是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序。
        kickoutSessionControlFilter.setKickoutAfter(false);
        //同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;
        kickoutSessionControlFilter.setMaxSession(1);
        //被踢出后重定向到的地址;
        kickoutSessionControlFilter.setKickoutUrl("kickout");
        return kickoutSessionControlFilter;
    }
}

未授权 异常捕获:

Shiro 是 JAVA 世界中新近出现的权限框架,较之 JAAS 和 Spring Security,Shiro 在保持强大功能的同时,还在简单性和灵活性方面拥有巨大优势。本文就带领读者一睹 Shiro 的风采。

可能大家早先会见过 J-security,这个是 Shiro 的前身。在 2009 年 3 月初之前,这个安全框架叫做 J-security,由于某些原因,更名为 Shiro(或者 Ki,意为 Fortress),是 Apache 的孵化项目,鉴于本文编写时 Shiro 的还没有正式发布的版本,本文使用的是 Jsecurity 的稳定版本 0.9,本文中 Shiro 等同于 Jsecurity。

本文将涉及 Shiro 的整体框架、安全模型、关键概念类,同时给出了 Shiro 以及 Grails Shiro Plugin 的使用示例,可以下载文中使用的源代码。

本文代码的开发环境:

  • Jsecurity 0.9
  • Grails 1.2.0
  • Grails Shiro Plugin 1.0.1
  • SpringSource Tool Suite 2.3

Shiro 是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。如下是它所具有的特点:

  1. 易于理解的 Java Security API;
  2. 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);
  3. 对角色的简单的签权(访问控制),支持细粒度的签权;
  4. 支持一级缓存,以提升应用程序的性能;
  5. 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
  6. 异构客户端会话访问;
  7. 非常简单的加密 API;
  8. 不跟任何的框架或者容器捆绑,可以独立运行。

目前还有其他出现较早的安全框架,比如 JAAS,Spring Security。

JAAS —面世的时间最早,但是鉴于其在使用上有很大的限制,很少有人真正的使用它。可以说它不是一个好的应用程序级别的安全框架;

Spring Security —目前是 Java 安全框架领域当之无愧的老大,已经非常成熟了;如果使用 Spring 框架,可以首选 Spring Security,但是对于单应用来说,Shiro 更显简单方便。

下面就开始我们的 Shiro 之旅吧!

整体架构

首先,我们来看看的 Shiro 的整体架构,见下图:

图 1. 整体架构

从上图可以看出,Shiro 主要有四个组件:

  1. SecurityManager
  2. 典型的 Facade,Shiro 通过它对外提供安全管理的各种服务。
  3. Authenticator
  4. 对“Who are you ?”进行核实。通常涉及用户名和密码。
  5. 这个组件负责收集 principals 和 credentials,并将它们提交给应用系统。如果提交的 credentials 跟应用系统中提供的 credentials 吻合,就能够继续访问,否则需要重新提交 principals 和 credentials,或者直接终止访问。
  6. Authorizer
  7. 身份份验证通过后,由这个组件对登录人员进行访问控制的筛查,比如“who can do what”, 或者“who can do which actions”。Shiro 采用“基于 Realm”的方法,即用户(又称 Subject)、用户组、角色和 permission 的聚合体。
  8. Session Manager
  9. 这个组件保证了异构客户端的访问,配置简单。它是基于 POJO/J2SE 的,不跟任何的客户端或者协议绑定。

Shiro 的认证和签权可以通过 JDBC、LDAP 或者 Active Directory 来访问数据库、目录服务器或者 Active Directory 中的人员以及认证 / 签权信息。SessionManager 通过会话 DAO 可以将会话保存在 cache 中,或者固化到数据库或文件系统中。

安全模型

从 Shiro 的框架图,已经能够体会到这个工具的简单了。下面让我们来看看 Shiro 是如何工作的。先了解一下它的安全模型吧!见下图:

图 2. 安全模型

上图中,涉及了 Shiro 的五个概念:

  • Subject 是安全领域术语,除了代表人,它还可以是应用。在单应用中,可将其视为 User 的同义词。
  • Principal 是 Subject 的标识,一般情况下是唯一标识,比如用户名。
  • Role 和 Permission 分别代表了不同粒度的权限,从上图中可以看出 Role 的粒度更大些,Permission 代表了系统的原子权限,比如数据的修改、删除权限。对于简单的权限应用,可以不需要 Permission。
  • Realm 是一个执行者,负责真正的认证和鉴权。

实现应用的安全模块的关键在于:定义合适的 role 和 permission,这就需要遵循如下原则:

  1. role 没有实质内容,只是代表一组 permission,目的是为了管理的方便,一般都是动态定义;
  2. permission 一般都是预先定义好的,不允许动态改变,除非源代码改动,它才会变化,它是整个安全模块的基础;
  3. 要使 permission 也能动态定义,并非不可能,但是这将使鉴权非常复杂,甚至可能导致鉴权语句遍布整个程序,得不偿失;
  4. 当然有一个例外:如果知道 permission 动态定义的规则和鉴权规则,如 Grails 的 fileter 中“${controllerName}:${actionName}:${params.id}”也可实现 permission 的动态定义

关键概念类

理解 Shiro 的架构和安全模型了,我们来看看更具体些的内容。下图显示了 Shiro 中的关键概念类(参考资料 -- JSecurity Mini Guide)。

图 3. 关键类

AuthenticationToken 和 AuthenticationInfo

前者在认证前使用,描述认证所需的信息,最常用的就是 username 和 password 对;后者在认证后使用,内容同前,但是表示已经经过认证的信息。

RememberMe

代表的是一种可能状态,并不表示该 Subject 已经经过了认证。对于一些普通的操作,这种可能状态并无大碍,但一旦涉及安全敏感的操作,必须经过认证。

Credentials 和 CredentialsMatcher

Credentials 是 Subject 的证书,在认证时使用,最常用的就是 password。在通常情况下,为了安全起见,Subject 的 credentials 都需要加密保存,于是 CredentialsMatcher 的作用就体现出来了,见下图:

图 4. CredentialsMatcher 的作用

这里 CredentialsMatcher 需要将加密后的证书跟用户登录时提供的证书进行比对,完成认证的过程。

PAM=Pluggable Authentication Modules

在有多个 Realm 的时候使用。由认证策略决定认证结果,即 PAM=Relams + 认证策略。一般的策略有 3 种:AllSuccessful、AtLeastOneSuccessful 和 FirstSuccessful。

AuthorizationInfo

可以看成是 Role + Permission 的组合体。

PermissionResolver 和 Permission

它们之间的关系如下:

图 5. PermissionResolver 和 Permission 的关系

在 Shiro 中,权限被转化为一种字符串描述(字符串分级表示,称之为 WildcardPermission),从而将权限转化为类似于对象 equals 的操作(Shiro 中的 implies 方法)。

内置的权限有 2 个:

  • AllPermission,总是返回 true
  • WildcardPermission,权限字符串的表示方式。

这里重点声明一下。WildcardPermission 是 Shiro 的精妙之处,我们可以将权限表示成字符串,这样对权限的控制可以不拘泥于物理存储,比如对 messagge 类具有修改和删除权限可以标识为:message:update,delete:*,其中‘ * ’表示所有;第一级分隔符为‘ : ’;第二级分隔符为‘ , ’,而对于权限字符串的解释完全可以由应用自己来定。

如果要比较权限字符串,可以使用 permission1.implies(permission2),它分别比较对应位置的字符串,在如下情况中,结果会返回 true:

  • permission1 中的子串有 * 或 permission1 子串==permission2 子串;
  • permission1 无子串,permission2 有;
  • permission1 有子串,permission2 无,permission1 的所有子串都是 *。

总的说来,Shiro 中的 Permission 需要注意如下内容:

  1. 权限的比较实际是字符串的比较,只不过是考虑到了字符串的分级
  2. 字符串的分级划分完全由使用者自己决定,Shiro 的惯例是 3 级:资源 : 操作 : 实例。
  3. 字符串的使用必须一致,分隔符之间不要有空格,避免无意间引入的不一致。如:定义使用“file : create, update : 1”,而验证使用“file : update”,那么分解之后一个是“ update ”,一个是“ update”,因空格而引起不等。

Realm

这是一个实际访问安全实体的组件,一般是应用相关的,跟数据源的关系是 1-1。它负责完成认证和鉴权,getAuthenticationInfo 代表了 login 的尝试,鉴权方法则由 Authorizer 继承而来。此处也体现了 Shiro 代码的另一个特点,通过继承来扩充功能。以常用的 JdbcRealm 为例,其继承链如下:

图 6. JdbcRealm 的继承链

Session

它关联一个 Subject 的上下文,其作用类似于在 HttpSession 中保存用户标识,session 一旦过期,则重新登录。Shiro 的 Session 是独立的,其目的是做到环境无关性。为了利用 Web 环境中,Shiro 实现了一个直接使用 HttpSession 的 WebSession。

SecurityManager

这是一个 Fa?ade 接口,=Authenticator + Authorizer + SessionFactory。在整体框架图中已经看到了它在 Shiro 中所处的位置。其特点同 Realm,一样是使用继承不断地扩充功能。对于 Web 应用一般使用 DefaultWebSecurityManager。

Filter

在 Web 环境下使用 filter 进行认证和权限检查是毋庸置疑的,而 Shiro 的特点则在于由一个主 Filter 将一群子 filter 串起来:

图 7. Filter 的作用

在实际使用时,须注意:

  1. web.xml 中只需配置 JSecurityFilter。对于 Spring 应用,则使用 SpringJSecurityFilter;
  2. 子 filter 作为主 filter 的配置参数值出现,特点是:顺序相关
  • 对于多个 URL,验证顺序是由上至下,类似 Exception 的匹配。因此,使用顺序应该是由细到粗。
  • 对于同一 URL,子 filter 的验证顺序是从左至右的 AND 操作。
  1. 如果配置值中含有分隔符,如 Permission,就需要使用引号来转义。

Subject

subject 代表了一个用户的状态和操作,它提供了所有安全相关的操作,包括认证和签权。可以将其视为另一种形式的 Fa?ade。缺省实现是将这些操作委派给其内部包含的 SecurityManager。

Configuration

configuration 负责将所有这些组件串起来,最终创建 SecurityManager。在 Shiro 中,缺省格式是 ini。整个配置关系如下图:

图 8. 配置关系

其中:

  • JSecurityFilter 创建 Configuration 实例,并将 ini 参数值传给 Configuation。在 Spring 环境中,分别使用 SpringJSecurityFilter 和 SpringIniWebConfiguration。
  • Configuration 实际就是 SecurityManager 的 Factroy,对 SpringIniWebConfiguration 而言,它需要知道 SecurityManager 的 BeanName,该值由 SpringJSecurityFilter 的初始化参数“securityManagerBeanName”值决定。即 SpringJSecurityFilter,实际有两个初始化参数:
  • config,是 ini 配置文件内容
  • securityManagerBeanName,是 SecurityManager 的 BeanName

SecurityUtils

这是 Shiro 中最重要的工具类,由它可以方便地获得 Subject 和 SecurityManager。

杂项

  • AOP,提供 AOP 方面的支持,实现对某个类某个方法的拦截,从而使权限控制延伸至类的方法。
  • Cache,提供缓存支持
  • Codec,提供编码方面的支持
  • Crypto,提供加密支持
  • IO,从多个资源位置读写原始数据
  • JNDI,提供 jndi 支持
  • util,工具类支持
  • 标签类,用于 Web 页面

典型使用

对 Shiro 有了一个感官认识后,下面我们就亲自动手试试这个框架吧!下面给大家举了两个使用案例。

在开始案例的学习之前,先作好准备工作 -- 获得 Shiro 相关的 jar 包,获取途径有两种:

  1. 直接到 J-security 的网站上 下载,本文用到的就是这个;
  2. 由于 Shiro 目前是 Apache 的孵化项目,还没有发布正式的版本,但是我们可以到 Subversion 上下载代码,之后使用 Maven 构建
  3. mkdir shiro
  4. cd shiro
  5. svn co https://svn.apache.org/repos/asf/incubator/shiro/trunk/
  6. mvn install

之后会得到 shiro-all-1.0-incubating-SNAPSHOT.jar,就可以使用 Shiro 了。

示例一:让 Shiro 为你的应用服务

这个案例中,我们使用 Grails 向大家讲述 Shiro 的使用。我们要实现如下功能:

  1. 用户登录后方可进入系统;
  2. 假定一个 message 的安全内容,用户可以创建 message 的内容,但是如果需要修改 / 删除 message 的内容就必须具有相应的权限;
  3. Admin 具有所有的权限;
  4. message 的权限跟角色关联。

示例程序执行的流程如下:

图 9 程序执行的流程

从上图中可以看到,任何人要访问应用中受保护的 URL,首先要通过 Filter 检查用户是否经过认证;对于没有认证的用户会将访问定向到登录页面;对于已经认证的用户,会对用户进行鉴权,这个用户是否具有访问其所提交的 URL 的权限;而管理员可以给角色授权。

好了,开始程序的编写啦!

创建安全领域类

最常见的就是 User、Role 和 Permission,见清单 1。

清单 1. User/Role/Permission 的 Domain class

class User {

String username

String password

static hasMany=[roles: Role]

static belongsTo=Role

……

}

class Role {

String rolename

static hasMany=[users: User, permissions: Permission]

……

}

class Permission {

String permission

static hasMany=[roles: Role]

static belongsTo=Role

……

}

这里使用了最简单的情形,即权限传递结构为:Permission -> Role -> User。通常情况下,Permission 也可以分配给单个 User。

创建一个安全实体

实体名为 message,见清单 2。只有经过授权的用户才能对这个实体进行修改和删除。

清单 2. message 的 Domain class

class Message {

String details

User user

static constraints={

}

}

配置 web.xml

清单 3. 在 web.xml 加入 SecurityFilter 的内容:

<filter>

<filter-name>SecurityFilter</filter-name>

<filter-class>

org.jsecurity.spring.SpringJSecurityFilter

</filter-class>

<init-param>

<param-name>securityManagerBeanName</param-name>

<param-value>jsecSecurityManager</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>SecurityFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

这里需要注意:

  • 这个 Filter 应该在 Grails 的 web.xml 中所有缺省的 Filter 最后;
  • url-pattern 不要使用“/**”,因为这样会造成登录页的 css 和图片无法访问。解决办法,可以通过遵循“只能通过 Controller/Action 访问”这个规则,并使用 Grails 的 Filter 机制,可以保证所有安全 URL 不被非法访问。

创建 realm

清单 4. conf/spring/resources.groovy

beans={

credentialMatcher(

org.jsecurity.authc.credential.Sha1CredentialsMatcher) {

storedCredentialsHexEncoded=true

}

permissionResolver(

org.jsecurity.authz.permission.WildcardPermissionResolver)

realm(org.jsecurity.realm.jdbc.JdbcRealm){

permissionResolver=ref("permissionResolver")

dataSource=ref("dataSource")

permissionsLookupEnabled=true

permissionsQuery="select permission from

permission, role_permissions, role where

permission.id=permission_id and role_id=role.id and rolename=?"

userRolesQuery="select rolename from role, role_users, user

where role.id=role_id and user_id=user.id and username=?"

authenticationQuery="select password from user where username=?"

}

jsecSecurityManager(

org.jsecurity.web.DefaultWebSecurityManager) {

bean ->bean.destroyMethod="destroy"

realms=[ ref("realm") ]

}

}

这里使用了 JdbcRealm,同时根据应用情况修改了相应的 SQL。如果允许 Permission 直

接分配给 User,即 Permission 和 User 之间是多对多关系,那么 permissionsQuery 应该使用 union,即“role 相关 permission union user 相关 permission”。对于 User 有多个 Role 的情况,JdbcRealm 会循环得出总的结果。

安全守护神:SecurityFilters

下面就是我们的安全守护神:SecurityFilters,这里遵循 Grails 的 Filter 语法。见清单 5。

清单 5. SecurityFilters

import org.jsecurity.SecurityUtils

class SecurityFilters {

def filters={

authc(controller:'*', action:'*', ) {

before={

if(controllerName!='auth'){

def subject=SecurityUtils.subject

if (!subject.authenticated) {

redirect(

controller: 'auth',

action: 'login',

params: [

targetUri: request.forwardURI - request.contextPath

])

return false

}

}

}

}

admin(controller: 'user|role|permission', action: '*'){

before={

def subject=SecurityUtils.subject

if(!subject.hasRole('admin')){

redirect(controller: 'auth', action: 'unauthorized')

return false

}

}

}

editmessage(controller: 'message', action: 'update|delete'){

before={

def subject=SecurityUtils.subject

if(!subject.isPermitted(

"${controllerName}:${actionName}:${params.id}")){

redirect(controller: 'auth', action: 'unauthorized')

return false

}

}

}

}

}

代码中 :

  • authc 表示的是所有用户对应用系统的任何访问都要经过 auth 认证,对于没有认证的用户的访问会重定向到登录界面;
  • admin 表示的是属于 admin 角色的用户对 user/role/permission 具有所有权限,对于非 admin 角色的用户会对其提示没有 user/role/permission 的访问权限;
  • editmessage 表示当用户对 message 进行修改或者删除的时候对其进行鉴权,只有具有 message 的 update/delete 权限的用户才能对 message 进行修改。

在上述代码中还可以看出,我们通常可以由 SecurityUtils 为出发点获得 Subject 和 SecurityManager。需要注意的是,认证的 Filter(authc)要打头阵。

认证的代码

清单 6. 认证代码

def signIn={

// 创建 AuthenticationToken

def authToken=new UsernamePasswordToken(

params.username,

params.password)

if (params.rememberMe) {

authToken.rememberMe=true

}

try{

// 使用 SecurityManager 的 login 登录

this.jsecSecurityManager.login(authToken)

flash.message=message(code: "$params.username")

def targetUri=params.targetUri ?: "/"

log.info "Redirecting to '${targetUri}'."

redirect(uri: targetUri)

}

catch (AuthenticationException ex){

// 如果出现异常,显示出错信息

log.info "Authentication failure for user '${params.username}'."

flash.message=message(code: "login.failed")

def m=[ username: params.username ]

if (params.rememberMe) {

m['rememberMe']=true

}

if (params.targetUri) {

m['targetUri']=params.targetUri

}

redirect(action: 'login', params: m)

}

}

授权部分很简单,即对安全实体进行 CRUD。其中 Permission 的权限字符串根据实际情况形成,在本例中:

  • 如果对所有 message 具有修改权限,权限字符串可以为:message:delete,update:*;
  • 如果针对某一个 message 具有修改权限,权限字符串可以为:message:update,delete:messageid。

示例二:使用 Shiro Plugin 快速构建安全模块

在示例一中,所有的代码都是自己手动写的,这就对初学者要求有些高了。但可喜的是 Grails 社区有了 Shiro 的 plugin,让我们的工作变得非常简单。同样示例一的功能,看看 plugin 能够给我们带来什么样的惊喜?

使用步骤如下:

  1. 安装 Shiro Plugin,在你的 grails 项目中运行:grails install-plugin shiro,会创建 grails-app/realms 目录,并提供如下新的 Grails 命令:
  • grails create-auth-controller,创建 AuthController 以及登录窗口,Controller 提供了登录、登出和权限验证失败处理等 Action。
  • grails create-db-realm,创建一个访问数据库的 Realm
  • grails create-ldap-realm,创建一个访问 ldap 的 Realm
  • grails create-wildcard-realm,创建一个访问数据库的 Realm,但使用的是 Shiro 的 WildcardPermission。
  • grails quick-start,它是 create-auth-controller 和 create-db-realm 的集合体,是 Shiro 的快速入门,在接下来的内容中将详细介绍它的使用。
  1. 下面进行 Shiro 快速入门。在 grails 项目的目录下执行:grails quick-start,这个命令会创建如下内容:
  • 在 grails-app/realms 下创建 ShiroDbRealm,这个 realm 是访问控制的信息库,用来决定一个用户有权访问哪些内容;
  • 在 grails-app/domain 创建 ShiroRole.groovy 和 ShiroUser.groovy;
  • 在 grails-app/controllers 下创建 AuthController,这里提供了登录和退出的 action;
  • 在 grails-app/views 下创建 auth 文件夹以及 login.gsp;
  • 在 grails-app/conf/ 下创建 SecurityFilters.groovy,这里管理着对所有 controller 和 action 的访问控制。
  1. 启动程序,当访问 controller 的时候,页面就会重定向到登录页面。但是这个时候是无法登录,因为我们没有添加用户。
  2. 进入到 grails-app/conf/ 修改 BootStrap.groovy,
  3. 清单 7. 修改后的 BootStrap.groovy

def init={ servletContext ->

def user=new User(username: "user",

passwordHash: new Sha1Hash("user").toHex())

user.save()

def role=new Role(name:"admin").addToUsers(user).save()

}

  1. 重新启动程序,就能使用 user/user 登陆了。
  2. 在 plugin 缺省创建的 SecurityFilters 中使用了对所有 URL 进行 before 拦截,但是我们根据实际情况进行修改,例如我们要示例一的 filter 内容,就可以做如下更改:
  3. 清单 8. 更改后的 SecurityFilters

auth(controller: "*", action: "*"){

before={

accessControl{true}

}

}

   management(controller: "user|role|permission", action: "*"){

before={

accessControl{role("admin")}

}

}

message(controller: "message", action: "delete|update"){

before={

accessControl{   

       permission("message:${actionName}:${params.id}")

}

}

}

  1. 看到这里,读者可能已经注意到了,这里的代码比示例一中的 Filter 的代码简单的许多。对,Shiro 插件已经将示例一中的类似代码封装到 ShiroGrailsPlugin.groovy 中了,我们使用的时候只需要:
  2. 调用 accessControl 方法,该方法的参数可以是 Map、Closure 或者 Map+Closure;
  3. 使用 role( …… ),验证访问对象是否具有相应的角色;
  4. 使用 permission( …… ),验证访问对象是否具有相应的 Permission。
  5. 授权部分内容参见上一示例,这里不做冗述。
  6. Shiro Plugin 中除了为我们提供了上述方便之外,还提供了一些常用的 taglib 来增强用户界面 , 这些 taglib 都在 pluginPath/grails-app/taglib /ShiroTagLib.groovy 中定义,我们可以直接在 gsp 中使用它们。
  7. 比如,只有具有修改权限的用户才能够看到一些修改类的按钮显示,可以这样写:
  8. 清单 9. Taglib 的使用

<shiro:hasPermission permission="message:update,delete">

<span class="button">

<g:actionSubmit class="edit" value="Edit" />

</span>

<span class="button">

<g:actionSubmit class="delete"

onclick="return confirm('Are you sure?');"

value="Delete" />

</span>

</shiro:hasPermission>

  1. 如下是经常使用到的 Tag:
  • principal,输出当前用户的标识
  • hasRole,判断当前用户是否属于给定的角色,参数:name
  • hasPermission, 判断当前用户是否具有指定的权限,参数:type,action 或者 permission
  • isLoggedIn,判断当前用户是否已经登录
  • hasAnyRole,判断当前用户是否属于给定的某个角色,参数:in

更多的 Tag 请参考 Shiro Plugin 的 gapi 的文档。

如果您已经非常了解了 Shiro,可以采用示例一的方式自己写所有的代码,但是对于初学者,作者还建议使用 Shiro plugin,这个插件帮助我们生成了安全相关的基本内容,以及提供了方便的 Tag。

总结

读到这里,是不是觉得 Shiro 真的不错?!

这里给大家举的示例只是抛砖引玉。Shiro 真正的精髓还需要在项目中慢慢的体会。本文是引领我们走向 Shiro 的第一步。在这里要感谢胡键对本文编写的大力支持。

源代码中,shiroApp 是示例一的源代码,ShiroPlugin 是示例二的源代码。

为一枚Java程序员,需要掌握哪些技术和工具才能完成一个JavaWeb项目呢?今天罗列一些常用技术和工具,这些技术都是我这10年工作中用的比较多的,我知道技术栈远不止这些,本人只列自己熟悉和用的最多的,完成一个项目绝对够用了。说这么多技术不是让大家都要熟悉,有些太老的现在也用的少了甚至不用了,本人主要结合我这10年开发经验告诉大家JavaWeb的常用技术栈。

下面这张是技术栈思维导图:

技术栈

什么是技术栈? 举个例子: 开发一个普通管理系统,会用到Sprin Boot+MyBatis+Spring+Mysql+Redis+RabbitMq+Nginx+Vue+Shiro+html+等等,这些技术合起来就可以称为技术栈。

我将技术栈大致分为5大块:前端、后端、中间件、数据库和工具。

前端

JSP

JSP全称Java Server Pages,是一种动态网页开发技术。它使用JSP标签在HTML网页中插入Java代码。

JSP本质上是一个servlet,主要用于实现Java web应用程序的用户界面部分。

<html>
    <head>
           <title>第一个 JSP 程序</title>
    </head>
    <body>
           <%
                  out.println("Hello World!");
           %>
    </body>
</html>

JSP这种网页技术我猜5年以下的程序员基本没接触过,10年前我接触的项目前端基本都是采用的JSP技术,jsp配合各种html+jquery/JavaScript+css完成前端页面开发。

前端框架

DWZ、EasyUI、EXT、BootStrap、KendoUI 都是基于基于 HTML、CSS、JavaScript/jquery的一些富文本客户端UI框架,在当时简直是后端开发人员的福音。这些框架最大的特点就是官网上提供了各种组件的使用方法,后端人员只要套到JSP页面中,进行数据渲染即可。这些UI框架风格基本已经固定,更适合开发一些管理类系统,都包括:强大的数据源,通用的拖拉(Drag-and-Drop)功能,模板,和UI控件。

DWZ

是中国人自己开发的基于jQuery实现的Ajax RIA开源框架,设计目标是简单实用,快速开发,降低ajax开发成本。

官网:https://jui.org/

EasyUI

easyui是一种基于jQuery的用户界面插件集合,为创建现代化,互动,JavaScript应用程序,提供必要的功能。使用easyui你不需要写很多代码,你只需要通过编写一些简单HTML标记,就可以定义用户界面,为网页开发的时间和规模。

官网;http://www.jeasyui.com/

EXT

ExtJS是基于YUI(雅虎用户界面)的sencha的JavaScript框架和产品,它基本上是具有现代UI的桌面应用程序开发平台。

中文官网:http://extjs-doc-cn.github.io/ext4api/#!/api/Ext

BootStrap

bootstrap是Twitter推出的一个用于前端开发的开源工具包

中文官网:https://www.bootcss.com/

KendoUI

是一套 JavaScript 函式库,提供抽象化、可自订主题的 GUI 控制项与动画效果。基于 jQuery JavaScript 函式库,可用来建构互动式的 Web 应用

官网:http://www.kendoui.io/

FreeMarker

FreeMarker是一个免费的模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写的,用来生成HTML Web页面,特别是基于MVC模式的应用程序。通常由Java程序准备要显示的数据,由FreeMarker生成页面,通过模板显示准备的数据(如下图)

FreeMarker不是一个Web应用框架,FreeMarker与容器无关,也可以在模板中使用JSP标记库。

<html>
<head>
  <title>Welcome!</title>
</head>
<body>
  <h1>Welcome ${user}!</h1>
  <p>Our latest product:
  <a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>

html

上面很多前端框架都是基于html的,需要你有js/jq、css基础,这是所有前端框架的基础。因为光框架有时并不能满足我们的需求,有时需要对框架无法实现的功能需要在框架基础上调整;还有这个飞速发展的互联网时代,对前端的要求越来越高,原生html得到了快速发展,基本所有前端效果使用原生时可以实现的。

VUE

vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

VUE+elementUI目前使用最多的,尤其是开发一些后台管理系统还是比较简单便捷的。

后端

servlet

servlet是Server Applet的简称,翻译过来就是服务程序,简单的讲就是是运行在服务器上的一个小程序,用来处理服务器请求的。我们通过浏览器访问一个应用,在这个过程中,我们的浏览器发送访问请求,服务器接收请求,并对浏览器的请求作出相应的处理,这就是我们熟悉的B/S模型(浏览器-服务器模型).而servlet就是对请求作出处理的组件,运行于支持Java的应用服务器中。如图如是:

struts

struts主要是指struts1和struts2,是经典的MVC框架,除去一些老项目,现在用的越来越少了。但struts1和struts2还是有区别的,主要区别二者本质不一样。

struts1:通过采用Java Servlet/JSP技术,实现了基于Java EE Web应用的Model-View-Controller(MVC)设计模式的应用框架,是MVC经典设计模式中的一个经典产品。

struts2:以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts 2可以理解为WebWork的更新产品。

最经典的组合strutsMVC+SPring+Hibernate,号称SSH,当年都是面试必问的技术。

Spring

Spring框架是一个开源Java应用框架,解决了开发者在开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于应用程序,也可以和Struts、Webwork等众多Web框架组合使用。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。

持久层框架

jdbc

JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

早期项目大部分都是通过对JDBC封装来操作数据库,实现增删改查,对性能考虑也不多,随时间推移不断衍生出很多框架,例如:mybatis,hibernate等。

ibatis

iBATIS一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目。最初侧重于密码软件的开发,现在是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO),同时还提供一个利用这个框架开发的JPetStore实例。

iBATIS 目前提供了三种语言实现的版本,包括:Java、.NET以及Ruby。

mybatis

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。

MyBatis 是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

MyBatis-Plus

Mybatis 增强工具包 - 只做增强不做改变,简化CRUD操作

JPA

JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。

Spring Boot

SpringBoot是由Pivotal团队在2013年开始研发、2014年4月发布第一个版本的全新开源的轻量级框架。它基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。

分布式/微服务

Spring Cloud

Spring Cloud 为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性 Token、全局锁、决策竞选、分布式会话和集群状态)操作的开发工具。使用 Spring Cloud 开发者可以快速实现上述这些模式。

Spring Cloud 的 GitHub 主页:https://github.com/spring-cloud

dubbo

Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。

主要核心部件:

Remoting: 网络通信框架,实现了 sync-over-async 和 request-response 消息机制

RPC: 一个远程过程调用的抽象,支持负载均衡、容灾和集群功能

Registry: 服务目录框架用于服务的注册和服务事件发布和订阅

Spring Cloud Alibaba

Spring Cloud Alibaba 致力于提供分布式应用服务开发的一站式解决方案。此项目包含开发分布式应用服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里分布式应用解决方案,通过阿里中间件来迅速搭建分布式应用系统。

安全框架

shiro

Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

Spring Security

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

oauth2.0

OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

OAuth 2.0 是目前比较流行的做法,它率先被Google, Yahoo, Microsoft, Facebook等使用。之所以标注为 2.0,是因为最初有一个1.0协议,但这个1.0协议被弄得太复杂,易用性差,所以没有得到普及。2.0是一个新的设计,协议简单清晰,但它并不兼容1.0,可以说与1.0没什么关系。

项目管理

maven

Maven 是 Apache 下的一个纯 Java 开发的开源项目。利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。是一个项目管理工具,可以对 Java 项目进行构建、依赖管理。

ant

ant 是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。在实际软件开发中,有很多地方可以用到ant。

gradle

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,目前也增加了基于Kotlin语言的kotlin-based DSL,抛弃了基于XML的各种繁琐配置。

服务器软件

tomcat

Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选

Apache

Apache(阿帕奇)是世界使用排名第一的Web服务器软件。它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用,是最流行的Web服务器端软件之一

Jetty

Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境。

weblogic

WebLogic Server是专门为企业电子商务应用系统开发的。企业电子商务应用系统需要快速开发,并要求服务器端组件具有良好的灵活性和安全性,同时还要支持关键任务所必需的扩展、性能、和高可用性。WebLogic Server简化了可移植及可扩展的应用系统的开发,并为其它应用 系统和系统提供了丰富的互操作性。

nginx

Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强。

中间件

缓存

缓存是现在系统中必不可少的模块,并且已经成为了高并发高性能架构的一个关键组件。缓存的主要作用:提升性能缓解数据库压力

Redis

Redis是一个开源的内存数据结构存储,用作数据库、缓存和消息代理。提供诸如字符串、哈希、列表、集合、带范围查询的排序集合、位图、超日志、地理空间索引和流等数据结构。具有内置的复制、Lua脚本、LRU逐出、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster的自动分区提供高可用性。

MongoDB

MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

Memcached

Memcached是一个自由开源的,高性能,分布式内存对象缓存系统,用来提高Web应用扩展性的重要因素。是一个基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。简洁而强大,它的简洁设计便于快速开发,减轻开发难度,解决了大数据量缓存的很多问题。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

差异性我就不多说了,简单说下他们的使用场景:

  • Memcached:动态系统中减轻数据库负载,提升性能;做缓存,适合多读少写,大数据量的情况(如人人网大量查询用户信息、好友信息、文章信息等)。
  • Redis:适用于对读写效率要求都很高,数据处理业务复杂和对安全性要求较高的系统(如新浪微博的计数和微博发布部分系统,对数据安全性、读写要求都很高)。
  • MongoDB:主要解决海量数据的访问效率问题。

搜索

solr

Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。

Elasticsearch

Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口

二者主要区别

es基本是开箱即用,非常简单,Solr略微复杂。

Solr 支持更多格式的数据,比如JSON、XML、CSV,而 Elasticsearch 仅支持json文件格式。

Solr 查询快,但更新索引时慢(即插入删除慢),ES建立索引快(即查询慢),即实时性查询快。

ELK

之所以推荐ELK是因为面对分布式微服务情况下,会显示出他的威力,不管是数据收集、数据处理、还是数据分析可以节约很多时间。ELK是Elasticsearch + Logstash + Kibana三个开源软件的组合,但是通常为了提高性能需要借助kafka,利用Filebeat进行日志收集,下面这个架构图是我做过的一个日志服务平台。

定时任务

说定定时任务大家可能都接触过,例如Spring自带的定时任务SpringTask,但是SpringTask是存在很多问题的,例如:最致命的一个问题就是一旦定时抛出异常,生命就结束,下一次不会再执行。复杂业务中使用起来是不方便的,这里推荐两个定时任务框架Quartz和xx-job,第三方的功能比较强大,好用。

Quartz

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:

  • 持久性作业 - 就是保持调度定时的状态;
  • 作业管理 - 对调度作业进行有效的管理;

xx-job

XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。

目前已有多家公司接入xxl-job,包括比较知名的大众点评,京东,优信二手车,北京尚德,360金融 (360),联想集团 (联想),易信 (网易)等等.... Quartz作为开源作业调度中的佼佼者,是作业调度的首选

Quartz在集群环境下只能通过API的方式对任务管理,同时,系统侵入性相当严重,而XX-JOB可以完美解决集群下任务额管理。所以,可以根据自己业务情况选择合适定时任务框架,

配置中心

什么是配置中心?

集中将应用系统中对配置信息的管理作为一个新的应用功能模块,区别与传统的配置信息分散到系统各个角落方式,进行集中统一管理,并且提供额外功能。尤其是在微服务架构中,是不可或缺组件,甚至是必要组件之一。

为什么要使用配置中心?

在微服务体系中,服务的数量以及配置信息的日益增多,比如各种服务器参数配置、各种数据库访问参数配置、各种环境下应用配置信息的不同、配置信息修改之后实时生效等等,传统的配置文件方式或者将配置信息存放于数据库中的方式已无法满足开发人员对配置管理的要求。常见的配置中心中间件有:SpringCloud Config ,Nacos,apollo等。

SpringCloud Config

2014年9月开源,Spring Cloud 生态组件,可以和Spring Cloud体系无缝整合,Spring Cloud Config支持通过/bus/refresh端点的destination参数来指定要更新配置的机器,不过整个流程不够自动化和体系化。相对于Apollo和Nacos还是比较弱的。

Nacos

2018年6月,阿里开源的配置中心,也可以做DNS和RPC的服务发现。Nacos使用起来相对比较简洁,在对性能要求比较高的大规模场景更适合。此外,Nacos除了提供配置中心的功能,还提供了动态服务发现、服务共享与管理的功能,降低了服务化改造过程中的难度。

apollo(阿波罗)

2016年5月,携程开源的配置管理中心,具备规范的权限、流程治理等特性,Apollo可以直接在控制台上点灰度发布指定发布机器的IP,接着再全量发布,做得比较体系化。

三者对比如何选择:

核心功能: Apollo和Nacos相对于Spring Cloud Config的生态支持更广

实时推送: Nacos和Apollo在配置实时推送链路上是比较简单高效的,Spring Cloud Config比较复杂。

高可用部署: Nacos的部署结构比较简单,运维成本较低。Apollo部署组件较多,运维成本比Nacos高。Spring Cloud Config生产高可用的成本最高。

监控

服务器监控是实时掌握服务器工作状态,并在需要时可以随时调用监控记录进行查看。

SpringBootAdmin

Spring Boot Admin用来管理和监控Spring Boot应用程序,通过UI来查看应用程序的状态,例如Spring Beans,系统属性,线程,http的调用情况等有限状态。

Druid

我知道Druid更多的用途就是连接池,其实他还有一个对SQL监控功能。

SkyWalking

Skywalking是由国内开源爱好者吴晟(原OneAPM工程师,目前在华为)开源并提交到Apache孵化器的产品,它同时吸收了Zipkin/Pinpoint/CAT的设计思路,支持非侵入式埋点。是一款基于分布式跟踪的应用程序性能监控系统。另外社区还发展出了一个叫OpenTracing的组织,旨在推进调用链监控的一些规范和标准工作。

存储

把涉及到数据库相关或者可以作为数据仓库的中间件都归到一起了。

MyCat

Mycat是一个开源数据库中间件,是一个实现了MySQL协议的的数据库中间件服务器,我们可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问Mycat,而Mycat再使用用MySQL原生(Native)协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信,包括SQL Server、Oracle、DB2、PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储,未来还会支持更多类型的存储;一般地,Mycat主要用于代理MySQL数据库,虽然它也支持去访问其他类型的数据库;

Mycat官网:http://www.mycat.io/

Canal

我们先看官网的介绍

canal,译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费

这句介绍有几个关键字:增量日志,增量数据订阅和消费

这里我们可以简单地把canal理解为一个用来同步增量数据的一个工具

接下来我们看一张官网提供的示意图:

sharing-jdbc

定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

主要功能

  • 分库 & 分表
  • 读写分离
  • 分布式主键

zookeeper

ZooKeeper是Hadoop的正式子项目,它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

事务

这个事务更多指的是分布式事务处理插件。

Apache ShardingSphere

Apache ShardingSphere(Incubator) 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(规划中)这3款相互独立,却又能够混合部署配合使用的产品组成。它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。

Seata

Seata 是 Simple Extensible Autonomous Transaction Architecture 的简写,由 feascar 改名而来。是阿里开源的分布式事务框架,属于二阶段提交模式。

消息队列

什么是消息队列

消息队列是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ。

为什么需要消息队列

1.通过异步处理提高系统性能(削峰、减少响应所需时间);

2.降低系统耦合性;

Kafka

Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群来提供实时的消息。

RabbitMQ

AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

ActiveMQ

ActiveMQ是一种开源的基于JMS(Java Message Servie)规范的一种消息中间件的实现,ActiveMQ的设计目标是提供标准的,面向消息的,能够跨越多语言和多系统的应用集成消息通信中间件。

三者区别及其使用场景

特性 ActiveMQ RabbitMQ kafka 开发语言 java erlang scala 单机吞吐量 万级 万级 10万级 时效性 ms级 us级 ms级以内 可用性 高(主从架构) 高(主从架构) 非常高(分布式架构) 功能特性 成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好 基于erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富 只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。

数据库

该数据库主要是指关系型数据库,是按照数据结构来组织、存储和管理数据的仓库,数据库是存放数据的仓库,是一个系统或者软件必不可少的一部分。每个数据库都有一个或多个不同的 API 用于创建,访问,管理,搜索和复制所保存的数据。我们也可以将数据存储在文件中,但是在文件中读写数据速度相对较慢。

所以,现在我们使用关系型数据库管理系统来存储和管理大数据量。所谓的关系型数据库,是建立在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。

关系数据库管理系统的特点:

  • 1.数据以表格的形式出现
  • 2.每行为各种记录名称
  • 3.每列为记录名称所对应的数据域
  • 4.许多的行和列组成一张表单
  • 5.若干的表单组成database

常见的数据管理系统主要有:oracle、mysql、SQL Server、access、TiDB。

oracle

Oracle Database,简称 Oracle。Oracle 数据库系统是美国 Oracle 公司(甲骨文)提供的以分布式数据库为核心的一组软件产品,是目前最流行的客户/服务器(client/server)的数据库之一。Oracle 数据库是目前世界上使用最为广泛的数据库管理系统,作为一个通用的数据库系统,它具有完整的数据管理功能;作为一个关系型数据库,它是一个完备关系的产品;作为分布式数据库它实现了分布式处理功能,但它的所有知识,只要在一种机型上学习了Oracle知识,便能在各种类型的机器上使用它。

mysql

MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System:关系数据库管理系统)应用软件之一。目前也是Oracle 公司来托管。

SQL Server

美国Microsoft公司推出的一种关系型数据库系统。SQL Server是一个可扩展的、高性能的、为分布式客户机/服务器计算所设计的数据库管理系统,实现了与WindowsNT的有机结合,提供了基于事务的企业级信息管理系统方案。

access

Access是微软把数据库引擎的图形用户界面和软件开发工具结合在一起的一个数据库管理系统。它是微软OFFICE的一个成员, 在包括专业版和更高版本的office版本里面被单独出售。

TiDB

TiDB 是 PingCAP 公司自主设计、研发的开源分布式关系型数据库,是一款同时支持在线事务处理与在线分析处理 (Hybrid Transactional and Analytical Processing, HTAP)的融合型分布式数据库产品,具备水平扩容或者缩容、金融级高可用、实时 HTAP、云原生的分布式数据库、兼容 MySQL 5.7 协议和 MySQL 生态等重要特性。目标是为用户提供一站式 OLTP (Online Transactional Processing)、OLAP (Online Analytical Processing)、HTAP 解决方案。TiDB 适合高可用、强一致要求较高、数据规模较大等各种应用场景。

上面这几个数据库优缺点都比较明显,不在啰嗦了,大家在工作中可以根据自己实际情况选择合适的数据。

工具

开发

IntelliJ IDEA

IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是超常的。IDEA是JetBrains公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。它的旗舰版本还支持HTML,CSS,PHP,MySQL,Python等。免费版只支持Java,Kotlin等少数语言。

eclipse

Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台, 是 Java 的集成开发环境(IDE),当然 Eclipse 也可以作为其他开发语言的集成开发环境,如C,C++,PHP,和 Ruby 等。

MyEclipse

MyEclipse,是在Eclipse 基础上加上自己的插件开发而成的功能强大的企业级集成开发环境,主要用于Java、Java EE以及移动应用的开发。在最新版本的MyEclipse中,配合CodeMix使用支持也十分广泛,尤其是对各种开源产品和主流开发框架的支持相当不错。目前已支持PHP、Python、Vue、Angular、React、Java、Java EE等语言和框架开发。

这三款工具好坏我也不做任何评价,每个工具用户都不少,我周围IntelliJ IDEA和MyEclipse都有,我个人比较喜欢用IntelliJ IDEA。

客户端工具

版本控制

常见的版本空间工具svn和git

svn

TortoiseSVN 是 Subversion 版本控制系统的一个免费开源客户端,可以超越时间的管理文件和目录。文件保存在中央版本库,除了能记住文件和目录的每次修改以外,版本库非常像普通的文件服务器。你可以将文件恢复到过去的版本,并且可以通过检查历史知道数据做了哪些修改,谁做的修改。这就是为什么许多人将 Subversion 和版本控制系统看作一种“时间机器”。

git

Git 是用于Linux内核开发的版本控制工具。与常用的版本控制工具 Subversion 不同,它采用了分布式版本库的方式,不必服务器端软件支持,代码的发布和交流极其方便。 Git 的速度很快,这样的大项目来说自然很重要。 Git 最为出色的是它的合并跟踪(merge tracing)能力。

SVN的特点是简单,只是需要一个放代码的地方时用是OK的。

Git的特点版本控制可以不依赖网络做任何事情,对分支和合并有更好的支持(这应该算是开发者最关心的地方)。

数据库

Navicat

Navicat是一套快速、可靠并价格相当便宜的数据库管理工具,专为简化数据库的管理及降低系统管理成本而设。它的设计符合数据库管理员、开发人员及中小企业的需要。Navicat 是以直觉化的图形用户界面而建的,让你可以以安全并且简单的方式创建、组织、访问并共用信息。

PLSQL

PL/SQL Developer是一个集成开发环境,专门开发面向Oracle数据库的应用。PL/SQL也是一种程序语言,叫做过程化SQL语言(Procedural Language/SQL)。PL/SQL是Oracle数据库对SQL语句的扩展。在普通SQL语句的使用上增加了编程语言的特点,所以PL/SQL把数据操作和查询语句组织在PL/SQL代码的过程性单元中,通过逻辑判断、循环等操作实现复杂的功能或者计算,PL/SQL 只有 Oracle 数据库有

Redis Desktop Manager

Redis Desktop Manager是一款windows平台下的可视化redis数据库桌面管理工具,使用它你可以查看、删除、修改你的redis数据库数据!

linux远程

XShell

XShell是一个强大的安全终端模拟软件,它支持SSH1,SSH2,以及Microsoft Windows平台的TELNET协议。 XShell可以在Windows界面下用来访问远端不同系统下的服务器,从而比较好的达到远程控制终端的目的。

WinSCP

宝塔

宝塔面板是一个可以安装在服务器上的集成环境,并配套了web管理面板,可以在浏览器上直接控制你的服务器,非常方便。 可以一键创建网站、FTP、数据库、SSL;安全管理,计划任务,文件管理,PHP多版本共存及切换;自带基础网站环境,支持windows系统(apache)和linux系统(apache或nginx)。

SecureCRT

Secure CRT 是一款SSH客户端软件,通过使用内含的VCP命令行程序可以进行加密文件的传输。

postMan

Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。

辅助

Typora+PicGo

Typora+PicGo,最好用的Markdown+最好用的图床工具!如果写博客的可以收藏起来。

notepad++

Notepad++中文版是程序员必备的文本编辑器,Notepad++中文版小巧高效,支持27种编程语言,通吃C,C++ ,Java ,C#, XML, HTML, PHP,JS 等,Notepad++中文版编辑器可完美地取代微软的记事本。

画图

visio

Office Visio 是Office软件系列中的负责绘制流程图和示意图的软件,是一款便于IT和商务人员就复杂信息、系统和流程进行可视化处理、分析和交流的软件。使用具有专业外观的 Office Visio 图表,可以促进对系统和流程的了解,深入了解复杂信息并利用这些知识做出更好的业务决策。常见软件架构、流程图都可以在里面完成。

processon

ProcessOn是一个在线作图工具的聚合平台, 它可以在线画流程图、思维导图、UI原型图、UML、网络拓扑图、组织结构图等等, 您无需担心下载和更新的问题,不管Mac还是Windows,一个浏览器就可以随时随地的发挥创意,规划工作。

官网:https://www.processon.com/

PowerDesigner

owerDesigner是一款非常全面的数据库设计工具。使用PowerDesigner可以快速创建表,支持表与表之间建立关系,界面简洁,功能强大。同时支持将sql脚本导出,多种导出类型任意挑选,简单实用。

Xmind

XMind 是一个全功能的思维导图和头脑风暴软件,为激发灵感和创意而生。作为一款有效提升工作和生活效率的生产力工具,受到全球百千万用户的青睐。

iReport

说到 iReport 不得不先介绍 Jasperreport,Jasperreport 是一个报表制作程序,用户需要按照它制定的规则编写一个 XML 文件,然后得到用户需要输出的格式文件。它支持输出的文件格式包括 PDF,HTML,XML,XLS,CVS 等等。而 iReport 就是一个制作 Jasperreport 的 XML 文件的可视化开发工具。

浏览器

Chrome

Google Chrome是一款由Google公司开发的网页浏览器,该浏览器基于其他开源软件撰写,包括WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

搜狗

搜狗浏览器 由搜狗公司开发,基于谷歌chromium内核,力求为用户提供跨终端无缝使用体验,让上网更简单、网页阅读更流畅的浏览器。

火狐

Mozilla Firefox,中文俗称“火狐”,是一个由Mozilla开发的自由及开放源代码的网页浏览器。其使用Gecko排版引擎,支持多种操作系统,如Windows、macOS及GNU/Linux等

IE

Internet Explorer(简称:IE)是微软公司微软公司推出的一款网页浏览器。原称Microsoft Internet Explorer(6版本以前)和Windows Internet Explorer(7、8、9、10、11版本)。在IE7以前,中文直译为“网络探路者”,但在IE7以后官方便直接俗称"IE浏览器"。

如果做前端开发,这几款浏览器兼容性都得兼顾。很早之前IE浏览器市场是比较大,随着时间推移,谷歌浏览器占据了比较大的市场。我个人日常开发中谷歌用的比较多,特殊情况才会使用其它浏览器。

性能分析

jmeter

JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。 它可以用于测试静态和动态资源,例如静态文件、Java 服务程序、CGI 脚本、Java 对象、数据库, 等等。

ab

apache bench简称ab,它是apache自带的压力测试工具。ab非常实用,它不仅可以对apache服务器进行网站访问压力测试,也可以对或其它类型的服务器进行压力测试。

MAT

MAT是Java堆内存分析工具,可从http://www.eclipse.org/mat/中下载。

你在成为大神的路上都学习了哪些技术,欢迎留言交流。