jarvis鉴权组件

基于shiro的通用鉴权组件Jarvis

大部分应用都有鉴权的需求。如应用认证,要求访问者需要登录才能访问。再如应用授权,不仅要求访问者登录,还需要访问被授予权限才能访问特定资源。

image.png

为每个大大小小应用开发安全模块其实是一件繁琐的事情。每个应用的安全模块都提供认证和授权功能。
jarvis是一个通用的spring java应用鉴权组件,具有如下特性:

  • 组件化。需要配置的项目仅需引用组件的maven依赖,提供简单配置文件即可拥有鉴权模块...
  • 在线化。提供在线web配置,在线管理用户,角色,权限。
  • 实时化。实时维护资源权限,实时生效(通常项目的url资源访问权限由配置文件进行定义,如果修改需要重启项目)。
  • url权限粒度

Apache Shiro

什么是shiro?

image.png

日语发音,中文意思是“城堡”,是一个功能强大且容易使用的java安全框架。提供认证,授权,加密,会话管理等功能,被广泛使用。

Servlet过滤器

Filter是在Servlet 2.3之后增加的新功能,当需要限制用户访问某些资源或者在处理请求时提前处理某些资源的时候,就可以使用过滤器完成。

image.png

image.png

shiroFilter

Shiro对Servlet容器的FilterChain进行了代理,先执行Shiro自己的Filter链,再执行Servlet容器的Filter链(即原始的Filter)。

image.png

shiro 核心概念

Subject、SecurityManager、Realm

image.png

Subject

表示当前正在执行操作的用户,用户通常是人,也可以是其他应用进程。获取当前用户的代码很简单:

import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();

一旦获取到当前用户实例对象,就可以进行登录登出,权限检查等操作...

SecurityManager

Subject代表当前用户,SecurityManager代表所有用户,是shiro框架的核心。
当Subject进行login或logout或checkPermission时,其实都是交由SecurityManager进行,SecurityManager能协调这些组件完成工作。

  • Authenticator 负责登录验证。获取安全数据(通过realm),获取当前Subject的信息,两者比较,通过则登录成功,失败则抛出异常。
  • Authorizer 负责登录Subject的权限校验。
  • SessionManager 创建,维护,清除所有应用session。
  • CacheManager 缓存管理组件,shiro需要可以获取用户安全数据用于认证,通常这些数据不会经常变动,对这些数据做缓存可以提升框架性能。
  • Cryptography 加密组件,如密码加密。

通常开发者很少直接和SecurityManager进行交互...SecurityManager采用门面模式(外观模式),可以当成一个容器类,包含各个安全组件,及各个组件的get,set方法,因此也可以看成是一个java bean,这种结构的类可以很方便的进行xml配置...扩展性也强,可以配置自己实现的组件。SecurityManager不进行真正的Security操作,但知道何时进行并调用协调各个组件进行工作。

  • Realms
Realm

realm是Shiro和应用安全数据的“桥梁”。进行登录或访问控制的时候,shiro需要获取应用的用户,角色,权限等数据,shiro通过配置好的realm来获取这些数据,从这点来看,realm是一个DAO
(数据访问对象)。shiro提供了一些内置的realm组件,用于访问不同数据源的安全数据,如关系型数据库,文本配置文件等。开发者也可以实现自己的Realm,如果这些内置的realm不满足需求。

Jarvis基于Apache Shiro的登录流程

  • controller接口,接收登录请求,请求参数通常包含用户名和密码,封装成shiro的 UsernamePasswordToken对象,获取当前Subject,调用login(token)方法;,将用户信息提交给认证authenticator组件。
@RequestMapping(value = "/login")
public String login(@Valid User user, BindingResult bindingResult, HttpServletRequest request, Map map) {
        if (bindingResult.hasErrors()) {
            return "index";
        }
        String username = user.getUsername();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword(), false);
        // 获取当前的Subject
        Subject currentUser = SecurityUtils.getSubject();
        try {
            logger.info("对用户[" + username + "]进行登录验证..验证开始");
            currentUser.login(token);
            logger.info("对用户[" + username + "]进行登录验证..验证通过");
        } catch (UnknownAccountException uae) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户");
.....
.....
  • authenticator组件调用自定义的realm组件doGetAuthenticationInfo(AuthenticationToken token)方法。
    其中doGetAuthenticationInfo(AuthenticationToken token)方法是登录验证的安全数据获取方法,代码逻辑如下:
  1. 根据当前Subject提供的用户名称查询出用户
  2. 用户为null,返回null,subject.login(token)方法抛未知账户异常
  3. 如果用户不为null,封装成SimpleAuthenticationInfo实体对象并返回,shiro的Authentication组件会进行密码校验,校验失败则抛出异常!(校验前按需进行密码解密,这些shiro都会帮开发者完成)。
    以下是realm组件doGetAuthenticationInfo(用户数据获取)的实现
@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        // UsernamePasswordToken对象用来存放提交的登录信息
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        logger.info("验证当前Subject时获取到token为:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
        // 查出是否有此用户
        User user = userService.findByName(token.getUsername());
        if (user != null) {
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), // account
                    user.getPassword(), // 密码
                    ByteSource.Util.bytes(user.getCredentialsSalt()), // username+salt
                    getName() // realm name
            );
            // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
            return info;
        }
        return null;
    }

  • 处理登录校验结果。shiro的authentication组件根据校验产生的错误会抛出各种异常。
