Shiro学习

Shiro

学习目标

知识点要求
Shiro简介掌握
Shiro架构原理掌握
INI文件介绍掌握
Shiro环境搭建及认证实现掌握
第一个Shiro演示掌握
授权掌握
加密及凭证匹配器掌握
自定义Realm掌握
凭证匹配器掌握
Spring Boot整合Shiro实现登录认证掌握
凭证匹配器掌握
Remember Me实现掌握
退出实现掌握
授权掌握
Thymeleaf整合shiro掌握
使用注解判断方法是否具有权限执行掌握
EHCache掌握
Shiro和EHCache整合掌握
Shiro中SessionManager管理掌握

一、Shiro 简介

1 概述

权限体系在现代软件应用中有着非常重要的地位。一个应用如果没有权限体系都会显着这个系统“特别不安全”,无论是传统的MIS系统还是互联网项目出于对业务数据和应用自身的安全,都会设置自己的安全策略。

目前市场上专门的Java权限框架有Apache Shiro 和 Spring Security。相较于Spring Security 来说 Shiro更加老牌,所以就先讲解Shiro,在后面的阶段中讲解Spring Security。学习好Shiro对于以后市场上在出现新型权限框架的学习能带来很大便利。因为权限的概念是不变的,变得是框架的实现方式。当然了,对于第一次学习权限框架的人来说,相较于权限框架的应用,更难的就是权限方面的概念。

2 Shiro官方解释

中文:Apache Shiro是一个强大的并且简单使用的java权限框架.主要应用认证(Authentication),授权(Authorization),cryptography(加密),和Session Manager.Shiro具有简单易懂的API,使用Shiro可以快速并且简单的应用到任何应用中,无论是从最小的移动app到最大的企业级web应用都可以使用.

img

3 核心功能

在Shiro官网首页上占用了很大的篇幅说明了Shiro的核心功能。

Authentication 认证。如用户的登录。

Authorization 授权。用户是否有权限访问指定URL等。

Cryptography 密码学。如密码的加密。

Session Management Session 管理。

Web Integration Web集成。Shiro不依赖于容器。

img

二、Shiro 架构原理

必须会。Shiro学习的重中之重。

1 第一行

第一行中示例出了一些语言。无论是什么语言都需要包含Subject

2 Subject

主体。每个用户登录成功后都会对应一个Subject对象,所有用户信息都存放在Subject中。可以理解:Subject就是Shiro提供的用户实体类。

3 Security Manager

Shiro最大的容器,此容器中包含了Shiro的绝大多数功能。在非Spring Boot项目中,获取Security Manager 是编写代码的第一步。而在Spring Boot中已经帮助我们自动化配置了。

4 Authenticator

认证器。执行认证过程调用的组件。里面包含了认证策略。

5 Authorizer

授权器。执行授权时调用的组件。

6 Session Manager

Shiro被Web集成后,HttpSession对象会由Shiro的Session Manager进行管理。

7 Cache Manager

缓存管理。Shiro执行很多第三方缓存技术。例如:EHCache等。

8 Session DAO

操作Session内容的组件。

9 Realms

Shiro框架实现权限控制不依赖于数据库,通过内置数据也可以实现权限控制。但是目前绝大多数应用的数据都存储在数据库中,所以Shiro提供了Realms组件,此组件的作用就是访问数据库。Shiro内置的访问数据库的代码,通过简单配置就可以访问数据库,也可以自定义Realms实现访问数据库逻辑(绝大多数都这么做)

ShiroArchitecture

三、INI文件介绍

INI英文名称(InitializationFile)

INI文件是Window系统配置文件的扩展名.

Shiro的全局配置文件就是.ini文件,ini中数据都是固定数据,后面会用数据库中数据替代下面users和roles(固定数据部分)

.ini文件内容的语法和.properties类似都是key=value,value格式.

INI文件中包含了四个部分:

1 [main] 主体部分.

这部分配置类对象,或设置属性等操作.

内置了根对象:securityManager,注意对象名大小写。

[main]  
securityManager.属性=值  
key=value 
securityManager.对象属性=com.bjsxt.pojo.People #后面值是字符串     peo=com.bjsxt.pojo.People
securityManager.对象属性=$peo    #出现$时才表示是引用对象  

