对Shiro安全框架的各种操作进行简单的入门,为Shiro框架嵌入Web应用进行基础练习讲解
Shiro
Apache Shiro是一个简单强大的Java安全框架,提供了用户身份验证、权限授权、加密和会话管理等功能,通过Shiro可以简单快速地帮助各种应用确保安全
Authentication(身份验证)
通过数据库或其他地方存储的用户信息进行比较,确认用户是否登录登录
Authorization(授权)
通过数据库内存储的用户
角色
信息确认用户权限Cryptography(加密)
对信息,如密码,进行加密操作
Session Management(会话管理)
进行用户会话管理,保持登录状态
Demo
maven依赖
1
2
3
4
5<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
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
42package 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();
public void addUser(){
simpleAccountRealm.addAccount("Ph0en1x", "1234", "admin");
}
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
2isAutenticated: 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] |
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
59package 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方法
doGetAuthorizationInfo
和doGetAuthenticationInfo
分别用于授权和身份验证请求的处理。还有就是密码的加盐操作,数据并不会明文存在数据库中。通常会通过一些加密操作,例如MD5码,在加密的过程中为了加大破译的难度,往往还会带上一些别的信息,叫作“盐”(salt)。例如上面我获取密码的方法
getPasswordByUserName
就在获得密码后进行了加盐加密的操作(用来模拟存储加密后的密码,实际使用中是加盐加密后再存入数据库中),并且通过setCredentialsSalt
方法告诉验证信息加入的盐是什么。
相应的,在Shiro的配置中也要加入相应的加密方法和盐的配置
1 | package org.phoenix.test; |
加密设置就是告诉Shiro我的密码是用了一次迭代的md5加密的,再配合setCredentialsSalt
里的盐信息,就可以完成身份验证
- 这里还有其他的一些关于Shiro的简单代码