Shiro安全框架入门

对Shiro安全框架的各种操作进行简单的入门,为Shiro框架嵌入Web应用进行基础练习讲解

Shiro

Apache Shiro是一个简单强大的Java安全框架,提供了用户身份验证、权限授权、加密和会话管理等功能,通过Shiro可以简单快速地帮助各种应用确保安全

  • Authentication(身份验证)

    通过数据库或其他地方存储的用户信息进行比较,确认用户是否登录登录

  • Authorization(授权)

    通过数据库内存储的用户角色信息确认用户权限

  • Cryptography(加密)

    对信息,如密码,进行加密操作

  • Session Management(会话管理)

    进行用户会话管理,保持登录状态

Demo

  1. maven依赖

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

  2. Authentication简单测试,使用Junit进行单元测试

    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
    package org.phoenix.test;

    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.realm.SimpleAccountRealm;
    import org.apache.shiro.subject.Subject;
    import org.junit.Before;
    import org.junit.Test;

    public class AutenticationTest {

    SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();

    @Before
    public void addUser(){
    simpleAccountRealm.addAccount("Ph0en1x", "1234", "admin");
    }

    @Test
    public void testAutentication(){

    // 构建SecurityManager环境
    DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    defaultSecurityManager.setRealm(simpleAccountRealm);

    // 主体提交认证请求
    SecurityUtils.setSecurityManager(defaultSecurityManager);
    Subject subject = SecurityUtils.getSubject();

    UsernamePasswordToken token = new UsernamePasswordToken("Ph0en1x", "1234");
    subject.login(token);

    System.out.println("isAutenticated: " + subject.isAuthenticated());

    subject.checkRole("admin");

    subject.logout();

    System.out.println("isAutenticated: " + subject.isAuthenticated());
    }
    }

    这是Shiro安全模块最简单结构的构建,首先需要构建SecurityManager环境。而构建SecurityManager环境需要一个叫作Realm的组件,Realm里面将会进行用户身份认证以及授权等一系列操作。这里我只创建了简单的,只能进行预先加入到内存中的用户的身份验证和授权的SimpleRealm。

    在Junit的@Before标签中我们就预先加入了一个用户用于测试

    这之后需要将刚刚完成Realm配置的SecurityManager配置到环境中,并生成一个主体(Subject),后面将由主体来发送认证请求给Realm

    身份认证请求被包含在一个UsernamePasswordToken中,然后由主体发送login请求,同时进行admin权限验证

    上述代码将会得到以下输出

    1
    2
    isAutenticated: true
    isAutenticated: false

    当主体发送logout请求后再进行身份验证就会返回false

当UsernamePasswordToken中的用户密码不正确时,会抛出异常

1
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - Ph0en1x, rememberMe=false] did not match the expected credentials.

当验证用户不具有相应身份或权限时,会抛出异常

1
org.apache.shiro.authz.UnauthorizedException: Subject does not have role [admin]
  1. CustomRealm自定义Realm

    Realm是Shiro进行身份验证和授权的最核心组件,所以也一定能开放给用户进行自定义,例如想根据某个数据库或者文件中的信息来进行相应的验证,密码加密验证等等操作。我这里也写了一个简单的从数据库中查询用户密码来验证登录用户身份的CustomRealm

    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
    package org.phoenix.shiro.realm;

    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.crypto.hash.Md5Hash;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;

    import java.sql.*;

    public class CustomerRealm extends AuthorizingRealm{

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    //角色授权

    return null;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    //认证用户

    String username = (String)authenticationToken.getPrincipal();
    String password = getPasswordByUserName(username);
    if(password == null){
    return null;
    }
    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
    "Ph0en1x", password, "customRealm"
    );
    // 传回的时候也要带盐
    authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(username));
    return authenticationInfo;
    }

    private String getPasswordByUserName(String username) {
    String url = "jdbc:mysql://127.0.0.1/test";
    String name = "root";
    String password = "1234";
    String result = null;
    try{
    Connection con = DriverManager.getConnection(url, name, password);
    String sql = String.format("select password from user where username = \'%s\'", username);
    Statement statement = con.createStatement();
    ResultSet rs = statement.executeQuery(sql);
    rs.next();
    // md5加盐操作
    result = (new Md5Hash(rs.getString("password"), username)).toString();
    System.out.println(result);
    }catch(SQLException e){
    e.printStackTrace();
    }

    return result;
    }
    }

    CustomerRealm都继承于AuthorizingRealm,而AuthorizingRealm继承于AuthenticatingRealm

    而完成CustomerRealm,需要实现两个abstract方法doGetAuthorizationInfodoGetAuthenticationInfo分别用于授权和身份验证请求的处理。

    还有就是密码的加盐操作,数据并不会明文存在数据库中。通常会通过一些加密操作,例如MD5码,在加密的过程中为了加大破译的难度,往往还会带上一些别的信息,叫作“盐”(salt)。例如上面我获取密码的方法getPasswordByUserName就在获得密码后进行了加盐加密的操作(用来模拟存储加密后的密码,实际使用中是加盐加密后再存入数据库中),并且通过setCredentialsSalt方法告诉验证信息加入的是什么。

相应的,在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
package org.phoenix.test;

import org.phoenix.shiro.realm.CustomerRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

public class CustomRealmTest {
@Test
public void testAutentication(){

CustomerRealm customerRealm = new CustomerRealm();

// 构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(customerRealm);
//加密设置
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1);
customerRealm.setCredentialsMatcher(matcher);

// 主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();

UsernamePasswordToken token = new UsernamePasswordToken("Ph0en1x", "1234");
subject.login(token);

System.out.println("isAutenticated: " + subject.isAuthenticated());

// subject.checkRole("admin");
// subject.checkPermission("user:delete");
subject.logout();

System.out.println("isAutenticated: " + subject.isAuthenticated());
}
}

加密设置就是告诉Shiro我的密码是用了一次迭代的md5加密的,再配合setCredentialsSalt里的信息,就可以完成身份验证

  1. 这里还有其他的一些关于Shiro的简单代码