概念

Shiro 是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序—从最小的移动应用程序到最大的web和企业应用程序。
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

核心架构

ShiroArchitecture.png

Subject

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

SecurityManager

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

Authenticator

Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

Authorizer

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

Realm

Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

  • 注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

SessionManager

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

SessionDAO

SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

CacheManager

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

Cryptography

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

认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。

shiro中认证的关键对象

  • Subject:主体:访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
  • Principal:身份信息:是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
  • credential:凭证信息:是只有主体自己知道的安全信息,如密码、证书等。

认证开发

  1. ini文件
  2. [users]
  3. xiaochen=123
  4. zhangsan=456
  5. public static void main(String[] args) {
  6. //1.创建安全管理器对象
  7. DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
  8. //2.给安全管理器设置realm - 读取ini配置文件
  9. defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));
  10. //3.全局安全工具类
  11. SecurityUtils.setSecurityManager(defaultSecurityManager);
  12. //4.获取主体
  13. Subject subject = SecurityUtils.getSubject();
  14. //5.令牌
  15. UsernamePasswordToken token = new UsernamePasswordToken("admin","admin");
  16. //6.认证
  17. try {
  18. subject.login(token);//用户登录
  19. System.out.println("登录成功");
  20. } catch (UnknownAccountException e) {
  21. e.printStackTrace();
  22. System.out.println("用户名错误");
  23. }catch (IncorrectCredentialsException e){
  24. e.printStackTrace();
  25. System.out.println("密码错误");
  26. }
  27. }
  28. //其他异常
  29. DisabledAccountException(帐号被禁用)
  30. LockedAccountException(帐号被锁定)
  31. ExcessiveAttemptsException(登录失败次数过多)
  32. ExpiredCredentialsException(凭证过期)等

自定义Realm

  1. public static void main(String[] args) {
  2. //1.创建安全管理器对象
  3. DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
  4. //2.给安全管理器设置realm
  5. defaultSecurityManager.setRealm(new CustomerRealm());
  6. //3.全局安全工具类
  7. SecurityUtils.setSecurityManager(defaultSecurityManager);
  8. //4.获取主体
  9. Subject subject = SecurityUtils.getSubject();
  10. //5.令牌
  11. UsernamePasswordToken token = new UsernamePasswordToken("admin","admin");
  12. //6.认证
  13. try {
  14. subject.login(token);//用户登录
  15. System.out.println("登录成功~~");
  16. } catch (UnknownAccountException e) {
  17. e.printStackTrace();
  18. System.out.println("用户名错误!!");
  19. }catch (IncorrectCredentialsException e){
  20. e.printStackTrace();
  21. System.out.println("密码错误!!!");
  22. }
  23. }
  24. //自定义realm
  25. class CustomerRealm extends AuthorizingRealm {
  26. //授权
  27. @Override
  28. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  29. return null;
  30. }
  31. //认证
  32. @Override
  33. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  34. //在token中获取用户名
  35. String principal = (String) token.getPrincipal();
  36. //根据身份信息,查询数据库,此处模拟 admin账户
  37. if("admin".equals(principal)){
  38. //参数1:数据库中正确的用户名。参数2:数据库中的正确密码。参数3:提供当前realm的名称 this.getName()
  39. SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal,"admin",this.getName());
  40. return info;
  41. }
  42. return null;
  43. }
  44. }

MD5 + salt盐 + hash散列 加密认证

  1. //shiro提供,创建Md5算法
  2. Md5Hash md5Hash = new Md5Hash("123");
  3. System.out.println(md5Hash.toHex());
  4. //加盐 md5 + salt
  5. Md5Hash md5Hash1 = new Md5Hash("123","fslkdjflks");
  6. System.out.println(md5Hash1.toHex());
  7. //md5 + salt + hash散列
  8. Md5Hash md5Hash2 = new Md5Hash("123","fslkdjflks",1024);
  9. System.out.println(md5Hash2.toHex());
  10. //测试
  11. public static void main(String[] args) {
  12. //1.创建安全管理器对象
  13. DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
  14. //2.设置realm使用hash凭证匹配器
  15. CustomerRealm customerRealm = new CustomerRealm();
  16. HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
  17. matcher.setHashAlgorithmName("md5");//设置md5算法
  18. matcher.setHashIterations(1024);//hash散列次数
  19. customerRealm.setCredentialsMatcher(matcher);
  20. //3.给安全管理器设置realm
  21. defaultSecurityManager.setRealm(customerRealm);
  22. //4.全局安全工具类
  23. SecurityUtils.setSecurityManager(defaultSecurityManager);
  24. //5.获取主体
  25. Subject subject = SecurityUtils.getSubject();
  26. //6.令牌 - 此处的密码admin,需要换成与上方一直的md5+salt盐的密码
  27. UsernamePasswordToken token = new UsernamePasswordToken("admin","admin密码需要更换");
  28. //7.认证
  29. try {
  30. subject.login(token);//用户登录
  31. System.out.println("登录成功~~");
  32. } catch (UnknownAccountException e) {
  33. e.printStackTrace();
  34. System.out.println("用户名错误!!");
  35. }catch (IncorrectCredentialsException e){
  36. e.printStackTrace();
  37. System.out.println("密码错误!!!");
  38. }
  39. }
  40. //自定义realm,加入md5和随机盐和hash散列
  41. class CustomerRealm extends AuthorizingRealm {
  42. //授权
  43. @Override
  44. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  45. return null;
  46. }
  47. //认证
  48. @Override
  49. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  50. //获取身份信息
  51. String principal = (String) token.getPrincipal();
  52. //根据用户名,查询数据库
  53. if("admin".equals(principal)){
  54. //ByteSource.Util.bytes("") 随机盐
  55. return new SimpleAuthenticationInfo(principal,"admin",
  56. ByteSource.Util.bytes("jfksdljf"),this.getName());
  57. }
  58. return null;
  59. }
  60. }