try {
            logger.info("对用户[" + username + "]进行登录验证..验证开始");
            currentUser.login(token);
            logger.info("对用户[" + username + "]进行登录验证..验证通过");
        } catch (UnknownAccountException uae) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户");
            map.put("message", "未知账户");
        } catch (IncorrectCredentialsException ice) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
            map.put("message", "密码不正确");
        } catch (LockedAccountException lae) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
            map.put("message", "账户已锁定");
        } catch (ExcessiveAttemptsException eae) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
            map.put("message", "用户名或密码错误次数过多");
        } catch (AuthenticationException ae) {
            // 通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");
            ae.printStackTrace();
            map.put("message", "用户名或密码不正确");
        }

shiro权限配置

shiro权限配置一般是这样的:
告诉shiro哪些资源需要哪些角色或哪些权限...

<property name="filterChainDefinitions" >
    <value>
        /** = anon
        /page/login.jsp = anon
        /page/register/* = anon
        /page/index.jsp = authc
        /page/addItem* = authc,roles[数据管理员]
        /page/file* = authc,roles[普通用户]
        /page/listItems* = authc,perms[删除数据]
        /page/showItem* = authc,perms[添加数据]
        /page/updateItem*=authc,roles[数据管理员]
    </value>
</property>

当请求传递到PathMatchingFilter过滤器时,PathMatchingFilter根据请求url路径解析对应的配置(anon,authc),即mappedValue,然后调用onPreHandle(),进行权限校验,和realm提供的安全数据比对,检查当前用户是否拥有mappedValue所要求的权限。

boolean pathsMatch(String path, ServletRequest request)  
boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception   

动态url权限配置

大部分shiro应用使用配置文件配置资源权限,修改配置后需要重启才能生效。

        AbstractShiroFilter abstractShiroFilter = null;
        try {
            abstractShiroFilter = (AbstractShiroFilter)bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 获取过滤管理器  
        PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) abstractShiroFilter  
                .getFilterChainResolver();  
        DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();  
        // 清空初始权限配置  
        manager.getFilterChains().clear();  
        bean.getFilterChainDefinitionMap().clear();  
        // 重新构建生成  
        set(bean,definitions);
        Map<String, String> chains = bean.getFilterChainDefinitionMap();  
        for (Map.Entry<String, String> entry : chains.entrySet()) {  
            String url = entry.getKey();  
            String chainDefinition = entry.getValue().trim().replace(" ", "");  
            manager.createChain(url, chainDefinition);  
        }  

Jarvis基于Apache Shiro的权限控制流程

Jarvis采用用户-角色-权限的权限控制体系。
表结构设计:
user用户表
role角色表
permission权限表
用户角色关系表 多对多关系
角色权限关系表 多对多关系

image.png

谈谈角色

角色是权限的集合。

隐式角色和显式角色(implicit role和explicit role)
  • 隐式角色。使用隐式角色做权限校验并不关心权限,仅仅关心角色本身。即能做什么事情,是因为拥有什么角色...
  • 显式角色。 能做什么事情,是因为拥有什么角色,而这个角色拥有这个权限去做这件事

Jarvis即支持隐式角色也支持显式角色,以下是realm组件doGetAuthorizationInfo(权限安全数据获取)的实现:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("##################执行Shiro权限认证##################");
        String loginName = (String) super.getAvailablePrincipal(principalCollection);
        // 到数据库查是否有此对象
        User user = userService.findByName(loginName);// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        if (user != null) {
            // 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            // 用户的角色集合
            info.setRoles(user.getRolesName());
            // 用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要 隐式角色
            List<Role> roleList = user.getRoleList();
            for (Role role : roleList) {
                info.addStringPermissions(role.getPermissionsName());
            }
            return info;
        }
        // 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址
        return null;
    }

Jarvis集成超级详情日志分析应用

引入maven依赖

 <dependency>
    <groupId>com.hlg.bigdata</groupId>
    <artifactId>Jarvis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency> 

配置文件application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/cjxqlog?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=5581653
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#logging.path=/Users/yangwq/cjxq/
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.properties.hibernate.hbm2ddl.auto=update

未来可以考虑集成data-plant,商品分类系统等需要鉴权的应用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 前言 Spring boot 是什么,网上的很多介绍,这里博客就不多介绍了。如果不明白Spring boot是什么...
    xuezhijian阅读 17,894评论 13 39
  • 文章转载自:http://blog.csdn.net/w1196726224/article/details/53...
    wangzaiplus阅读 3,385评论 0 3
  • 构建一个互联网应用,权限校验管理是很重要的安全措施,这其中主要包含: 认证 - 用户身份识别,即登录 授权 - 访...
    zhuke阅读 3,487评论 0 30
  • Apache Shiro Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权...
    罗志贇阅读 3,214评论 1 49
  • (高中毕业那年,你走之后,风都静止了) 风吹来了伤悲 月从此流泪 忘了你发光的面 只留我残碎的心 今天不会再次出现...
    漫步1阅读 167评论 0 1