赞
踩
shiro是一种易于使用的Java安全框架,shiro可以用来完成:认证、授权、加密、会话管理、与Web集成、缓存等。
(1)Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
(2)Authorization:授权,验证已认证的用户是否拥有某个权限,能否进行相关的操作;
(3)Seesion Manager:会话管理,用户登录之后就是一次会话,没有退出之前,它的所有信息都保存在会话中;会话可以是普通的JavaSE环境,也可以是WEB环境;
(4)Cryptography:加密,保护数据的安全性,如密码加密存储到数据库
(5)Web Support:Web支持,可以非常容易的集成到Web环境
(6)Caching : 缓存,可以缓存用户登录后的信息、角色以及权限
(7)Concurrency:Shiro支持多线程应用的并发验证
(8)Testing:提供测试支持
(9)Run As:允许用户假装为另外的用户的身份进行访问
(10)Remember Me:记住我,下次登录就不需要进行验证
从外部来看,Shiro最主要的组件是Subject、SecurityManager以及Realm
(1)Subject:Shiro对外的接口,使用Shiro,就得拥有Subject对象,我们对Shiro的操作都是从Subject对象开始,subject对象可以理解为操作Shiro的媒介;
(2)SecurityManager:安全管理器,与安全相关的交互都会与SecurityManager交互,它同时也管理着所有的Subject,相当于Spring MVC中的DispatchServlet的角色
(3)Realm:Shiro从Realm获取安全数据,SecurityManager要想验证用户的身份信息,就需要从Realm中获取用户的信息,或者用户所拥有的权限/角色,SecurityManager才能判断该用户是否能够进行下一步的操作。
从Shiro内部来看:
(1)Subject:任何可以与应用交互的“用户”
(2)SecurityManager:相当于Shiro的心脏,所有的具体交互都要通过SecurityManager来进行管理,它管理着所有 Subject、且负责认证、授权、会话以及缓存的管理。
(3)Authentication:负责Subject认证,可以自定义实现,可以使用认证策略,及什么情况下该用户算通过认证
(4)Authorizer:授权器,控制着用户能够访问那些应用,是用户鉴权的核心
(5)Realm:可以有1个或者多个Realm,可以认为是安全的实体数据源,用与获取安全实体的,可以是JDBC实现,也可以是内存实现,由用户提供;所以一般在应用中都需要实现自己的Realm;
(6)SessionManager:管理Session生命周期的组件,
(7)CacheManager:缓存
(8)Cryptography:密码加密模块,对数据库中的用户密码进行加密处理
(1)身份验证:验证你是否是我的用户、一般通过用户名/密码来证明身份。
(2)在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,使应用能够验证用户身份:
principals:身份,能够唯一标识主体的属性,比如手机号、邮箱、用户名等
credentials:证明/凭证,只有主体知道,比如密码
最常见的principals和credentials组合就是用户名/密码。
(1)得到用户的登录信息,比如用户名、密码;
(2)调用调用Subject.login进行登录,如果失败则得到相应的AuthenticationException异常信息,否则登录成功
(3)创建自定义的Realm类,继承shiro中的AuthenticatingRealm类,实现doGetAuthenticationInfo()方法
身份认证的基本流程:
(1)首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager
(2)SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份
验证;
(3)Authenticator 才是真正的身份验证者,Shiro API 中核心的身份 认证入口点,此
处可以自定义插入自己的实现;
(4)Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份
验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm
身份验证;
(5) Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如
果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序
及策略进行访问。
Shiro 获取权限相关信息可以通过数据库获取,也可以通过 ini 配置文件获取,我们通过ini文件获取。
创建简单的maven工程,在resource下创建shiro.ini文件,在该文件中定义登录用户的用户名和密码
注意shiro.ini的配置文件中,【user】更改为【users】,下图没有更改,后续测试会报错。
导入依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
创建测试类:
import com.sun.org.omg.CORBA.InitializerSeqHelper; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; public class ShiroLoginText { public static void main(String[] args) { //1.初始化获取SecurityManager IniSecurityManagerFactory factory=new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager instance = factory.getInstance(); SecurityUtils.setSecurityManager(instance); //2.获取Subject对象 Subject subject = SecurityUtils.getSubject(); //3.创建token对象,web应用从前端传递用户名和密码 AuthenticationToken token=new UsernamePasswordToken("zhangsan","zss"); //4.完成登录 try { subject.login(token); System.out.println("登录成功" ); }catch (UnknownAccountException e){ e.printStackTrace(); System.out.println("用户不存在"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误"); } } }
输入正确的用户名、密码:
输入错误的用户名:
输入错误的密码:
判断用户是否拥有角色,使用subject.hasRole()方法完成、或者使用注解@RequiresRoles(“角色名称”)、或者在JSP/GSP标签中,使用<shiro:hasRole name=“角色名称进行判断”>
在shiro.ini文件中添加角色和角色对应的权限信息
登录成功之后,判断该用户是否用户“admin”这个角色,同时判断是否拥有user:insert插入数据的权限
校验校色使用:subject.hasRoles()
校验权限使用:subject.isPermitted()
import com.sun.org.omg.CORBA.InitializerSeqHelper; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; public class ShiroLoginText { public static void main(String[] args) { //1.初始化获取SecurityManager IniSecurityManagerFactory factory=new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager instance = factory.getInstance(); SecurityUtils.setSecurityManager(instance); //2.获取Subject对象 Subject subject = SecurityUtils.getSubject(); //3.创建token对象,web应用从前端传递用户名和密码 AuthenticationToken token=new UsernamePasswordToken("zhangsan","zss"); //4.完成登录 try { subject.login(token); System.out.println("登录成功" ); //验证是否拥有 角色admin boolean admin = subject.hasRole("admin"); System.out.println("是否拥有admin角色 " + admin); //拥有该角色,验证是否拥有 User:insert权限 boolean permitted = subject.isPermitted("user:insert"); System.out.println("是否拥有该权限 = " + permitted); }catch (UnknownAccountException e){ e.printStackTrace(); System.out.println("用户不存在"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密码错误"); } } }
运行结果:
看到他没有这个角色和权限,检查一下是不是我们的参数写错了:
果然写错了,我们登录的账户是zhangsan,结果角色给lisi了,肯定就没有这个权限和角色了,在实际开发中,这种情况就可以拦截这个请求了,或者直接return一个空值结束请求。
我们继续把admin角色给张三
再次运行查看结果:
这次就拥有这个角色和权限啦。
我们在数据库中保存账户名和密码时,一般都需要对账户的密码进行加密,不可能保存明文密码吧,使用shiro可以很简单的对数据进行加密
例如:
import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.crypto.hash.SimpleHash; public class shiroMD5 { public static void main(String[] args) { String password="12345"; //比如这是密码 //使用MD5加密 Md5Hash md5Hash=new Md5Hash(password); System.out.println("MD5加密 = " + md5Hash.toHex()); //带盐的MD5加密,就是给你的密码加点料,在你的密码后面拼接字符串,在进行M加密 Md5Hash md5Hash1=new Md5Hash(password,"jialiao");//很明显,多的这个字符串参数就是加的料 System.out.println("带盐(加料)的加密= " + md5Hash1.toHex()); //你以为这就完了?加一次还可能被别人破解,那我多加几次,多迭代几次,多加点料 Md5Hash md5Hash2=new Md5Hash(password,"jialiao",3); System.out.println("迭代三次盐(加了三次料)的加密 = " + md5Hash2.toHex()); //还可以使用父类加密,Md5Hash它的父类是SimpleHash //使用父类需要额外指定加密方式 SimpleHash simpleHash=new SimpleHash("MD5",password,"jialiao",3); System.out.println("使用父类,指定加密方式为MD5,迭代三次盐(加了三次料)的加密 = "+simpleHash.toHex()); } }
运行结果:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.9.0</version> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency
在yml文件配置进行配置
mybatis-plus: mapper-locations: classpath:mapper/*.xml # MySQL数据源 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/shirodb?characterEncoding=utf-8&useSSL=false username: ***** password: ***** type: com.zaxxer.hikari.HikariDataSource #日期格式 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 #登录接口 shiro: loginUrl: /myController/login
创建登录表
CREATE DATABASE IF NOT EXISTS `shirodb` CHARACTER SET utf8mb4;
USE `shirodb`;
CREATE TABLE `user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` VARCHAR(30) DEFAULT NULL COMMENT '用户名',
`pwd` VARCHAR(50) DEFAULT NULL COMMENT '密码',
`rid` BIGINT(20) DEFAULT NULL COMMENT '角色编号',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户表';
创建数据库表对应的实体类User(或者使用MybatisX逆向生成实体类、mapper、service、serviceimpl)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private String pwd;
private Integer rid;
}
创建mapper
package com.lqkj.dong.springboot_shiro.mapper;
import com.lqkj.dong.springboot_shiro.pojo.User;
public class UserMapper extends BaseMapper<User>{
//自定义查询数据库的方法
}
创建service接口及实现类
package com.dong.shirotext.service;
import com.dong.shirotext.pojo.User;
public interface UserService {
//定义根据用户名从数据库中查询用户信息的方法
public User getUSerByName(String name);
}
package com.dong.shirotext.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.dong.shirotext.mapper.UserMapper; import com.dong.shirotext.pojo.User; import com.dong.shirotext.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceimpl implements UserService { @Autowired private UserMapper userMapper;//注入mapper /** * 根据用户名,从数据库中查询用户记录 * @param name * @return */ @Override public User getUSerByName(String name) { User user = userMapper.selectOne(new QueryWrapper<User>().eq("name", name)); return user; } }
项目架构大概是这个样子,大家随意即可
当我们使用shiro框架时,我们需要shiro去查询我的数据库,走我们自己的登录验证的逻辑,这时候就需要我们自定义realm,创建类去继承AuthorizingRealm类,重写里面的两个方法(重载),即可完成自定义的授权和登录认证的方法。详见代码:
package com.dong.shirotext.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class MyRealm extends AuthorizingRealm { //用于鉴权 /** * 当前登录用户,我们可以从数据库中查询到该用户的权限,并通过该方法,将我们查询到的 该用户拥有的角色,保存在shiro框架中,后面我们使用shiro框架去核对用户权限时,所比较的 就是我们在这里查询、并且保存到的权限 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } /** * 这个方法是用于登录认证 我们要自定义自己的登录逻辑,就需要在该方法中编写自己的登录逻辑 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { return null; } }
接下来,我们自定义自己的登录逻辑,让它从我们的数据库中查询用户的信息进行比对。
package com.dong.shirotext.realm; import com.dong.shirotext.pojo.User; import com.dong.shirotext.service.UserService; 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.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; //用于鉴权 /** * 当前登录用户,我们可以从数据库中查询到该用户的权限,并通过该方法,将我们查询到的 * 该用户拥有的角色,保存在shiro框架中,后面我们使用shiro框架去核对用户权限时,所比较的 * 就是我们在这里查询、并且保存到的权限 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } /** * 这个方法是用于登录认证 * 我们要自定义自己的登录逻辑,就需要在该方法中编写自己的登录逻辑 * * @param * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //首先,从token中获取登录时保存的用户信息,注意,该方法是登录时shiro会调用的方法 //也就是说我们在登录时,会将名称和密码封装成token,所以这里不要疑惑为什么token中可以 //获取到用户的信息,后续我们编写登录时,你就会恍然大悟 String name = token.getPrincipal().toString();//得到的就是用户名称 System.out.println("name = " + name);//可以打印出来看看 //从数据库中查询用户信息,根据name查询 User user = userService.getUSerByName(name); //查到用户信息之后,将登录时的密码,和现在从数据库中查到的密码进行比较 //我们将相关的数据封装到AuthenticationInfo中,知道密码的盐,用户名、密码, //该对象会帮我们进行校验,并且将校验结果返回给shiro框架 if (user != null) { //创建AuthenticationInfo对象,在构造器中传递相关参数 AuthenticationInfo info = new SimpleAuthenticationInfo( token.getPrincipal(), user.getPwd(),//用户密码 ByteSource.Util.bytes("salt"),//密码使用MD5加密时指定的盐 token.getPrincipal().toString() ); return info; } return null; } }
接下来,我们去数据库中准备两条数据,注意密码要使用MD5加密,盐使用“salt”,并且需要迭代三次的密码
(12345)
接下来创建Controller登录接口:
package com.dong.shirotext.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/myController") public class LoginController { @GetMapping("/login")//使用get方式,方便参数传递 public String login( String name, String password){ //1.获取subject对象 Subject subject= SecurityUtils.getSubject(); //通过name,password封装为token对象中 //现在能理解我们自定义的登录验证方法中,为什么能够从token中获取用户信息了吧 AuthenticationToken token=new UsernamePasswordToken(name,password); //然后调用subject.login方法登录 try { //此时就会去执行我们自定义的登录验证的方法,同时将token作为参数传递过去了 subject.login(token); return "登录成功"; }catch (AuthenticationException e){ e.printStackTrace(); return "登录失败!"; } } }
ok,现在可以试一下我们能否登录成功了,在看一下我目前的目录结构
启动我们的项目(记得在启动上方,添加MapperScan注解,扫描mapper所在的目录)
启动报错
Please create bean of type 'Realm' or add a shiro.ini in the root classpath (src/main/resources/shiro.ini) or in the META-INF folder (src/main/resources/META-INF/shiro.ini).
因为我们目前还没有对shiro进行配置,所以shiro默认去寻找配置文件,发现找不到配置文件,没有配置文件就没有Realm对象,没有Realm对象你让我怎么干活,直接罢工了,所以解下来我们需要对shiro进行配置,既然使用了Spring Boot框架,我们就不需要配置文件了, 直接创建一个配置类即可
package com.dong.shirotext.config; import com.dong.shirotext.realm.MyRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.management.MXBean; @Configuration public class ShiroConfig { @Autowired private MyRealm realm; //注入我们自定义的Realm对象 @Bean public DefaultWebSecurityManager defaultWebSecurityManager(){ DefaultWebSecurityManager defaultWebSecurityManager= new DefaultWebSecurityManager(); //创建密码加密对象 HashedCredentialsMatcher matcher=new HashedCredentialsMatcher(); //设置加密对象的属性 matcher.setHashAlgorithmName("md5"); matcher.setHashIterations(3); //将加密对象 存储到Realm对象中 realm.setCredentialsMatcher(matcher); //将Realm对象 存入 defaultWebSecurityManager对象中 defaultWebSecurityManager.setRealm(realm); //返回 return defaultWebSecurityManager; } //配置shiro的拦截范围,我们的登录接口肯定是需要放行的 @Bean public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition definition =new DefaultShiroFilterChainDefinition(); //设置不认证可以访问的资源 definition.addPathDefinition("/myController/userlogin","anon"); definition.addPathDefinition("/myController/login","anon"); //配置需要拦截的请求范围 definition.addPathDefinition("/**","authc"); return definition; } }
ok,对shiro进行完配置,同时放行了我们的登录接口,这次启动应该不会报错了吧,启动项目
启动成功,测试登录接口
http://localhost:8080/myController/login?name=zs&password=12345
可以看到能够登录成功,并且是通过数据库的方式登录。
接下来,我们增加一个前端页面,在前端页面中进行登录操作,在resources文件下创建templates文件,在templates文件下创建login.html和登录成功之后的跳转页面main.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Shiro 登录认证</h1> <br> <form action="/myController/userlogin"> <div>用户名:<input type="text" name="name" value=""></div> <div>密码:<input type="password" name="password" value=""></div> <div><input type="submit" value="登录"></div> </form> </body> </html> main.html <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Shiro 登录认证后主页面</h1> <br> 登录用户为:<span th:text="${session.user}"></span> </body> </html>
增加一个跳转登录页面的controller方法:
@GetMapping("/login")
public String toLogin(){
return "login";
}
改造登录接口
package com.dong.shirotext.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.http.HttpRequest; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @Controller @RequestMapping("/myController") public class LoginController { @GetMapping("/userlogin")//使用get方式,方便参数传递 public String login(String name, String password, HttpSession session){ //1.获取subject对象 Subject subject= SecurityUtils.getSubject(); //通过name,password封装为token对象中 //现在能理解我们自定义的登录验证方法中,为什么能够从token中获取用户信息了吧 AuthenticationToken token=new UsernamePasswordToken(name,password); //然后调用subject.login方法登录 try { //此时就会去执行我们自定义的登录验证的方法,同时将token作为参数传递过去了 subject.login(token); //将用户信息存到session中 session.setAttribute("user",token.getPrincipal().toString()); //跳转页面 return "main"; }catch (AuthenticationException e){ e.printStackTrace(); return "登录失败!"; } } @GetMapping("/login") public String toLogin(){ return "login"; } }
再次启动项目,访问http://localhost:8080/myController/login,页面如下:
输入用户名称,密码点击登录,能够登录成功,并且展示存在session中的用户
到这里我们发现了,其实一种Realm就代表了一种认证规则,而Shiro可以同时创建多个Realm,每一个Realm代表不同的认证规则,比用账号密码、手机号+验证码等等,如果存在多个Realm,Shiro又该怎么进行验证?
Shiro 的 ModularRealmAuthenticator 会使用内部的 AuthenticationStrategy 组件判断认证是成功还是失败。
AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这4 次交互所需的任何必要的状态将被作为方法参数):
认证策略的另外一项工作就是聚合所有 Realm 的结果信息封装至一个AuthenticationInfo 实例中,并将此信息返回,以此作为 Subject 的身份信息。
Shiro中定义了三种认证策略的实现
AuthenticationStrategy class | 描述 |
---|---|
AtLeastOneSuccessfulStrategy | 只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功 |
FirstSuccessfulStrategy | 第一个Realm验证成功,就认为整体是成功的,后面的Realm会被忽略 |
AllSuccessfulStrategy | 所有的Realm都验证成功,认证才视为成功 |
ModularRealmAuthenticator 默认的认证策略是第一种AtLeastOneSuccessfulStrategy
多个Realm代码实现,主要在配置中配置modularRealmAuthenticator,指定认证策略,然后封装Realm集合
//多个Realm @Bean public DefaultWebSecurityManager defaultWebSecurityManager(){ DefaultWebSecurityManager manager=new DefaultWebSecurityManager(); //创建认证对象,并指定认证策略 ModularRealmAuthenticator modularRealmAuthenticator=new ModularRealmAuthenticator(); //这里指定了第三种认证策略,及所有的Realm都要认证成功 modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy()); //封装Realm集合 List<Realm> list=new ArrayList<>(); list.add(realm);//有几个就添加几个 //将Realm集合存入DefaultWebSecurityManager中返回 manager.setRealms(list); return manager; } }
记住我,这个功能相信大家都有使用过,我们直接开始正文,首先,在shiro配置类中进行配置
package com.dong.shirotext.config; import com.dong.shirotext.realm.MyRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.authc.pam.AllSuccessfulStrategy; import org.apache.shiro.authc.pam.ModularRealmAuthenticator; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.management.MXBean; import java.util.ArrayList; import java.util.List; @Configuration public class ShiroConfig { @Autowired private MyRealm realm; //注入我们自定义的Realm对象 @Bean public DefaultWebSecurityManager defaultWebSecurityManager() { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //创建密码加密对象 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //设置加密对象的属性 matcher.setHashAlgorithmName("md5"); matcher.setHashIterations(3); //将加密对象 存储到Realm对象中 realm.setCredentialsMatcher(matcher); //将Realm对象 存入 defaultWebSecurityManager对象中 defaultWebSecurityManager.setRealm(realm); //设置remember Me defaultWebSecurityManager.setRememberMeManager(rememberMeManager()); //返回 return defaultWebSecurityManager; } //cokie属性设置 public SimpleCookie remeberMeCookie(){ SimpleCookie cookie=new SimpleCookie("remeberMe"); //设置跨域 cookie.setPath("/"); cookie.setHttpOnly(true); cookie.setMaxAge(30*24*60*60); return cookie; } //创建Shiro的cookie管理对象 public CookieRememberMeManager rememberMeManager(){ CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager(); cookieRememberMeManager.setCookie(remeberMeCookie()); cookieRememberMeManager.setCipherKey("1234567890987654".getBytes()); return cookieRememberMeManager; } //配置shiro的拦截范围,我们的登录接口肯定是需要放行的 @Bean public DefaultShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition(); //设置不认证可以访问的资源 definition.addPathDefinition("/myController/userlogin", "anon"); definition.addPathDefinition("/myController/logon", "anon"); //配置需要拦截的请求范围 definition.addPathDefinition("/**", "authc"); //添加存在用户的过滤器(remeberMe) definition.addPathDefinition("/**","user"); return definition; } }
修改controller
@GetMapping("/userlogin")//使用get方式,方便参数传递 public String login(String name, String password, @RequestParam(defaultValue = "false") boolean rememberMe, HttpSession session){ //1.获取subject对象 Subject subject= SecurityUtils.getSubject(); //通过name,password封装为token对象中 //现在能理解我们自定义的登录验证方法中,为什么能够从token中获取用户信息了吧 AuthenticationToken token=new UsernamePasswordToken(name,password,rememberMe); //然后调用subject.login方法登录 try { //此时就会去执行我们自定义的登录验证的方法,同时将token作为参数传递过去了 subject.login(token); //将用户信息存到session中 session.setAttribute("user",token.getPrincipal().toString()); //跳转页面 return "main"; }catch (AuthenticationException e){ e.printStackTrace(); return "登录失败!"; } //登录认证验证 rememberMe @GetMapping("userLoginRm") public String userLogin(HttpSession session) { session.setAttribute("user","rememberMe"); return "main"; } }
启动项目,直接去访问: http://localhost:8080/myController/userLoginRm 会跳转到登录页面让我们登录,
选择记住我,然后登录,重启浏览器直接访问http://localhost:8080/myController/userLoginRm ,就不会自动跳转到登录页面。,而是直接访问成功。
退出登录很简单,我们直接把退出登录的接口配置在Shiro配置类中即可
//退出登录
definition.addPathDefinition("/logout","logout");
用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断。
这个工具就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种方式:
通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加在业务方法上,一般加在控制器方法上。常用注解如下:
新加接口,该接口添加注解RequiresRoles,表示当前登录的用户需要拥有admin角色才能访问该方法。
@GetMapping("/userLoginRoles")
@ResponseBody
@RequiresRoles("admin")
public String Role(){
return "恭喜你通过认证!";
}
启动项目进行测试,直接报错,报错信息就是当前的用户不具有admin这个角色。
赋予它admin的角色
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//我们直接当前的用户赋予它admin的角色,正常来说角色是需要根据用户名称去数据库中查询的
// String name = principalCollection.getPrimaryPrincipal().toString();
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
authorizationInfo.addRole("admin");
return authorizationInfo;
}
这次就不会再报错
无论是角色还是权限,都是在我们自定义的Realm中的 doGetAuthorizationInfo(PrincipalCollection principalCollection) 方法中进行验证,如果我们的用户存在角色和权限,我们需要再该方法中,将角色和权限从数据库中查询出来,保存在AuthenticationInfo对象中返回。
添加依赖
<!--配置 Thymeleaf 与 Shrio 的整合依赖-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
配置类中添加新配置
//解析thymeleaf中的shiro:相关属性
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
Thymeleaf 中常用的 shiro:属性
--guest 标签 <shiro:guest> </shiro:guest> 用户没有身份验证时显示相应信息,即游客访问信息。 --user 标签 <shiro:user> </shiro:user> 用户已经身份验证/记住我登录后显示相应的信息。 --authenticated 标签 <shiro:authenticated> </shiro:authenticated> 用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的。 --notAuthenticated 标签 <shiro:notAuthenticated> </shiro:notAuthenticated> 用户已经身份验证通过,即没有调用 Subject.login 进行登录,包括记住我自动登录的 也属于未进行身份验证。 --principal 标签 <shiro: principal/> <shiro:principal property="username"/> 相当于((User)Subject.getPrincipals()).getUsername()。 -lacksPermission 标签 <shiro:lacksPermission name="org:create"> </shiro:lacksPermission> 如果当前 Subject 没有权限将显示 body 体内容。 --hasRole 标签 <shiro:hasRole name="admin"> </shiro:hasRole> 如果当前 Subject 有角色将显示 body 体内容。 --hasAnyRoles 标签 <shiro:hasAnyRoles name="admin,user"> </shiro:hasAnyRoles> 如果当前 Subject 有任意一个角色(或的关系)将显示 body 体内容。 --lacksRole 标签 <shiro:lacksRole name="abc"> </shiro:lacksRole> 如果当前 Subject 没有角色将显示 body 体内容。 --hasPermission 标签 <shiro:hasPermission name="user:create"> </shiro:hasPermission> 如果当前 Subject 有权限将显示 body 体内容
导入依赖
<!--Shiro 整合 EhCache-->
<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>
<?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"/> <!-- defaultCache:默认缓存策略,当 ehcache 找不到定义的缓存时,则使用这个 缓存策略。只能定义一个。 --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout 将不起作用。 overflowToDisk:是否保存到磁盘,当系统宕机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间 无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间 介于创建时间和失效时间之间。仅当 eternal=false 对象不是永久有效时使用,默认 是 0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置 DiskStore(磁盘缓存)的缓存区大 小。默认是 30MB。每个 Cache 都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是 120 秒。 memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时, Ehcache 将会根据指定的策略去清理内存。默认策略是 LRU(最近最少使用)。你可以 设置为 FIFO(先进先出)或是 LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策 略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是 讲一直以来最少被使用的。如上面所讲,缓存的元素有一个 hit 属性,hit 值最小的将 会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当 缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳 离当前时间最远的元素将被清出缓存。 --> </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> <!--登录认证信息缓存:缓存用户角色权限--> <cache name="loginRolePsCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"/> </ehcache>
修改shiro配置类
package com.dong.shirotext.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import com.dong.shirotext.realm.MyRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.authc.pam.AllSuccessfulStrategy; import org.apache.shiro.authc.pam.ModularRealmAuthenticator; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.io.ResourceUtils; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.management.MXBean; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @Configuration public class ShiroConfig { @Autowired private MyRealm realm; //注入我们自定义的Realm对象 @Bean public DefaultWebSecurityManager defaultWebSecurityManager() { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //创建密码加密对象 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //设置加密对象的属性 matcher.setHashAlgorithmName("md5"); matcher.setHashIterations(3); //将加密对象 存储到Realm对象中 realm.setCredentialsMatcher(matcher); //将Realm对象 存入 defaultWebSecurityManager对象中 defaultWebSecurityManager.setRealm(realm); //设置remember Me defaultWebSecurityManager.setRememberMeManager(rememberMeManager()); //设置缓存管理器 defaultWebSecurityManager.setCacheManager(getCacheManager()); //返回 return defaultWebSecurityManager; } //获取缓存管理器 private CacheManager getCacheManager() { 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; } //cokie属性设置 public SimpleCookie remeberMeCookie(){ SimpleCookie cookie=new SimpleCookie("rememberMe"); //设置跨域 cookie.setPath("/"); cookie.setHttpOnly(true); cookie.setMaxAge(30*24*60*60); return cookie; } //创建Shito的cookie管理对象 public CookieRememberMeManager rememberMeManager(){ CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager(); cookieRememberMeManager.setCookie(remeberMeCookie()); cookieRememberMeManager.setCipherKey("1234567890987654".getBytes()); return cookieRememberMeManager; } //配置shiro的拦截范围,我们的登录接口肯定是需要放行的 @Bean public DefaultShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition(); //设置不认证可以访问的资源 definition.addPathDefinition("/myController/userlogin", "anon"); definition.addPathDefinition("/myController/logon", "anon"); //配置需要拦截的请求范围 definition.addPathDefinition("/**", "authc"); //添加存在用户的过滤器(remeberMe) definition.addPathDefinition("/**","user"); //退出登录 definition.addPathDefinition("/logout","logout"); return definition; } //解析thymeleaf中的shiro:相关属性 @Bean public ShiroDialect shiroDialect(){ return new ShiroDialect(); } }
会话管理器,负责创建和管理用户的会话(Session)生命周期,它能够在任何环境中在本地管理用户会话,即使没有Web/Servlet/EJB容器,也一样可以保存会话。默认情况下,Shiro会检测当前环境中现有的会话机制(比如Servlet容器)进行适配,如果没有(比如独立应用程序或者非Web环境),它将会使用内置的企业会话管理器来提供相应的会话管理服务,其中还涉及一个名为SessionDAO的对象。SessionDAO负责Session的持久化操作(CRUD),允许Session数据写入到后端持久化数据库。
SessionManager由SecurityManager管理。Shiro提供了三种实现:
获取Session方式
(1)实现
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute(“key”,”value”)
(2)说明
Controller 中的 request,在 shiro 过滤器中的 doFilerInternal 方法,被包装成ShiroHttpServletRequest。
SecurityManager 和 SessionManager 会话管理器决定 session 来源于 ServletRequest还是由 Shiro 管理的会话。
无论是通过 request.getSession 或 subject.getSession 获取到 session,操作session,两者都是等价的。
使用Shiro,导入Shiro依赖,自定义自己的登录逻辑和鉴权,继承AuthorizingRealm类,重写其中两个方法,一个用于鉴权,一个用于登录认证。
然后是shiro配置类的编写,自定义的操作需要再配置类中进行配置,DefaultWebSecurityManager。
其次,在Spring Boot中,可以使用注解@RequiresRole(是否拥有角色)、@RequiresPermissions(是否拥有权限)、@RequiresGuest(是否游客)、@RequiresUser(是否被记忆)、@RequiresAuthentication(是否登录)。
最后是缓存EhCache和Session的使用。demo案例在代码包中,大家可以自己下载来看看。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。