授权

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

shiro中授权的关键对象

授权可简单理解为who对what(which)进行How操作:

  • Who,即主体(Subject),主体需要访问系统中的资源。
  • What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
  • How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。

授权方式

基于角色的访问控制

RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
if(subject.hasRole("admin")){
   //操作什么资源
}

**

基于资源的访问控制

RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制
if(subject.isPermission("user:update:01")){ //资源实例
  //对01用户进行修改
}
if(subject.isPermission("user:update:*")){  //资源类型
  //对01用户进行修改
}

权限字符串

权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。

用户创建权限:user:create,或user:create:*
用户修改实例001的权限:user:update:001
用户实例001的所有权限:user:*:001

授权开发

shiro中授权编程实现方式

编程式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
    //有权限
} else {
    //无权限
}

注解式
@RequiresRoles("admin")
public void hello() {
    //有权限
}

标签式
JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
<shiro:hasRole name="admin">
    <!— 有权限—>
</shiro:hasRole>
注意: Thymeleaf 中使用shiro需要额外集成!

开发


    public static void main(String[] args) {
        //1.创建安全管理器对象
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //2.设置realm使用hash凭证匹配器
        CustomerRealm customerRealm = new CustomerRealm();
//        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//        matcher.setHashAlgorithmName("md5");//设置md5算法
//        matcher.setHashIterations(1024);//hash散列次数
//        customerRealm.setCredentialsMatcher(matcher);
        //3.给安全管理器设置realm
        defaultSecurityManager.setRealm(customerRealm);
        //4.全局安全工具类
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //5.获取主体
        Subject subject = SecurityUtils.getSubject();
        //6.令牌
        UsernamePasswordToken token = new UsernamePasswordToken("admin","admin");
        //7.认证
        try {
            subject.login(token);//用户登录
            System.out.println("登录成功~~");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误!!");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误!!!");
        }

        //认证的用户进行授权
        if(subject.isAuthenticated()){
            //1.基于单角色的权限控制 - 如果有该角色则返回true,无则返回false
            System.out.println(subject.hasRole("admin"));

            //2.基于多角色的权限控制
            System.out.println(subject.hasAllRoles(Arrays.asList("admin","user")));

            //3.是否具有其中 1 个角色 - 返回boolean数组,与集合一一对应
            boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "super", "user"));
            for (boolean b : booleans) {
                //此处输出 : true  false  true
                System.out.println(b);
            }

            //4.基于权限字符串的访问控制, 资源标识符:操作:资源类型
            System.out.println("资源权限:" + subject.isPermitted("user:*:*"));

            //5.分别具有哪些权限 - 返回boolean数组
            boolean[] permitted = subject.isPermitted("user:*:01", "user:*:02", "order:*:10");

            //6.同时具有哪些权限 - 返回boolean数组
            boolean permittedAll = subject.isPermittedAll("user:*:01", "user:*:02", "order:*:10");

        }
    }
}


//自定义realm,加入md5和随机盐和hash散列
class CustomerRealm extends AuthorizingRealm {

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        System.out.println("身份信息:" + primaryPrincipal);
        //根据身份信息(用户名),获取当前用户的角色信息,以及权限信息。
        //此处假设admin用户有admin和user的权限
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        //将数据库查询的角色赋值给权限对象
        authorizationInfo.addRole("admin");
        authorizationInfo.addRole("user");

        //将数据库查询的权限信息赋值给权限对象
        authorizationInfo.addStringPermission("user:create:01"); //只对01用户有权限

        //返回权限对象
        return authorizationInfo;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取身份信息
        String principal = (String) token.getPrincipal();
        //根据用户名,查询数据库
        if("admin".equals(principal)){
            //ByteSource.Util.bytes("")  随机盐
            return new SimpleAuthenticationInfo(principal,"admin",this.getName());
        }
        return null;
    }
}