Shiro

shiro

Shiro安全框架

什么是Shiro

它是一个安全框架,用于解决系统的认证和授权问题,同时提供了会话管理,数据加密机制。

  • 传统登陆方式

  • Shiro登陆方式

Shiro的内部组织结构

可以用在javaEE环境。Shiro可以帮助我们完成:认证,授权,加密,会话管理,与Web集成,缓存等。

  • primary Concerns 主要的功能:
    • Authentication 认证
    • Authorization 授权
    • Session Management 会话管理
    • Cryptography 加密

应用程序如何使用Shiro框架

Shiro SecurityManager:安全管理,最核心的控制器
Realm域 :程序员提供的安全数据

  • 可以看出,程序员只关注两部分:
    1. 如何获取Subject.
    2. 如何定义一个符合规定的Realm域(密码比较器的定义也是程序员干的)

  • Shiro使用时要配置相关过滤器(anon,authc,authcBasic,perms,port,rest,roles,ssl,user,logout)
    • Shiro中有十个拦截器,但可以只配置一个代理的就行,以一当十
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      //shiro的filter必须在struts2的filter之前,否则action无法创建
      <filter>
         <filter-name>shiroFilter</filter-name>
         //这里的名字在spring中还会用到
         <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
         <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
         </init-param>
      </filter>
      <filter-mapping>
         <filter-name>shiroFilter</filter-name>
         <url-pattern>/*</url-pattern>
      </filter-mapping>

使用

引入依赖pom.xml或jarbao

1
2
3
4
5
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-all</artifactId>
    <version>1.2.3</version>
</dependency>

配置过滤器(上面有)

配置shiro配置文件

  • 在applicationContext.xml中配置shiro配置文件,放在事务管理器之前配置,Spring中引入生成Shiro的动态代理,使用cglib的方式
    <aop:aspectj-autoproxy proxy-target-class="true" />

  • 同时添加专门配置shiro的配置文件
    <import resource="spring/applicationContext-shiro.xml"/>

  • 和ehcache支持ehcache-shiro.xml里面放(记得在pom中引入依赖):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <ehcache updateCheck="false" name="shiroCache">
        <defaultCache
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                overflowToDisk="false"
                diskPersistent="false"
                diskExpiryThreadIntervalSeconds="120"
                />
    </ehcache>

写配置文件

  • 新建一个applicationContext-shiro.xmlShiro的配置的文件
    • 里面的约束就是Spring的约束,因为Spring与Shire天生就是一对
    • 具体内容
      • SecurityManager配置
      • 配置Realm域
      • 密码比较器
      • 代理如何生成? 用工厂来生成Shiro的相关过滤器
      • 配置缓存:ehcache缓存
      • 安全管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
   <description>Shiro的配置文件</description>
   <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
        <!-- 缓存 -->
        <property name="cacheManager" ref="shiroEhcacheManager"/>
    </bean>
    <!-- 自定义权限认证 -->
    <bean id="authRealm" class="自定义了一个Realm域的包名">
    <property name="userService" ref="userService"/>
    <!-- 自定义密码加密算法  -->
    <property name="credentialsMatcher" ref="passwordMatcher"/>
  </bean>
  <!-- 设置密码加密策略 md5hash -->
  <bean id="passwordMatcher" class="自定义密码比较器的全路径"/>
    <!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!--登录页面  -->
        <property name="loginUrl" value="/index.jsp"></property>
        <!-- 登录成功后 -->
        <property name="successUrl" value="/home.action"></property>
        <!-- 还有过滤器过滤的属性`filterChainDefinitions`
             anon(匿名访问)可以直接访问
             authc(认证)不登录不可以直接访问 -->
        <property name="filterChainDefinitions">
            <!-- /**代表下面的多级目录也过滤 -->
            <value>
        /index.jsp* = anon
        /home* = anon
        /sysadmin/login/login.jsp* = anon
        /sysadmin/login/logout.jsp* = anon
        /login* = anon
        /logout* = anon
        /components/** = anon
        /css/** = anon
        /images/** = anon
        /js/** = anon
        /make/** = anon
        /skin/** = anon
        /stat/** = anon
        /ufiles/** = anon
        /validator/** = anon
        /resource/** = anon
        /** = authc
        /*.* = authc
            </value>
        </property>
    </bean>
    <!-- 用户授权/认证信息Cache, 采用EhCache  缓存 -->
    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
    </bean>
    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
    <bean id="lifecycleBeanPostProcessor"
  class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    <!-- 生成代理,通过代理进行控制 -->
    <bean  class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true"/>
    </bean>
    <!-- 安全管理器 -->
    <bean  class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

自定义realm

  • 在认证、授权内部实现机制中都有提到,最终处理都将交给Realm进行处理。
  • 因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。
  • 通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
    • 记得配置到配置文件
    • 注入userServicecredentialsMatchar密码比较器(父类的父类中已经有了,所以就不用再注入了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
 * 自定义了一个Realm域 (主要作用是提供安全数据:用户,角色,模块)
 * @author Administrator
 *
 */
