shiro Pit Guide - Basic concepts and practical

Explanation

  Code and some related information according to Mu-class network Mark teacher of video finishing.

  other information:

Basic concepts

Authenticate/Authentication(认证)

  Certification means checking the legitimacy of the user identity and password by checking user input is correct, to determine whether the user is himself.

  There are several concepts need to understand:

  • The Principals are (body identification)
    anything that can uniquely identify a user's attributes can act as a principal, such as: email, phone number, user ID, etc., which are one to one with the user, can uniquely identify a user.

  • Credentials (certificates body)
    Credentials user is able to confirm the identity of the things that can be a certificate (Certificate), can also be password (password).

  • token (Token)
    token in the token and api Here a little difference here is the principal and credential token combination or container. Here to talk about the part, and the rest into the "Subject" to explain.

Authorize/Authorization(授权)

  shiro in the "authorization" is more appropriate to say that "authentication", that is, whether the user has certain rights, as has the authority on what is the point in the business, is determined by the business itself.

  About "authorization", shiro introduces two concepts:

  • Role (role)
    character used to distinguish between categories of users. Between roles and users-many relationship, a user can have multiple roles, such as Bob can be admin (administrator) and user (general user) at the same time.

  • Permission (authority)
    authority is a specific description of the role, serving to illustrate the special nature of the role in business. Such as admin (administrator) you can have the user: delete (delete user), user: modify (modify user information) and other privileges. Similarly, the number of roles and permissions are many relationship.
    shiro permissions can be graded using ":" segmentation, such as delete, user: delete, user: info: delete. Can use all rights "*" as a wildcard, for example, can be given operation of the user to ADMIN, may be configured to "user: *", so that authorization, isPermitted ( "user: 123" ), isPermitted ( "user: 123: abc ") returns are true; if configured as" *: *: delete ", want to return true, you need permission to do similar: isPermitted (" 123: abc: delete "), isPermitted (" hello: 321: delete " ).

Subject (body)

  Subject object applications and related components for shiro interact, it can be seen as an application of "user" agent, can be regarded as shiro "User." For instance, in one application, User objects as well as program business, "user", when implementing authentication and authorization shiro, and does not directly use the User object shiro components interact, but the User object information (username and password) to the Subject, Subject to call their own methods, authentication or authorization to initiate shiro components.
  The following is a method Subject interface provided, including login (login), exit (logout), certification (isAuthenticated), authorization (checkPermission) such as:



  Following the above continue to speak Token, you can see in the picture, login method needs to pass a AuthenticationToken type parameter, which is an interface point to go to see Jiang Zi:

This interface has two methods were used to return principal and credential. Thus Token is the principal and credential container for Subject Submit "Login" transmission of information request.
  Need to be reminded, Subject of these methods were in fact calling SecurityManager authentication and authorization process. Subject only facade business processes and communication of SecurityManager.

SecurityManager

  As the name implies, is used to the SecurityManager Manage (management), shiro management authentication and authorization processes, management shiro components, some of the data management shiro, etc. Session management.
  Here's inheritance SecurityManager interface:


  SecurityManager inherited Authorizer (authorization), Authenticator (certification), and SessionManager (Session Management). It should be noted here refers to Session and Session Subject SecurityManager communication can not be narrowly understood as WebSession. After a SecurityManager inherited these interfaces, and additionally provides a login, logout and createSubject method.

Realm (Domain)

  与Subject和SecurityManager一样,Realm是shiro中的三大核心组件之一。Realm相当于DAO,用于获取与用户安全相关的数据(用户密码、角色、权限等)。当Subject发起认证和授权时,实际上是调用其对应的SecurityManager的认证和授权的方法,而SecurityManager则又是调用Authenticator和Authorizer的方法,这两个类,最后是通过Realm来获取主体的认证和授权信息。
  shiro的认证和授权过程如下所示:


使用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处理。

shiro认证授权示例

  Realm using a SimpleAccountRealm , SimpleAccountRealm directly to the user authentication data stored to the instance, SecurityManager used DefaultSecurityManager , SecurityUtils created using the Subject , with the Token UsernamePasswordToken . Testing with Junit. maven dependency follows:

<!--单元测试-->
<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>

shiro authentication example

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();
    }
}

shiro authorization example

  SimpleAccountRealm add user roles and permissions method is relatively simple and can be on her own. Realm here to switch to IniRealm, iniRealm need to write information stored in the user's ini file, ini file in the resource folder. Code is as follows:
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
Demo ini file

[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]

Here are other Realm


JdbcRealm

  JdbcRealm contains the default database query, can be used directly, but pay attention to create a table structure to keep the query, respectively. Of course, you can go your own custom query and database.
  mavern dependence:

<!--数据库相关-->
<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>

The default query

The default query


Default 'users' table structure

default 'user_roles' table structure

default 'roles_permissions' table structure

Database sql statement

create database shiro;
use shiro;
SET FOREIGN_KEY_CHECKS=0;

DROP TABLE IF EXISTS roles_permissions;
CREATE TABLE roles_permissions (
id bigint(20) NOT NULL AUTO_INCREMENT,
role_name varchar(100) DEFAULT NULL,
permission varchar(100) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY idx_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 TABLE users (
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 KEY idx_users_username (username)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO users VALUES ('1', 'java', '123', null);

DROP TABLE IF EXISTS user_roles;
CREATE TABLE user_roles (
id bigint(20) NOT NULL AUTO_INCREMENT,
username varchar(100) DEFAULT NULL,
role_name varchar(100) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY idx_user_roles (username,role_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO user_roles VALUES (null,'java','admin');


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");
    }
}

Custom query

Custom query


Since 'test_users' table structure definition

'test_user_roles' custom table structure

'test_roles_permissions' custom table structure

Database sql statement

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;
CREATE TABLE test_roles_permissions (
role varchar(20) DEFAULT NULL,
permission varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO test_roles_permissions VALUES('admin','user:delete');

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");
    }
}

Custom Realm

  Custom Realm, can inherit the abstract class AuthorizingRealm, and achieve its two methods --doGetAuthenticationInfo doGetAuthorizationInfo, respectively, to return AuthenticationInfo (authentication information) and AuthorizationInfo (authorization information).


As indicated above, AuthenticationInfo comprising principal and crendential, and the same token, the difference is that the former is surly data in a database or other data source, which is input by the user, data to be verified.

AuthorizationInfo is similar, contains the user's role and permissions information.
Can SimpleAuthenticationInfo and SimpleAuthorizationInfo to achieve these two methods:

@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;
}

Complete, contains the user to add custom functionality 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);}
    }
}

Test code
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");
    }
}

encryption

  Using encryption, the user need only add the salt value corresponding to a password in the return AuthenticationInfo Realm:

@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;
}

  And the Realm provided to the encryption code in the test:

//设置加密
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));
}
MyEncryptedRealm.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.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 Map
   
   
    
     userMap;
    /** 存储用户及其对应的角色 */
    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);} } } 
                
               
              
             
            
           
          
         
       
      
      
     
     
    
    
   
   
MyEncryptedRealmTest.java
 
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");
    }
}

文件传送门

github地址

Guess you like

Origin www.cnblogs.com/life-of-coding/p/12173634.html