2 [users]

定义用户,密码及用户可以具有的角色.

[users]  
用户名=密码,角色1,角色2    #角色部分可以省略. 
zhangsan=123  Sxt=123,role1,role2  

3 [roles]

定于角色具有的权限

[roles]
角色名=权限名,权限名
role1=user:insert,user:update
role2=insert,update,delete,select
role3=user:*  

4 [urls]

定义哪个控制器被哪个过滤器过滤.Shiro内置很多过滤器。此部分主要在WEB应用中使用。

下图是官网上提供的Shiro内置Filter及名称。

img

anon:不认证也可以访问。例如:/admin/**=anon /login=anon

authc:必须认证。 /**=authc —所有的资源都认证

authcBasic:没有参数时表示httpBasic认证(客户端认证方式)。

logout:退出。

noSessionCreation:新增Filter,表示没有Session创建。

perms:判断是有具有指定权限。例如:/admin/user/**=perms[“per1”,”per2”]。必须同时具有给定权限才可以访问。如果只有一个权限可以省略双引号。

port:限制端口。例如:/admin/**=port[8081]。只要请求不是8081端口就重新发送URL到8081端口。

rest:请求方式和权限的简便写法。例如:/admin/=rest[user],相当于/admin/ = perms[user:方式],方式是http请求的方式:post、get等。

roles:判断是否具有指定角色。/admin/**=roles[role1]

ssl:表示是安全的请求。协议为https

user:表示必须存在用户。

[urls] 控制器名称=过滤器名称 /login=authc /**=anon

四、Shiro环境搭建及认证实现

1 认证流程

获取主体,通过主体Subject对象的login方法进行登录

把Subject中内容传递给Security Manager

Security Manager内部组件Authenticator进行认证,

认证数据使用InI Realm,调用Ini文件中数据.

认证流程

2 名词解释

Principal: 身份。用户名,邮箱,手机等能够唯一确认身份的信息.

Credential: 凭证,代表密码等。

AuthenticationInfo:认证时存储认证信息。

五、 第一个Shiro演示

Shiro是不依赖于容器的,所以建立一个普通的Maven项目就可以。

1 添加依赖

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.4.2</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

2 新建配置文件

在resources下新建shiro.ini,文件名称任意,扩展名必须是ini。

[users] zhangsan=zs lisi=ls

3 新建测试类

新建com.bjsxt.ShiroRun。

public class ShiroRun {
    public static void main(String[] args) {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();
        //web项目时,用户名和密码是客户端表单传递过来的用户名和密码。
        AuthenticationToken token = new UsernamePasswordToken("admin", "pwd");
        try {
            //login()方法没有返回值,只能通过是否有异常判断是否登录成功。
            subject.login(token);
            System.out.println("登录成功");
        } catch (UnknownAccountException e) {
            System.out.println("账号不存在");
        } catch (IncorrectCredentialsException e) {
            System.out.println("密码错误");
        } catch (AuthenticationException e) {
             e.printStackTrace();
        }
    }
}

六、授权

授权流程

授权:代表的是判断认证成功的用户是否具有某一个权限或者角色

1 判断角色

1.1 修改配置文件

直接在密码后面添加用户包含的角色名即可。

[users]
zhangsan=zs,role1,role2
lisi=ls
1.2 添加代码

hasRole()通过返回值判断用户是否具有指定角色。

boolean hasRoleResult = SecurityUtils.getSubject().hasRole("role1");
System.out.println("result:"+hasRoleResult);

2 判断权限

2.1 修改配置文件
[users]
zhangsan=zs,role1,role2
lisi=ls
[roles]
role1=permission1,permission2
2.2 添加测试代码

通过是否出现AuthenticationException异常控制是否有指定权限。

SecurityUtils.getSubject().checkPermission(“permission”);

boolean ii = subject.isPermitted(“update”);

七、加密及凭证匹配器

在实际开发中数据库中一些敏感信息经常会被加密存储。如:用户密码等。

Shiro框架内嵌了很多加密算法。如MD5等。使用Shiro框架时可以很方便的实现加密功能。

String password = "smallming";
//md5加密
Md5Hash md5Hash = new Md5Hash(password);
System.out.println(md5Hash.toHex());
//带盐的MD5加密。盐就是在原有字符串后面拼接盐形成新的字符串,然后加密。
Md5Hash md5Hash2 = new Md5Hash(password, "bjsxt");
System.out.println(md5Hash2);
//无论是否加盐都可以很容易的被破解,可以多次迭代加密保证数据安全性。
//第三个参数表示迭代加密次数
Md5Hash md5Hash3 = new Md5Hash(password, "bjsxt", 2);
System.out.println(md5Hash3);

//使用Md5的父类也也实现
SimpleHash simpleHash = new SimpleHash("md5",password,"bjsxt",2);
System.out.println(simpleHash);

八、自定义Realm

ShiroRun 代码不需要做任何变化。

1 自定义Realm类

在项目中新建com.bjsxt.realm.MyRealm

public class MyRealm extends AuthenticatingRealm {

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证");
        //用户名
        String username = token.getPrincipal().toString();
        //密码
        String pwd = new String((char[])token.getCredentials());
        System.out.println(username+"  "+pwd);
        //先从数据库查询select * from user where username=? 查看用户名是否存在
        if(username.equals("admin")){//假设用户名为admin时能从数据库中查询出来
            //根据之前查询出来的用户信息获取密码。假设查询出来的密码是pwd
            String password= "pwd";
            //此处需要注意,第二个参数是从数据库查询出来的密码,而不是传递过来的密码。
            //第三个参数自定义。但是尽量不重复了。常直接使用用户名当做realname名字。
            AuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(),password,"realmname");
            //shiro会判断从数据库查询出来的密码和客户端传递过来的密码是否一致。
            return info;
        }
        //返回null说明用户名不存在。
        return null;
    } 
}

2 修改配置文件

在配置文件中添加下面内容

[main]
myrealm=com.bjsxt.realm.MyRealm
securityManager.realms=$myrealm

3 测试结果

在ShiroRun中修改用户名和密码,当用户名和密码为admin、pwd时可以正常登录。

此时无论shiro.ini是否配置了[users],都按照Realm中配置的逻辑进行比较用户名和密码。

九、凭证匹配器

在实际应用中数据库密码都是加密的。Shiro内置了凭证匹配器,通过简单配置就可以实现明文数据和数据库中加密数据匹配的效果。

1 修改自定义Realm

AuthenticationInfo 的构造方法由三个参数变成四个参数的。新增第三个参数表示加盐。

一般都是拿用户数据的id作为盐。

//密码必须是MD5加密后的密码
String password= "7614fd642608ca0755b78d2b2c352e19";
//假设id是从数据库中取出的id
Long id = 123L;
//第三个参数是加密的盐
AuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(), password, ByteSource.Util.bytes(id+"") ,token.getPrincipal().toString()); 

2 修改配置文件

在配置文件中配置凭证匹配器配置。

[main]  md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher 
md5CredentialsMatcher.hashIterations=2    myrealm=com.bjsxt.shiro.MyRealm  myrealm.credentialsMatcher=$md5CredentialsMatcher securityManager.realms=$myrealm

十、 Spring Boot整合Shiro实现登录认证

整体项目结构。省略数据访问层相关代码,使用固定数据进行认证。

img

1 添加依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.13.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.4.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <!--mybatis的启动器-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.1</version>
    </dependency>

    <!--数据库的驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.11</version>
    </dependency>
</dependencies> 

2 编写配置文件

新建application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/rbac1?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
shiro:
  loginUrl: /login

3 新建实体类

新建com.bjsxt.pojo.User

public class User {
    private Long id;
    private String username;
    private String password;
    // 省略getter和setter
// 省略构造方法
} 

3 编写业务层代码

新建com.bjsxt.service.UserService及实现类

public interface AdminService {
    Admin findOne(String uname);
}
@Service
public class AdminServiceImpl implements AdminService {
     @Autowired
    private AdminMapper  adminMapper;
    @Override
    public Admin findOne(String uname) {
        return adminMapper.selectOne(uname);
    }
}

4 自定义Realm

新建com.bjsxt.realm.MyRealm编写认证逻辑

@Component
public class MyRealm extends AuthorizingRealm {
     @Autowired
    private AdminService  adminService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String name = authenticationToken.getPrincipal().toString();
        Admin admin = adminService.findOne(name);
        if(admin!=null){
            AuthenticationInfo  info=new SimpleAuthenticationInfo(name,admin.getPwd(), ByteSource.Util.bytes(admin.getSalt()),"sxt");
            return   info;
        }
        return null;
    }} 

5 编写配置

新建com.bjsxt.config.ShiroConfig,编写配置

@Configuration
public class SApplicationConfig {
    @Autowired
    private   MyRealm  myRealm;

    @Bean
   public DefaultWebSecurityManager  defaultWebSecurityManager(){
       DefaultWebSecurityManager  defaultWebSecurityManager=new DefaultWebSecurityManager();
        HashedCredentialsMatcher  matcher=new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");
        matcher.setHashIterations(3);
        myRealm.setCredentialsMatcher(matcher);     
        defaultWebSecurityManager.setRealm(myRealm);
       return defaultWebSecurityManager;
   }
    @Bean
    public  shiroFilterChainDefinition() {     
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        definition.addPathDefinition("/doLogin", "anon");
        definition.addPathDefinition("/login", "anon");
        definition.addPathDefinition("/**", "authc");
        return definition;
    }
} 

6 编写控制器

新建com.bjsxt.controller.UserController

@Controller
public class AdminController {
    @RequestMapping("/{path}")
    public String  getUrl(@PathVariable String path){
        System.out.println("iii");
        return path;
    }

    @RequestMapping("/doLogin")
       public String login(String uname,String pwd){
           Subject subject = SecurityUtils.getSubject();
           try {
               subject.login(new UsernamePasswordToken(uname,pwd));
               return "main" ;
           } catch (AuthenticationException e) {
               System.out.println("失败");
               return "login";
           }
       }
}

7 编写启动类

新建com.bjsxt.ShiroApplication

@SpringBootApplication
@MapperScan("com.bjsxt.mapper")
public class ShiroApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShiroApplication.class,args);
    }
}

8 编写页面

8.1 编写登录页面

新建templates/login.html

<form action="/doLogin" method="post">
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password" /><br/>
    <input type="submit" value="登录"/>
</form> 
8.2 编写主页面

新建templates/login.html。页面中虽然编写内容

<body>
index.html
</body>

十一、凭证匹配器

1 修改ShiroConfig

@Bean
public DefaultWebSecurityManager securityManager() {
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    hashedCredentialsMatcher.setHashAlgorithmName("md5");
    hashedCredentialsMatcher.setHashIterations(2);
    myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
    manager.setRealm(myRealm);
    return manager;
} 

2 修改UserServiceImpl

模拟从数据库查询出来的密码是加密的密码。

此加密数据是”admin”加盐”123”迭代两次加密的结果。

return new User(1L,"admin","6bdae6366c1e46d541eb0ca9547d974c"); 

3 修改MyRealm

修改MyRealm中doGetAuthenticationInfo方法。把盐添加进去。

AuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(),user.getPassword(), ByteSource.Util.bytes("123"),user.getUsername()); 

十二、多个Realm认证策略

1 简介

当一个应用程序配置了两个或两个以上的Realm 时,ModularRealmAuthenticator 依靠内部的AuthenticationStrategy 组件来判定认证的成功或失败。

​ AuthenticationStrategy是一个无状态的组件,它在身份验证尝试中被询问4 次(这4 次交互所需的任何必要的状态将被作为方法参数):

  1. 在任何Realm 被调用之前被询问;

  2. 在一个单独的Realm 的getAuthenticationInfo 方法被调用之前立即被询问;

  3. 在所有的Realm 被调用后询问。

认证策略的另外一项工作就是聚合所有Realm的结果信息封装至一个AuthenticationInfo实例中,并将此信息返回,以此作为Subject的身份信息。

Shiro中定义了3种认证策略的实现:

img

ModularRealmAuthenticator内置的认证策略默认实现是AtLeastOneSuccessfulStrategy 方式,因为这种方式也是被广泛使用的一种认证策略。当然,你也可以通过配置文件定义你需要的策略

2 代码实现

@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){

    DefaultWebSecurityManager  securityManager=new DefaultWebSecurityManager();
ModularRealmAuthenticator   modularRealmAuthenticator=new ModularRealmAuthenticator();
    modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
    securityManager.setAuthenticator(modularRealmAuthenticator);
    List<Realm>  list=new ArrayList<>();
    list.add(myRealm);
    list.add(myRealm2);
    securityManager.setRealms(list);
    return  securityManager;
}  

十三、remember me实现

1 修改ShiroConfig

秘钥长度为16位,使用的时AES加密。

@Configuration
public class ShiroConfig {
    @Autowired
    private MyRealm myRealm;
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(2);
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        manager.setRealm(myRealm);
        manager.setRememberMeManager(rememberMeManager());
        return manager;
    }
    /**
     * cookie 属性设置
     */
    public SimpleCookie rememberMeCookie()
    {
        SimpleCookie cookie = new SimpleCookie("rememberMe");
//        cookie.setDomain(domain);
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(30 * 24 * 60 * 60);
        return cookie;
    }

    /**
     * 记住我
     */
    public CookieRememberMeManager rememberMeManager()
    {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        cookieRememberMeManager.setCipherKey(1234567890987654);
        return cookieRememberMeManager;
    }
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        definition.addPathDefinition("/doLogin", "anon");
        definition.addPathDefinition("/logout", "logout");
        definition.addPathDefinition("/**", "user");
        return definition;
    }

    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