public class AuthRealm extends AuthorizingRealm {
    //注入userService
    private UserService userService;
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    //授权
    /**
     * 验证用户是否具有某某权限
     * 当jsp页面上碰到Shiro标签时就会调用这个方法,当第一次碰到时才调用这个方法
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
        //1.得到用户信息
        User user = (User) pc.fromRealm(this.getName()).iterator().next();
        //2.通过对象导航,得到用户的角色列表
        Set<Role> roles = user.getRoles();
        List<String> permissions = new ArrayList<String>();
        //3.遍历角色列表,得到用户的每个角色
        for(Role role :roles){
            //得到每个角色 ,并通过对象导航,进一步加载这个角色下的模块列表
            Set<Module> modules = role.getModules();
            //遍历模块的集合,得到每个模块信息
            for(Module module :modules){
                permissions.add(module.getName());
            }
        }
        //声明AuthorizationInfo的一个子类对象
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);
        return info;
    }
    /**
     * 认证(在登录时就会调用这个方法)    Subject.login();
     * 参数:AuthenticationToken代表用户在界面上输入的用户名和密码
     * 返回值不为null就会执行密码比较器
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.将token转化为子类对象
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        //2.从token中获取用户在界面输入的用户名
        String username = upToken.getUsername();
        //3.调用业务逻辑,根据用户名查询用户对象
        List<User> userList = userService.find("from User where userName=?" , User.class, new String[]{username});
        if(userList!=null && userList.size()>0){
            //查询到了用户对象,说明用户名是正确的
            User user = userList.get(0);
            //principal 代表用户信息             credentials 代表用户的密码              第三个参数:只要是一个字符串就可以
            AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
            return info;
        }
        //4.组织返回的结果
        return null;
    }
}

自定义的密码比较器CustomCredentialsMatcher

  • 继承SimpleCredentialsMatcher
  • 重写了密码比较的方法
    • 第一个参数AuthenticationToken 代表用户在界面上输入的用户名和密码
    • 第二个参数AuthenticationInfo 代表了当前这个用户在数据库中的信息,就会有加密后的密码
  • 返回值:
    • true证明密码比较成功了
    • false证明密码比较失败,密码输入错误,程序会抛出异常
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
          //1.将用户在界面输入的密码进行加密
          UsernamePasswordToken upToken = (UsernamePasswordToken) token;//向下转型
          String inputpwd = new String(upToken.getPassword());
          String inputpwdEncrypt = Encrypt.md5(inputpwd, upToken.getUsername());//md5hash算法进行加密
          //2.将用户在数据库中的密码读取出来
          String dbPwd = info.getCredentials().toString();
          //3.进行比较
          return super.equals(inputpwdEncrypt, dbPwd);
        }

自己的程序与Shiro交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class LoginAction extends BaseAction {
  private static final long serialVersionUID = 1L;
  private String username;
  private String password;
  public String login() throws Exception {
    //如果username是空的则表示还没有登陆跳转到登陆页面
    if(UtilFuns.isEmpty(username)){
      return "login";
    }
    try {
      //1.与Shiro交互
      Subject subject = SecurityUtils.getSubject();
      //2.调用subject中的方法,来实现登录
      //将用户在界面输入的用户名密码进行封装
      UsernamePasswordToken token = new UsernamePasswordToken(username, password);
      //当login执行时,就会自动跳入authRealm域中的认证方法
      subject.login(token);
      //3.从Shiro中取出用户登录结果信息
      User user = (User) subject.getPrincipal();
      //4.将用户信息保存到session中
      session.put(SysConstant.CURRENT_USER_INFO, user);
      return SUCCESS;
    } catch (Exception e) {
      e.printStackTrace();
      request.put("errorInfo", "登录失败,用户名或密码错误!");
      return "login";
    }
  }
  //退出
  public String logout(){
    session.remove(SysConstant.CURRENT_USER_INFO);    //删除session   
    return "logout";
  }
  public String getUsername() {
    return username;
  }
  public void setUsername(String username) {
    this.username = username;
  }
  public String getPassword() {
    return password;
  }
  public void setPassword(String password) {
    this.password = password;
  }
}

面使用shiro标签,/home/title.jsp 主菜单

当页面上出现shiro标签的时候就会去授权,然后比较集合中是否有这一项,有就显示没有就不显示

1
2
3
4
5
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
<shiro:hasPermission name="sysadmin">
<span id="topmenu" onclick="toModule('sysadmin');">系统管理</span>
</shiro:hasPermission>

shiro的实现图解

  • shiro的执行流程

  • 使用shiro实现用户登录

  • 授权的过程

md5hash

比md5强大的加密算法md5hash:

  • 破解md5:
    • 破解思路,暴力破解,因为md5是一 一 对应方式,所以如果有一个大的数据字典就很容易破解
    • 或者如果能够操作数据库把密码改成一个自己知道的密文即可,md5hash不知道具体加密的次数跟盐所以行不通
  • md5hash算法加密:
    • md5hash因为同样的明文对应不一样的密文,所以不好破解
    • 因为加密的时候有三个参数:原文,盐(用什么参数加密),跟加密的次数
张冲 wechat
欢迎扫一扫上面的微信关注我,一起交流!
坚持原创技术分享,您的支持将鼓励我继续创,点击打赏!