一、shiro核心组件

 

 Subject

Subject主体,外部应用与subject进行交互,subject将用户作为当前操作的主体,这个主体:可以是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权 

SecurityManager

SecurityManager权限管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口

Authenticator

Authenticator即认证器,对用户登录时进行身份认证

Authorizer

Authorizer授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

Realm

Realm领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据
比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

SessionManager

SessionManager会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。 

SessionDAO

SessionDAO即会话dao,是对session会话操作的一套接口 
比如:可以通过jdbc将会话存储到数据库 也可以把session存储到缓存服务器

CacheManager

CacheManager缓存管理,将用户权限数据存储在缓存,这样可以提高性能 

Cryptography

Cryptography密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能

 二、身份认证

 流程如下:

1、Shiro把用户的数据封装成标识token,token一般封装着用户名,密码等信息
2、使用Subject门面获取到封装着用户的数据的标识token
3、Subject把标识token交给SecurityManager,在SecurityManager安全中心中,SecurityManager把标识token委托给认证器Authenticator进行身份验证。认证器的作用一般是用来指定如何验证,它规定本次认证用到哪些Realm
4、认证器Authenticator将传入的标识token,与数据源Realm对比,验证token是否合法

实例代码

引入shiro核心库依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.3.2</version>
</dependency>
        //导入权限ini文件构建权限工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //工厂构建安全管理器
        SecurityManager securityManager = factory.getInstance();
        //使用SecurityUtils工具生效安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //使用SecurityUtils工具获得主体
        Subject subject = SecurityUtils.getSubject();
        //构建账号token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jay", "123");
        //登录操作
        subject.login(usernamePasswordToken);
        System.out.println("是否登录成功:" + subject.isAuthenticated());

返回:是否登录成功:true

 三、Realm

 

 一般在真实的项目中,我们不会直接实现Realm接口,我们一般的情况就是直接继承AuthorizingRealm,能够继承到认证与授权功能。它需要强制重写两个方法

public class DefinitionReam extends AuthorizingRealm {
    /**
     * 认证
     * @param token 对象
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        return null;
    }

    /**
     *  鉴权
     * @param principals 令牌
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}

自定义realm范例:

public class DefinitionRealm extends AuthorizingRealm {
    /**
     * 认证
     * @param token 对象
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String loginName = (String) token.getPrincipal();
        SecurityService securityService = new SecurityService();
        String password = securityService.findPasswordByLoginName(loginName);
        if ("".equals(password)||password==null){
            throw new UnknownAccountException("账户不存在");
        }
        //传递账号和密码
        return new SimpleAuthenticationInfo(loginName, password, getName());
    }

    /**
     *  鉴权
     * @param principals 令牌
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}
public class SecurityService {
    public String findPasswordByLoginName(String loginName) {
        return "123";
    }
}

shiro.ini

#声明自定义的realm,且为安全管理器指定realms
[main]
definitionRealm=com.suxiaodong.shiro.DefinitionRealm
securityManager.realms=$definitionRealm

 四、Realm使用SHA1散列算法认证

shiro配置文件ini同上

自定义realm:

public class DefinitionRealm extends AuthorizingRealm {
    public DefinitionRealm() {
        //指定密码匹配方式sha1
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(DigestsUtil.SHA1);
        //指定密码迭代次数
        hashedCredentialsMatcher.setHashIterations(DigestsUtil.ITERATIONS);
        //使用父层方法指定匹配方式生效
        setCredentialsMatcher(hashedCredentialsMatcher);
    }

    /**
     * @Description 认证方法
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取登录名
        String loginName = (String) authenticationToken.getPrincipal();
        SecurityService securityService = new SecurityService();
        Map<String, String> map = securityService.findPasswordByLoginName(loginName); //模拟从数据库取
        if(map.isEmpty()){
            throw  new UnknownAccountException("账户不存在");
        }
        String salt = map.get("salt");
        String password = map.get("password");
        return new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName());
    }

    /**
     * @Description 鉴权方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}
public class SecurityService {
    public Map<String,String> findPasswordByLoginName(String loginName) {
        return DigestsUtil.entryptPassword("123");
    }
}
public class DigestsUtil {
    public static final String SHA1 = "SHA-1";
    public static final Integer ITERATIONS =512;

    /**
     * @Description sha1方法
     * @param input 需要散列字符串
     * @param salt 盐字符串
     * @return
     */
    public static String sha1(String input, String salt) {
       return new SimpleHash(SHA1, input, salt,ITERATIONS).toString();
    }

    /**
     * @Description 随机获得salt字符串
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        return randomNumberGenerator.nextBytes().toHex();
    }


    /**
     * @Description 生成密码字符密文和salt密文
     * @param
     * @return
     */
    public static Map<String,String> entryptPassword(String passwordPlain) {
       Map<String,String> map = new HashMap<>();
       String salt = generateSalt();
       String password =sha1(passwordPlain,salt);
       map.put("salt", salt);
       map.put("password", password);
       return map;
    }
}

 五、身份授权

 

1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager;

2、SecurityManager接着会委托给内部组件Authorizer;

3、Authorizer再将其请求委托给我们的Realm去做;Realm才是真正干活的;

4、Realm将用户请求的参数封装成权限对象。再从我们重写的doGetAuthorizationInfo方法中获取从数据库中查询到的权限集合。

5、Realm将用户传入的权限对象,与从数据库中查出来的权限对象,进行一一对比。如果用户传入的权限对象在从数据库中查出来的权限对象中,则返回true,否则返回false。
进行授权操作的前提:用户必须通过认证。

 范例:

DefinitionRealm:

    /**
     * @Description 鉴权方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //拿到用户凭证信息
        String loginName = (String) principalCollection.getPrimaryPrincipal();
        //从数据库中查询对应的角色和权限
        SecurityService securityService = new SecurityService();
        List<String> roles = securityService.findRoleByLoginName(loginName);
        List<String> permissions = securityService.findPermissionByLoginName(loginName);
        //构建资源校验对象
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(roles);
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

 SecurityService:

    public List<String> findRoleByLoginName(String loginName) {
        List<String> list = new ArrayList<>();
        list.add("admin");
        list.add("dev");
        return list;
    }

    public List<String> findPermissionByLoginName(String loginName) {
        List<String> list = new ArrayList<>();
        list.add("order:add");
        list.add("order:list");
        list.add("order:del");
        return list;
    }

main:

       Subject subject = shiroLogin();
        //打印登录信息
        System.out.println("登录结果:"+subject.isAuthenticated());
        //-------校验当前用户是否拥有管理员的架势
        System.out.println("是否有管理员角色"+subject.hasRole("admin"));
        //-------校验当前用户没有的角色
        try {
            subject.checkRole("coder");
            System.out.println("当前用户有coder角色");
        }catch (Exception ex){
            System.out.println("当前用户没有coder角色");
        }
        //-------校验当前用户的权限信息
        System.out.println("是否有查看订单的权限"+subject.isPermitted("order:list"));
        //-------校验当前用户满意的权限
        try {
            subject.checkPermission("order:update");
            System.out.println("当前用户有修改的权限");
        }catch (Exception ex){
            System.out.println("当前用户没有修改的权限");
        }

登录结果:true

是否有管理员角色true

当前用户没有coder角色

是否有查看订单的权限true

当前用户没有修改的权限