img

2 修改控制器方法

修改控制doLogin方法,在UsernamePasswordToken中添加第三个参数。表示是否启用remember me功能。

此功能应该在页面中添加一个复选框,代码中直接假设用户勾选了复选框

@RequestMapping("/doLogin")
public String showLogin(User user){
    Subject subject = SecurityUtils.getSubject();
    try {
        //添加第三个参数,表示是否启用rememberme功能
        subject.login(new UsernamePasswordToken(user.getUsername(),user.getPassword(),true));
        return "redirect:/showIndex";
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "redirect:/showLogin";
}

十四、退出实现

1 修改配置类

修改ShiroConfig类,添加logout filter 对应的url。

@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
    DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
    definition.addPathDefinition("/doLogin", "anon");
    definition.addPathDefinition("/logout", "logout");
    definition.addPathDefinition("/**", "authc");
    return definition;
} 

2 修改主页面

在index.html页面中添加超链接。跳转到/logout后会由shiro内置filter进行拦截。

<body>
index.html
<a href="/logout">退出</a>
</body> 

十五、授权

授权就是判断认证用户是否具有指定角色或指定权限。

Shiro可以和JSP整合也可以和Thymeleaf整合,我们讲解SpringBoot的视图技术Thymeleaf整合Shiro。

只要是授权就执行Realm的doGetAuthorizationInfo进行判断,而触发doGetAuthorizationInfo的方式,常用的就两种:

