代码及部分相关资料根据慕课网Mark老师的视频进行整理。java
其余资料:mysql
认证是指检查用户身份合法性,经过校验用户输入的密码是否正确,判断用户是否为本人。git
有几个概念须要理解:github
Principals (主体标识)
任何能够惟一地肯定一个用户的属性均可以充当principal,例如:邮箱、手机号、用户ID等,这些都是与用户一一对应的,能够惟一地肯定一个用户。web
credentials (主体凭证)
credentials是能确认用户身份的东西,能够是证书(Certificate),也能够是密码(password)。算法
token(令牌)
这里的token和api里的token有一点儿差异,这里token是principal和credential的结合体或者说容器。这里先讲一部分,剩下的放到"Subject"讲解。sql
shiro中的“受权”,更贴切说法是“鉴权”,即断定用户是否拥有某些权限,至于拥有该权限在业务上有何意义,则是由业务自己来决定。数据库
关于“受权”,shiro引入了两种概念:apache
Role (角色)
角色用来区分用户的类别。角色与用户间是多对多的关系,一个用户能够拥有多个角色,如Bob能够同时是admin(管理员)和user(普通用户)。api
Permission (权限)
权限是对角色的具体的描述,用于说明角色在业务上的特殊性。如admin(管理员)能够拥有user:delete(删除用户)、user:modify(修改用户信息)等的权限。一样的,角色与权限是多对多的数量关系。
shiro权限能够分级,使用":"分割,如delete、user:delete、user:info:delete。可使用"*"做通配符,例如能够给admin赋予操做用户的全部权限,能够配置为"user:*",这样在受权时,isPermitted("user:123")、isPermitted("user:123:abc")都是返回true;若是配置为"*:*:delete",想要返回true,则须要相似这样的权限: isPermitted("123:abc:delete")、isPermitted("hello:321:delete")。
Subject对象用于应用程序与shiro的相关组件进行交互,能够把它看做应用程序中“用户”的代理,也能够将其视为shiro中的“用户”。譬如在一个应用中,User对象做为业务上以及程序中的“用户”,在实现shiro的认证和受权时,并不直接使用User对象与shiro组件进行交互,而是把User对象的信息(用户名和密码)交给Subject,Subject调用本身的方法,向shiro组件发起身份认证或受权。
以下是Subject接口提供的方法,包括登陆(login)、退出(logout)、认证(isAuthenticated)、受权(checkPermission)等:
顾名思义,SecurityManager是用来manage(管理)的,管理shiro认证受权的流程,管理shiro组件、管理shiro的一些数据、管理Session等等。
以下是SecurityManager接口的继承关系:
与Subject和SecurityManager同样,Realm是shiro中的三大核心组件之一。Realm至关于DAO,用于获取与用户安全相关的数据(用户密码、角色、权限等)。当Subject发起认证和受权时,其实是调用其对应的SecurityManager的认证和受权的方法,而SecurityManager则又是调用Authenticator和Authorizer的方法,这两个类,最后是经过Realm来获取主体的认证和受权信息。
shiro的认证和受权过程以下所示:
shiro的使用实际上是比较简单的,只要熟记这几个步骤,而后在代码中实现便可。
1. 建立Realm
Realm是一个接口,其实现类有SimpleAccountRealm, IniRealm, JdbcRealm等,实际应用中通常须要自定义实现Realm,自定义的Realm一般继承自抽象类AuthorizingRealm,这是一个比较完善的Realm,提供了认证和受权的方法。
2. 建立SecurityManager并配置环境
配置SecurityManager环境其实是配置Realm、CacheManager、SessionManager等组件,最基本的要配置Realm,由于安全数据是经过Realm来获取。用SecurityManager的setRealm(xxRealm)方法便可给SecurityManager设置Realm。能够为SecurityManager设置多个Realm。
3. 建立Subject
可使用SecurityUtils建立Subject。SecurityUtils是一个抽象工具类,其提供了静态方法getSubject(),用来建立一个与线程绑定的Subject。建立出来的Subject用ThreadContext类来存储,该类也是一个抽象类,它包含一个Map<Object, Object>类型的ThreadLocal静态变量,该变量存储该线程对应的SecurityManager对象和Subject对象。在SecurityUtils调用getSubject方法时,其实是调用SecurityManager的CreateSubject()方法,既然如此,为何还要经过SecurityUtils建立Subject?由于SecurityUtils不只仅建立了Subject还将其与当前线程绑定,并且,使用SecurityManager的CreateSubject()方法还要构建一个SubjectContext类型的参数。
4. Subject提交认证和受权
Subject的login(Token token)方法能够提交“登陆”(或者说认证),token就是待验证的用户信息(用户名和密码等)。登陆(认证)成功后,使用Subject的ckeckRole()、checkPermission等方法判断主体是否拥有某些角色、权限,以达到受权的目的。再次提醒,Subject不实现实际上的认证和受权过程,而是交给SecurityManager处理。
Realm用的是SimpleAccountRealm,SimpleAccountRealm直接把用户认证数据存到实例中, SecurityManager使用DefaultSecurityManager, 使用SecurityUtils建立Subject, Token用UsernamePasswordToken。 用Junit进行测试。 maven依赖以下:
<!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13-beta-3</version> </dependency> <!--shiro核心包--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency>
AuthenticationTest.java:
package com.lifeofcoding.shiro; 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.Test; public class AuthenticationTest { @Test public void testAuthentication(){ //1.建立Realm并添加数据 SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm(); simpleAccountRealm.addAccount("java","123"); //2.建立SecurityManager并配置环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(simpleAccountRealm); //3.建立subject SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); //4.Subject经过Token提交认证 UsernamePasswordToken token = new UsernamePasswordToken("java","123"); subject.login(token); //验证认证状况 System.out.println("isAuthenticated: "+ subject.isAuthenticated()); //退出登陆subject.logout(); } }
SimpleAccountRealm添加用户角色和权限的方法比较简单,能够本身琢磨。此处的Realm改用IniRealm,iniRealm须要编写ini文件存储用户的信息,ini文件放在resource文件夹下。代码以下:
AuthorizationTest.java
package com.lifeofcoding.shiro; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.text.IniRealm; import org.apache.shiro.subject.Subject; import org.junit.Test; import java.util.ArrayList; public class AuthorizationTest { @Test public void testAuthorization() { //1.建立Realm并添加数据 IniRealm iniRealm = new IniRealm("classpath:UserData.ini"); //2.建立SecurityManager并配置环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(iniRealm); //3.建立Subject SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); //4.主体提交认证 UsernamePasswordToken token = new UsernamePasswordToken("java", "123"); subject.login(token); /*如下为受权的几种方法*/ //①.直接判断是否有某权限,isPermitted方法返回boolean,不抛异常 System.out.println("user:login isPermitted: " + subject.isPermitted("user:login")); //②.经过角色进行受权,方法有返回值,不抛异常 subject.hasRole("user");//判断主体是否有某角色 subject.hasRoles(new ArrayList<String>() {//返回boolean数组,数组顺序与参数Roles顺序一致,接受List<String>参数 { add("admin"); add("user"); } }); subject.hasAllRoles(new ArrayList<String>() {//返回一个boolean,Subject包含全部Roles时才返回true,接受Collection<String>参数 { add("admin"); add("user"); } }); //③.经过角色受权,与上面大致相同,不过这里的方法无返回值,受权失败会抛出异常,需作好异常处理 subject.checkRole("user"); subject.checkRoles("user", "admin");//变参 //④.经过权限受权,无返回值,受权失败抛出异常 subject.checkPermission("user:login"); //ini文件配置了test角色拥有"prefix:*"权限,也就是全部以"prefix"开头的权限 subject.checkPermission("prefix:123:456:......"); //ini文件配置了test角色拥有"*:*:suffix"权限,意味着其拥有全部以"suffix"结尾的,一共有三级的权限 subject.checkPermission("1:2:suffix"); subject.checkPermission("abc:123:suffix"); subject.checkPermissions("user:login", "admin:login");//变参 //subject.checkPermission(Permission permission); 须要Permission接口的实现类对象做参数 //subject.checkPermissions(Collection<Permission> permissions); } }
user.ini:
[users] java = 123,user,admin,test [roles] user = user:login,user:modify admin = user:delete,user:modify,admin:login test = prefix:*,*:*:suffix
[main] # Objects and their properties are defined here, # Such as the securityManager, Realms and anything # else needed to build the SecurityManager # 此处能够用来配置shiro组件,不用编写代码,如: ##--CredentialsMatcher是用来设置加密的--## hashedCredentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher ##--设置加密的算法--## hashedCredentialsMatcher.hashAlgorithmName = MD5 ##--设置加密次数--## hashedCredentialsMatcher.hashIterations = 1 ##--给Realm配置加密器的Matcher,"$"表引用--## iniRealm.credentialsMatcher = $hashedCredentialsMatcher ##--配置SecurityManager--## securityManager = com.xxx.xxxManager securityManager.realm = $iniRealm [users] # The 'users' section is for simple deployments # when you only need a small number of statically-defined # set of User accounts. # 此处是用户信息,以及用户与角色对应关系,格式为 username=password,roleName1,roleName2,roleName3,…… Java=123,user,admin Go=123 Python=123,user [roles] # The 'roles' section is for simple deployments # when you only need a small number of statically-defined # roles. # 角色与权限对应关系,格式:rolename = permissionDefinition1, permissionDefinition2,…… user=user:delete,user:modify,user:login admin=user:delete [urls] # The 'urls' section is used for url-based security # in web applications. We'll discuss this section in the # Web documentation #用于配置网页过滤规则 /some/path = ssl, authc /another/path = ssl, roles[admin]
JdbcRealm包含默认的数据库查询语句,直接使用便可,但要注意建立的表结构要跟查询语句相对应。固然也能够本身去自定义查询语句和数据库。
mavern依赖:
<!--数据库相关--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency>
默认的查询语句
create database shiro;
use shiro;
SET FOREIGN_KEY_CHECKS=0;DROP TABLE IF EXISTS
roles_permissions
;
CREATE TABLEroles_permissions
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
role_name
varchar(100) DEFAULT NULL,
permission
varchar(100) DEFAULT NULL,
PRIMARY KEY (id
),
UNIQUE KEYidx_roles_permissions
(role_name
,permission
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO
roles_permissions
VALUES (null,'admin','user:delete');DROP TABLE IF EXISTS
users
;
CREATE TABLEusers
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
username
varchar(100) DEFAULT NULL,
password
varchar(100) DEFAULT NULL,
password_salt
varchar(100) DEFAULT NULL,
PRIMARY KEY (id
),
UNIQUE KEYidx_users_username
(username
)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;INSERT INTO
users
VALUES ('1', 'java', '123', null);DROP TABLE IF EXISTS
roles_permissionsroles_permissionsidrole_namepermissionididx_roles_permissionsrole_namepermissionroles_permissionsusersusersidusernamepasswordpassword_saltididx_users_usernameusernameusersuser_rolesuser_rolesidusernamerole_nameididx_user_rolesusernamerole_nameINSERT INTOuser_roles
;
CREATE TABLEuser_roles
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
username
varchar(100) DEFAULT NULL,
role_name
varchar(100) DEFAULT NULL,
PRIMARY KEY (id
),
UNIQUE KEYidx_user_roles
(username
,role_name
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;user_roles
VALUES (null,'java','admin');
user_roles
JdbcRealmTest.java:
package com.lifeofcoding.shiro; import com.alibaba.druid.pool.DruidDataSource; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.jdbc.JdbcRealm; import org.apache.shiro.subject.Subject; import org.junit.Test; public class JdbcRealmTest { DruidDataSource druidDataSource = new DruidDataSource(); { druidDataSource.setUrl("jdbc:mysql://localhost:3306/shiro"); druidDataSource.setUsername("root"); druidDataSource.setPassword("0113"); } @Test public void testJdbcRealm(){ //1.建立Realm并添加数据 JdbcRealm jdbcRealm = new JdbcRealm(); jdbcRealm.setDataSource(druidDataSource);//配置数据源 jdbcRealm.setPermissionsLookupEnabled(true);//设置容许查询权限,不然checkPermission抛异常,默认值为false //2.建立SecurityManager并配置环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(jdbcRealm); //3.建立subject SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); //4.Subject经过Token提交认证 UsernamePasswordToken token = new UsernamePasswordToken("java","123"); subject.login(token);//退出登陆subject.logout(); //验证认证与受权状况 System.out.println("isAuthenticated: "+ subject.isAuthenticated()); subject.hasRole("admin"); subject.checkPermission("user:delete"); } }
自定义的查询语句
DROP TABLE IF EXISTS test_users;
CREATE TABLE test_users (
user_name varchar(20) DEFAULT NULL,
password varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO test_users VALUES('java','123');
DROP TABLE IF EXISTS test_user_roles;
CREATE TABLE test_user_roles (
user_name varchar(20) DEFAULT NULL,
role varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO test_user_roles VALUES('java','admin');
DROP TABLE IF EXISTS test_roles_permissions;
INSERT INTO test_roles_permissions VALUES('admin','user:delete');
CREATE TABLE test_roles_permissions (
role varchar(20) DEFAULT NULL,
permission varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
MyJdbcRealmTest.java:
import com.alibaba.druid.pool.DruidDataSource; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.jdbc.JdbcRealm; import org.apache.shiro.subject.Subject; import org.junit.Test; public class MyJdbcRealmTest { //从数据库获取对应用户密码实现认证 protected static final String AUTHENTICATION_QUERY = "select password from test_users where user_name = ?"; //从数据库中获取对应用户的全部角色 protected static final String USER_ROLES_QUERY = "select role from test_user_roles where user_name = ?"; //从数据库中获取角色对应的全部权限 protected static final String PERMISSIONS_QUERY = "select permission from test_roles_permissions where role = ?"; DruidDataSource druidDataSource = new DruidDataSource(); { druidDataSource.setUrl("jdbc:mysql://localhost:3306/shiro"); druidDataSource.setUsername("root"); druidDataSource.setPassword("0113"); } @Test public void testMyJdbcRealm(){ //1.建立Realm并设置数据库查询语句 JdbcRealm jdbcRealm = new JdbcRealm(); jdbcRealm.setDataSource(druidDataSource);//配置数据源 jdbcRealm.setPermissionsLookupEnabled(true);//设置容许查询权限,不然checkPermission抛异常,默认值为false jdbcRealm.setAuthenticationQuery(AUTHENTICATION_QUERY);//设置用户认证信息查询语句 jdbcRealm.setUserRolesQuery(USER_ROLES_QUERY);//设置用户角色信息查询语句 jdbcRealm.setPermissionsQuery(PERMISSIONS_QUERY);//设置角色权限信息查询语句 //2.建立SecurityManager并配置环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(jdbcRealm); //3.建立subject SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); //4.Subject经过Token提交认证 UsernamePasswordToken token = new UsernamePasswordToken("java","123"); subject.login(token);//退出登陆subject.logout(); //验证认证与受权状况 System.out.println("isAuthenticated: "+ subject.isAuthenticated()); subject.hasRole("admin"); subject.checkPermission("user:delete"); } }
自定义Realm,能够继承抽象类AuthorizingRealm,实现其两个方法——doGetAuthenticationInfo和doGetAuthorizationInfo,分别用来返回AuthenticationInfo(认证信息)和AuthorizationInfo(受权信息)。
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //1.获取主体中的用户名 String userName = (String) authenticationToken.getPrincipal(); //2.经过用户名获取密码,getPasswordByName自定义实现 String password = getPasswordByUserName(userName); if(null == password){ return null; } //构建AuthenticationInfo返回 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName,password,"MyRealm"); return authenticationInfo; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //1.获取用户名。principal为Object类型,是用户惟一标识,能够是用户名,用户邮箱,数据库主键等,能惟一肯定一个用户的信息。 String userName = (String) principalCollection.getPrimaryPrincipal(); //2.获取角色信息,getRoleByUserName自定义 Set<String> roles = getRolesByUserName(userName); //3.获取权限信息,getPermissionsByRole方法一样自定义,也能够经过用户名查找权限信息 Set<String> permissions = getPermissionsByUserName(userName); //4.构建认证信息并返回。 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setStringPermissions(permissions); simpleAuthorizationInfo.setRoles(roles); return simpleAuthorizationInfo; }
完整的,包含添加用户功能的自定义Realm
MyRealm.java:
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; import org.apache.shiro.util.CollectionUtils; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class MyRealm extends AuthorizingRealm { /**存储用户名和密码*/ protected final Map<String,String> userMap; /**存储用户及其对应的角色*/ protected final Map<String, Set<String>> roleMap; /**存储全部角色以及角色对应的权限*/ protected final Map<String,Set<String>> permissionMap; { //设置Realm名 super.setName("MyRealm") ; } public MyRealm(){ userMap = new ConcurrentHashMap<>(16); roleMap = new ConcurrentHashMap<>(16); permissionMap = new ConcurrentHashMap<>(16); } /** * 身份认证必须实现的方法 * @param authenticationToken token * @return org.apache.shiro.authc.AuthenticationInfo */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //1.获取主体中的用户名 String userName = (String) authenticationToken.getPrincipal(); //2.经过用户名获取密码,getPasswordByName自定义实现 String password = getPasswordByUserName(userName); if(null == password){ return null; } //构建AuthenticationInfo返回 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName,password,"MyRealm"); return authenticationInfo; } /** * 用于受权 * @return org.apache.shiro.authz.AuthorizationInfo */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //1.获取用户名。principal为Object类型,是用户惟一标识,能够是用户名,用户邮箱,数据库主键等,能惟一肯定一个用户的信息。 String userName = (String) principalCollection.getPrimaryPrincipal(); //2.获取角色信息,getRoleByUserName自定义 Set<String> roles = getRolesByUserName(userName); //3.获取权限信息,getPermissionsByRole方法一样自定义,也能够经过用户名查找权限信息 Set<String> permissions = getPermissionsByUserName(userName); //4.构建认证信息并返回。 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setStringPermissions(permissions); simpleAuthorizationInfo.setRoles(roles); return simpleAuthorizationInfo; } /** * 自定义部分,经过用户名获取权限信息 * @return java.util.Set<java.lang.String> */ private Set<String> getPermissionsByUserName(String userName) { //1.先经过用户名获取角色信息 Set<String> roles = getRolesByUserName(userName); //2.经过角色信息获取对应的权限 Set<String> permissions = new HashSet<String>(); //3.添加每一个角色对应的全部权限 roles.forEach(role -> { if(null != permissionMap.get(role)) { permissions.addAll(permissionMap.get(role)); } }); return permissions; } /** * 自定义部分,经过用户名获取密码,可改成数据库操做 * @param userName 用户名 * @return java.lang.String */ private String getPasswordByUserName(String userName){ return userMap.get(userName); } /** * 自定义部分,经过用户名获取角色信息,可改成数据库操做 * @param userName 用户名 * @return java.util.Set<java.lang.String> */ private Set<String> getRolesByUserName(String userName){ return roleMap.get(userName); } /** * 往realm添加帐号信息,变参不传值会接收到长度为0的数组。 */ public void addAccount(String userName,String password) throws UserExistException{ addAccount(userName,password,(String[]) null); } /** * 往realm添加帐号信息 * @param userName 用户名 * @param password 密码 * @param roles 角色(变参) */ public void addAccount(String userName,String password,String... roles) throws UserExistException{ if( null != userMap.get(userName) ){ throw new UserExistException("user \""+ userName +" \" exists"); } userMap.put(userName, password); roleMap.put(userName, CollectionUtils.asSet(roles)); } /** * 从realm删除帐号信息 * @param userName 用户名 */ public void delAccount(String userName){ userMap.remove(userName); roleMap.remove(userName); } /** * 添加角色权限,变参不传值会接收到长度为0的数组。 * @param roleName 角色名 */ public void addPermission(String roleName,String...permissions){ permissionMap.put(roleName,CollectionUtils.asSet(permissions)); } /**用户已存在异常*/ public class UserExistException extends Exception{ public UserExistException(String message){super(message);} } }
测试代码
MyRealmTest.java:
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.subject.Subject; import org.junit.Test; public class MyRealmTest { @Test public void testMyRealm(){ //1.建立Realm并添加数据 MyRealm myRealm = new MyRealm(); try { myRealm.addAccount("java", "123", "admin"); }catch (Exception e){ e.printStackTrace(); } myRealm.addPermission("admin","user:delete"); //2.建立SecurityManager并配置环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(myRealm); //3.建立subject SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); //4.Subject经过Token提交认证 UsernamePasswordToken token = new UsernamePasswordToken("java","123"); subject.login(token);//退出登陆subject.logout(); //验证认证与受权状况 System.out.println("isAuthenticated: "+ subject.isAuthenticated()); subject.hasRole("admin"); subject.checkPermission("user:delete"); } }
使用加密,只须要在Realm返回的AuthenticationInfo添加用户密码对应的盐值:
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //1.获取主体中的用户名 String userName = (String) authenticationToken.getPrincipal(); //2.经过用户名获取密码,getPasswordByName自定义实现 String password = getPasswordByUserName(userName); if(null == password){ return null; } //3.构建authenticationInfo认证信息 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName,password,"MyRealm"); //设置盐值 String salt = getSaltByUserName(userName); authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(salt)); return authenticationInfo; }
而且在测试代码中给Realm设置加密:
//设置加密 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("MD5");//设置加密算法 matcher.setHashIterations(3);//设置加密次数 myEncryptedRealm.setCredentialsMatcher(matcher);
CredentialMatcher用来匹配用户密码。shiro经过Realm获取AuthenticationInfo,AuthenticationInfo里面包含该用户的principal、credential、salt。principal就是用户名或手机号或其余,credential就是密码(加盐加密后,存到数据源中的密码),salt就是密码对应的“盐”。shiro获取到这些信息以后,利用CredentialMatcher中配置的信息(加密算法,加密次数等),对token中用户输入的、待校验的密码,进行加盐加密,而后比对结果是否和AuthenticationInfo中的credential一致,若一致,则用户经过认证。
“加密”算法通常用的是hash算法,hash算法并非用来加密的,而是用来生成信息摘要,该过程是不可逆的,不能从结果逆推得出用户的密码的原文。下文这一段话也是相对于hash算法而言,其余算法不在考虑范围内。shiro的CredentialMatcher并无“解密”这个概念,由于不能解密,不能把数据库中“加密”后的密码还原,只能对用户输入的密码,进行一次相同的“加密”,而后比对数据库的“加密”后的密码,从而判断用户输入的密码是否正确。
必要地,在存密码时,要存储加盐加密后的密码:
public void addAccount(String userName,String password,String... roles) throws UserExistException { if(null != userMap.get(userName)){ throw new UserExistException("user \""+ userName +"\" exist"); } //若是配置的加密次数大于0,则进行加密 if(iterations > 0){ //使用随机数做为密码,可改成UUID或其它 String salt = String.valueOf(Math.random()*10); saltMap.put(userName,salt); password = doHash(password, salt); } userMap.put(userName, password); roleMap.put(userName, CollectionUtils.asSet(roles)); }
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.crypto.hash.SimpleHash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.apache.shiro.util.CollectionUtils; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class MyEncryptedRealm extends AuthorizingRealm { /** 加密次数 */ private int iterations; /** 算法名 */ private String algorithmName; /** 存储用户名和密码 */ private final MapuserMap; /** 存储用户及其对应的角色 */ private final Map > roleMap; /** 存储全部角色以及角色对应的权限 */ private final Map > permissionMap; /** 存储用户盐值 */ private Map saltMap; { //设置Realm名 super.setName("MyRealm"); } public MyEncryptedRealm(){ this.iterations = 0; this.algorithmName = "MD5"; this.userMap = new ConcurrentHashMap<>(16); this.roleMap = new ConcurrentHashMap<>(16); this.permissionMap = new ConcurrentHashMap<>(16); this.saltMap = new ConcurrentHashMap<>(16); } /** * 身份认证必须实现的方法 * @param authenticationToken token * @return org.apache.shiro.authc.AuthenticationInfo */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //1.获取主体中的用户名 String userName = (String) authenticationToken.getPrincipal(); //2.经过用户名获取密码,getPasswordByName自定义实现 String password = getPasswordByUserName(userName); if(null == password){ return null; } //3.构建authenticationInfo认证信息 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName,password,"MyRealm"); //设置盐值 String salt = getSaltByUserName(userName); authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(salt)); return authenticationInfo; } /** * 用于受权 * @return org.apache.shiro.authz.AuthorizationInfo */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //1.获取用户名。principal为Object类型,是用户惟一凭证,能够是用户名,用户邮箱,数据库主键等,能惟一肯定一个用户的信息。 String userName = (String) principalCollection.getPrimaryPrincipal(); //2.获取角色信息,getRoleByUserName自定义 Set roles = getRolesByUserName(userName); //3.获取权限信息,getPermissionsByRole方法一样自定义,也能够经过用户名查找权限信息 Set permissions = getPermissionsByUserName(userName); //4.构建认证信息并返回。 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setStringPermissions(permissions); simpleAuthorizationInfo.setRoles(roles); return simpleAuthorizationInfo; } /** * 自定义部分,经过用户名获取权限信息 * @return java.util.Set */ private Set getPermissionsByUserName(String userName) { //1.先经过用户名获取角色信息 Set roles = getRolesByUserName(userName); //2.经过角色信息获取对应的权限 Set permissions = new HashSet<>(); //3.添加每一个角色对应的全部权限 roles.forEach(role -> { if (null != permissionMap.get(role)) { permissions.addAll(permissionMap.get(role)); } }); return permissions; } /** * 自定义部分,经过用户名获取密码,可改成数据库操做 * @return java.lang.String */ private String getPasswordByUserName(String userName){ return userMap.get(userName); } /** * 自定义部分,经过用户名获取角色信息,可改成数据库操做 * @return java.util.Set */ private Set getRolesByUserName(String userName){ return roleMap.get(userName); } /** * 自定义部分,经过用户名获取盐值,可改成数据库操做 * @return java.util.Set */ private String getSaltByUserName(String userName) { return saltMap.get(userName); } /** * 往realm添加帐号信息,变参不传值会接收到长度为0的数组。 */ public void addAccount(String userName,String password) throws UserExistException { addAccount(userName,password,(String[]) null); } /** * 往realm添加帐号信息 */ public void addAccount(String userName,String password,String... roles) throws UserExistException { if(null != userMap.get(userName)){ throw new UserExistException("user \""+ userName +"\" exist"); } //若是配置的加密次数大于0,则进行加密 if(iterations > 0){ String salt = String.valueOf(Math.random()*10); saltMap.put(userName,salt); password = doHash(password, salt); } userMap.put(userName, password); roleMap.put(userName, CollectionUtils.asSet(roles)); } /** * 从realm删除帐号信息 * @param userName 用户名 */ public void deleteAccount(String userName){ userMap.remove(userName); roleMap.remove(userName); } /** * 添加角色权限,变参不传值会接收到长度为0的数组。 * @param roleName 角色名 */ public void addPermission(String roleName,String...permissions){ permissionMap.put(roleName, CollectionUtils.asSet(permissions)); } /** * 设置加密次数 * @param iterations 加密次数 */ public void setHashIterations(int iterations){ this.iterations = iterations; } /** * 设置算法名 * @param algorithmName 算法名 */ public void setAlgorithmName(String algorithmName){ this.algorithmName = algorithmName; } /** * 计算哈希值 * @param str 要进行"加密"的字符串 * @param salt 盐 * @return String */ private String doHash(String str,String salt){ salt = null==salt ? "" : salt; return new SimpleHash(this.algorithmName,str,salt,this.iterations).toString(); } /** * 注册时,用户已存在的异常 */ public class UserExistException extends Exception{ public UserExistException(String message) {super(message);} } }
package com.lifeofcoding.shiro; 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; /** * @Classname MyEncryptionRealmTest * @Description TODO * @Date 2019-11-2019-11-20-17:41 * @Created by yo */ public class MyEncryptedRealmTest { private MyEncryptedRealm myEncryptedRealm; @Test public void testShiroEncryption() { //1.建立Realm并添加数据 MyEncryptedRealm myEncryptedRealm = new MyEncryptedRealm(); myEncryptedRealm.setHashIterations(3); myEncryptedRealm.setAlgorithmName("MD5"); try { myEncryptedRealm.addAccount("java", "123456", "admin"); }catch (Exception e){ e.printStackTrace(); } myEncryptedRealm.addPermission("admin","user:create","user:delete"); //2.建立SecurityManager对象 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(myEncryptedRealm); //3.设置加密 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("MD5");//设置加密算法 matcher.setHashIterations(3);//设置加密次数 myEncryptedRealm.setCredentialsMatcher(matcher); //4.建立主体并提交认证 SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("java","123456"); subject.login(token); System.out.println(subject.getPrincipal()+" isAuthenticated: "+subject.isAuthenticated()); subject.checkRole("admin"); subject.checkPermission("user:delete"); } }