shiro作为一个安全框架,可以很好的在javaee项目中使用,他可以不依赖于spring单独使用。也可以和spring框架一起使用。现在的springboot开发中也越来越多的使用shiro来做安全验证。
shiro框架的三大核心组件:SecurityManager,Realm,Subject。三者构成了安全框架的主要组成部分。
SecurityManager:管理shiro内部组件,负责管理Subject,Session,Cache,验证,授权。
Realm:为shiro验证用户是否存在或者是否有相关权限提供数据支持。
Subject:主体,当前操作的用户。
shiro入门,一般从最简单的登录验证开始。我们构建maven工程,引入依赖:
junit
junit
4.11
test
commons-logging
commons-logging
1.1.3
org.apache.shiro
shiro-core
1.4.0
com.zaxxer
HikariCP
2.7.9
mysql
mysql-connector-java
5.1.46
从最简单的iniRealm开始,我们准备一个shiro-user.ini 的配置文件,内容如下:
[users]
admin=123456,admin
[roles]
admin=*
直接利用单元测试测试登录:
@Test
public void initTest() {
Factory factory = new IniSecurityManagerFactory("classpath:shiro-user.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
try {
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
}
if(subject.isAuthenticated()) {
System.out.println(subject.getPrincipal().toString()+" login successfully.");
}
}
单元测试成功,打印:admin login successfully.
这个示例的过程大致是先创建SecurityManager,然后创建Subject,最后登录。 这个示例的数据来源于ini配置文件,看不出来自Realm。下面开始另一个示例,构建一个SimpleAccountRealm:
SimpleAccountRealm realm = new SimpleAccountRealm();
@Before
public void before() {
realm.addAccount("admin", "123456");
}
@Test
public void simpleRealmTest() {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");
try {
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
}
if(subject.isAuthenticated()) {
System.out.println(subject.getPrincipal().toString()+" login successfully.");
}
}
同样测试会成功。打印和第一个示例一样的信息。这个示例很好的说明了Realm为shiro提供了验证用户和用户是否拥有权限数据支持。我们也可以自定义Realm,让他实现类似的功能。
CustomRealm.java
package com.xxx.shirodemo.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.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
if(!"admin".equals(username)) {
return null;
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"123456",getName());
return info;
}
}
单元测试方法:
@Test
public void customRealmTest() {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
CustomRealm realm = new CustomRealm();
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");
try {
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
}
if(subject.isAuthenticated()) {
System.out.println(subject.getPrincipal().toString()+" login successfully.");
}
}
这种方式和第二种方式也类似,只不过使用了自定义的Realm。数据还是来源于内存设置,我们可以利用JdbcRealm来实现数据来源于数据库。这里要用到HikariCP数据源。还需要准备一个mysql数据库webapp,然后准备一张表users至少包含username,password两个字段。
@Test
public void jdbcRealmTest() {
String jdbcUrl = "jdbc:mysql:///webapp?useUnicode=true&useSSL=false";
String driverClassName = "com.mysql.jdbc.Driver";
Properties properties = new Properties();
String username = "hadoop";
String password="hadoop";
DriverDataSource dataSource = new DriverDataSource(jdbcUrl, driverClassName, properties, username, password);
JdbcRealm realm = new JdbcRealm();
realm.setDataSource(dataSource);
CredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("md5");
realm.setCredentialsMatcher(credentialsMatcher);
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");
try {
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
}
if(subject.isAuthenticated()) {
System.out.println(subject.getPrincipal().toString()+" login successfully.");
}
}
这里我的数据库中的用户密码经过了md5加密:
mysql> select * from users;
+----+----------+----------------------------------+
| id | username | password |
+----+----------+----------------------------------+
| 1 | admin | e10adc3949ba59abbe56e057f20f883e |
+----+----------+----------------------------------+
1 row in set
JdbcRealm需要设置credentialsMatcher,其中credentialsMatcher的加密方法采用MD5。这样整个示例就可以测试通过了。
几个单元测试下来,我们基本搞清楚了shiro登录验证的基本流程,创建securityManager->创建Subject->登录验证login。这里面需要我们注意的就是如何构建securityManager,是否使用自定义的Realm,用户密码是否加密等。
如果用户名不存在,报错:
org.apache.shiro.authc.UnknownAccountException: Realm [com.xxx.shirodemo.realm.CustomRealm@5577140b] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - admin1, rememberMe=false]
如果密码不正确,报错:
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=false] did not match the expected credentials.
在web项目中,我们可以根据不同的异常类型,返回页面不同的提示语,用户名不存在,密码错误等。