(1)在页面中通过shiro:xxxx 属性进行判断

(2)在java代码中通过注解@RequiresXXX/config文件中配置

1、使用注解判断方法是否具有权限执行

方法:可以用控制器方法,也可以是业务方法。常在控制器方法上添加注解进行判断。

常用注解:

@RequiresAuthentication

验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。

@RequiresUser

验证用户是否被记忆,user有两种含义:

一种是成功登录的(subject.isAuthenticated() 结果为true);

另外一种是被记忆的(subject.isRemembered()结果为true)。

@RequiresGuest

验证是否是一个guest的请求,与@RequiresUser完全相反。

换言之,RequiresUser == !RequiresGuest。

此时subject.getPrincipal() 结果为null.也就是所谓的游客

@RequiresRoles

例如:@RequiresRoles(“aRoleName”);

void someMethod();

如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException

@RequiresPermissions

例如: @RequiresPermissions({“file:read”, “write:aFile.txt”} )
void someMethod();

要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException

页面显示问题处理

当用户登录成后,点击超链接“跳转到判断权限的”超链接会出现500错误页面信息。

img

新建依赖处理类

新建com.bjsxt.controller.NoPermissionException

@ControllerAdvice
public class NoPermissionException {
    @ResponseBody
    @ExceptionHandler(UnauthorizedException.class)
    public String handleShiroException(Exception ex) {
        return "无权限";
    }
    @ResponseBody
    @ExceptionHandler(AuthorizationException.class)
    public String AuthorizationException(Exception ex) {
        return "权限认证失败";
    }
} 

2、Thymeleaf整合shiro

2.1 添加依赖
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency> 
2.2 修改index.html

访问页面后会发现“有角色”不显示,“有权限”显示。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
index.html
<a href="/logout">退出</a>
<span shiro:hasRole="role3">有角色</span>
<span shiro:hasPermission="permission1">有权限</span>
</body> 
2.3 修改配置类

在配置类中com.bjsxt.config.ShiroConfig中添加。

负责解析thymeleaf中shiro:相关属性。

@Bean
public ShiroDialect shiroDialect() {
    return new ShiroDialect();
}
2.4 thymeleaf中常用属性

shiro:user=””认证通过或已记住的用户。

shiro:authenticated认证通过的用户。不包含记住的用户。

shiro:principal输出认证用户信息。shiro:principal/

shiro:hasRole="admin"判断是否具有指定角色。

shiro:hasAllRoles="role1,role2"判断指定角色用户是否都具有。

shiro:hasAnyRoles="role1,role2"只要用户具有其中一个角色就表示判断通过。

shiro:hasPermission="userInfo:add"是否具有指定权限。

shiro:lacksPermission="userInfo:del"是否不具有指定权限

shiro:hasAllPermissions="userInfo:view, userInfo:add"是否全具有指定权限。

shiro:hasAnyPermissions="userInfo:view, userInfo:del"只要有其中任何一个权限即可。

十六、EHCache

1 ehcache简介

EHCache是sourceforge的开源缓存项目,现已经具有独立官网,网址:(http://www.ehcache.org)。其本身是纯JAVA实现的,所以可以和绝大多数Java项目无缝整合,例如:Hibernate的缓存就是基于EHCache实现的。

EHCache支持内存和磁盘缓存,默认是存储在内存中的,当内存不够时允许把缓存数据同步到磁盘中,所以不需要担心内存不够问题。

EHCache支持基于Filter的Cache实现,同时也支持Gzip压缩算法提高响应速度。

ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。

2 EHCache API演示

2.1 添加依赖

从3.0版本开始groupid为org.ehcache

<dependencies>
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache</artifactId>
        <version>2.6.11</version>
    </dependency>
</dependencies> 
2.2 新建配置文件

在src/main/resources中新建ehcache.xml。

属性含义:

maxElementsInMemory:缓存中允许创建的最大对象数。

eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。

timeToIdleSeconds:缓存数据的钝化时间,取值0表示无限长。

timeToLiveSeconds:缓存数据的生存时间,取值0表示无限长。

overflowToDisk:内存不足时,是否启用磁盘缓存。

memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
   <!-- 磁盘缓存位置 -->
   <diskStore path="java.io.tmpdir/ehcache"/>
   <!-- 默认缓存 -->
   <defaultCache
           maxEntriesLocalHeap="10000"
           eternal="false"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           maxEntriesLocalDisk="10000000"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU">
       <persistence strategy="localTempSwap"/>
   </defaultCache>
   <!-- helloworld缓存 -->
   <cache name="HelloWorldCache"
          maxElementsInMemory="1000"
          eternal="false"
          timeToIdleSeconds="5"
          timeToLiveSeconds="5"
          overflowToDisk="false"
          memoryStoreEvictionPolicy="LRU"/>
</ehcache>
public static void main(String[] args) {
  /*  String property = System.getProperty("java.io.tmpdir");
    System.out.println(property);*/
    CacheManager  cacheManager=CacheManager.create("D:\\SXTCourse\\Testshiro\\uu\\src\\main\\resources\\each.xml");
    Cache cache = cacheManager.getCache("HelloWorldCache");
    Element  element=new Element("key","sxt");
    cache.put(element);
   Element element1 = cache.get("key");
    System.out.println(element1);
}

十七、Shiro和EhCache整合

Shiro支持很多第三方缓存工具。官方提供了shiro-ehcache,实现了把EHCache当做Shiro的缓存工具的解决方案。其中最好用的一个功能是就是缓存认证执行的Realm方法,减少对数据库的访问。

1 添加依赖

添加shiro-ehcache依赖。

commons-io主要是为了使用里面的工具类。本质和当前整合功能没有关系。

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.4.2</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

2 编写ehcache缓存配置

在resources下新建ehcache/ehcache-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="ehcache" updateCheck="false">

<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir"/>
<!-- 默认缓存 -->
<defaultCache
        maxEntriesLocalHeap="1000"
        eternal="false"
        timeToIdleSeconds="3600"
        timeToLiveSeconds="3600"
        overflowToDisk="false">
</defaultCache>

<!-- 登录记录缓存 锁定10分钟 -->
<cache name="loginRecordCache"
       maxEntriesLocalHeap="2000"
       eternal="false"
       timeToIdleSeconds="600"
       timeToLiveSeconds="0"
       overflowToDisk="false"
       statistics="true">
</cache>

</ehcache>

3 修改配置文件

@Bean
public DefaultWebSecurityManager securityManager() {
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    hashedCredentialsMatcher.setHashAlgorithmName("md5");
    hashedCredentialsMatcher.setHashIterations(2);
    myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
    manager.setRealm(myRealm);
    manager.setRememberMeManager(rememberMeManager());
    manager.setCacheManager(getEhCacheManager());
    return manager;
}

public EhCacheManager ehCacheManager(){
    EhCacheManager ehCacheManager = new EhCacheManager();
    InputStream is = null;
    try {
        is = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");
    } catch (IOException e) {
        e.printStackTrace();
    }
    net.sf.ehcache.CacheManager cacheManager = new net.sf.ehcache.CacheManager(is);
    ehCacheManager.setCacheManager(cacheManager);
    return ehCacheManager;
}

十八、Shiro中sessionManager

1、sessionManager的简介

在shiro里所有的用户的会话信息都会由Shiro来进行控制,shiro提供的会话可以用于JavaSE/JavaEE环境,不依赖 于任何底层容器,可以独立使用,是完整的会话模块。通过Shiro的会话管理器(SessionManager)进行统一的 会话管理

SessionManager(会话管理器):管理所有Subject的session包括创建、维护、删除、失效、验证等工作。

SessionManager是顶层组件,由SecurityManager管理

shiro提供了三个默认实现:

  1. DefaultSessionManager:用于JavaSE环境

  2. ServletContainerSessionManager:用于Web环境,直接使用servlet容器的会话。

  3. DefaultWebSessionManager:用于web环境,自己维护会话(自己维护着会话,直接废弃了Servlet容器的会话管理)

//登录成功后,打印所有session内容 
@RequestMapping(value="/show")
public String show(HttpSession session) { 
       // 获取session中所有的键值 
       Enumeration<?> enumeration = session.getAttributeNames(); 
       // 遍历enumeration中的 
       while (enumeration.hasMoreElements()) { 
           // 获取session键值 
           String name = enumeration.nextElement().toString(); 
           // 根据键值取session中的值 
           Object value = session.getAttribute(name); 
           // 打印结果 
           System.out.println("---" + name + "---" + value + "---"); 
 } 
return "session"; 

2理解示意图

img

3 获得session 方式

Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("key","value");

结论:

在shiro使用过程中,我们是可以调到httpSession对象的,调用方式即为shiro获取session的方法。

(1)controller中的request,在shiro过滤器中的doFilterInternal方法,将被包装为ShiroHttpServletRequest 。

(2)在controller中,通过 request.getSession(_) 获取会话 session ,该session到底来源servletRequest 还是由shiro管理并管理创建的会话,主要由 安全管理器 SecurityManager 和 SessionManager 会话管理器决定。

(3)不管是通过 request.getSession或者subject.getSession获取到session,操作session,两者都是等价的。在使用默认session管理器的情况下,操作session都是等价于操作HttpSession。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Redamancy_WC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值