1、SpringBoot整合shiro
1.1、基本的整合
1.1.1、导包
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <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>
</dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--导入的是spring对shiro的支持包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
1.1.2、配置模板引擎的配置(application.properties)
#配置模板引擎
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
1.1.3、编写login.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="userName"/><span th:text="${userNameError}"></span><br>
密码:<input type="password" name="password"><span th:text="${passwordError}"></span><br>
<input type="submit" value="登陆">
</form>
</body>
</html>
1.1.4、编写index.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is index Page And you?
</body>
</html>
1.1.5、编写ShiroConfig的配置文件
@SpringBootConfiguration
public class ShiroConfig {
private Logger logger= LoggerFactory.getLogger(ShiroConfig.class);
//配置咋们的过滤器拦截请求
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
logger.info("shiro的过滤器执行了.....");
//配置如果认证没有通过的话 那么跳转的这个页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
Map<String,String> maps=new LinkedHashMap<>();
//第一个参数是路劲 第二个参数是过滤器的名字
/**
* 常见的过滤器的名字以及含义
* /**:当前以及目录和后面的所有子目录全部都匹配上
* 127.0.0.1:8080/bobo 127.0.0.1:8080/bobo/xiaobobo
* /* :这个相当于只是匹配当前这一级的节点 127.0.0.1:8080/bobo
* 127.0.0.1:8080/bobo/xiaobobo
* authc:认证的过滤器
* anon: 表示的是/toIndex这个请求 不认证就可以访问 (匿名访问)
* maps.put("/toIndex","anon");
* logout:登陆过滤器
* maps.put("/logout","logout")
* perms:权限控制的
* roles:具有某一个角色才能访问
*
* 注意事项: /** 这个配置一定是最后 一个
*
*/
//maps.put("/toIndex","anon"); //表示的是不需要认证就可以访问
maps.put("/login","anon"); //访问请求的地址
maps.put("/**","authc"); //所有的请求都必须在用户认证之后才能访问
shiroFilterFactoryBean.setFilterChainDefinitionMap(maps);
//设置这个安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
//配置的是安全管理器
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置校验的realm对象
logger.info("securityManager的过滤器执行了.....");
securityManager.setRealm(myRealm);
return securityManager;
}
//配置的是realm
@Bean
public MyRealm myRealm(){
MyRealm myRealm = new MyRealm();
logger.info("myRealm的过滤器执行了.....");
return myRealm;
}
}
1.1.6、controller的编写
@Controller
public class UserController {
private Logger logger= LoggerFactory.getLogger(UserController.class);
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
/**
* 跳转到Index页面的方法
* @return
*/
@RequestMapping("toIndex")
public String toIndex(){
return "index";
}
/**
* 登陆的方法
* @return
*/
@RequestMapping(value = "login")
public String login(User user, Model model){
//封装成请求对象
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
//获取登陆的主体对象
Subject subject = SecurityUtils.getSubject();
//登陆
try{
subject.login(token);
}catch (UnknownAccountException err){ //用户名不对
logger.error("用户名不对");
model.addAttribute("userNameError","用户名不对");
return "login";
}catch (IncorrectCredentialsException err){ //说明是密码不对
logger.error("密码不对");
model.addAttribute("passwordError","密码不对");
return "login";
}catch (Exception err){
logger.error("其他问题造成登陆失败:"+err.fillInStackTrace());
model.addAttribute("otherError","其他问题造成登陆失败");
return "login";
}
return "index";
}
}
1.1.7、Realm的编写
public class MyRealm extends AuthorizingRealm {
@Override
public String getName() {
return "MyRealm";
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//第一步:获取用户名
String userName= (String) authenticationToken.getPrincipal();
//通过用户名查询用户对象
if(!(userName.equals("xiaobobo"))){
return null;
}
//假设查询出来了
User user = new User(1, "xiaobobo", "123");
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());
return simpleAuthenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
1.1.8、理解图
1.2、退出的问题
1.2.1、在页面上编写退出按钮
<a href="/logout">退出</a>
1.2.2、在全局的过滤器中进行登出过滤器的配置
maps.put("/logout","logout"); //这个就是退出功能
1.3、密码散列的问题
1.3.1、在配置文件申明凭证匹配器
//配置密码散列的问题
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//这里就是设置散列的方法的地方
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置的是散列的次数
hashedCredentialsMatcher.setHashIterations(1);
return hashedCredentialsMatcher;
}
1.3.2、改造realm让他支持盐
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//第一步:获取用户名
String userName= (String) authenticationToken.getPrincipal();
//通过用户名查询用户对象
if(!(userName.equals("xiaobobo"))){
return null;
}
//假设查询出来了
User user = new User(1, "xiaobobo", "e99a18c428cb38d5f260853678922e03");
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
user.getUserName(),
user.getPassword(),
ByteSource.Util.bytes("abc")
,getName());
return simpleAuthenticationInfo;
}
1.4、在首页显示用户信息的问题
1.4.1、通过代码实现在首页显示用户信息
改造 realm
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//第一步:获取用户名
String userName= (String) authenticationToken.getPrincipal();
//通过用户名查询用户对象
if(!(userName.equals("xiaobobo"))){
return null;
}
//假设查询出来了
User user = new User(1, "xiaobobo", "e99a18c428cb38d5f260853678922e03");
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
user,
user.getPassword(),
ByteSource.Util.bytes("abc")
,getName());
return simpleAuthenticationInfo;
}
改造Cotnroller
public String login(User user, Model model){
//封装成请求对象
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
//获取登陆的主体对象
Subject subject = SecurityUtils.getSubject();
//登陆
try{
subject.login(token);
}catch (UnknownAccountException err){ //用户名不对
logger.error("用户名不对");
model.addAttribute("userNameError","用户名不对");
return "login";
}catch (IncorrectCredentialsException err){ //说明是密码不对
logger.error("密码不对");
model.addAttribute("passwordError","密码不对");
return "login";
}catch (Exception err){
logger.error("其他问题造成登陆失败:"+err.fillInStackTrace());
model.addAttribute("otherError","其他问题造成登陆失败");
return "login";
}
//首页要显示用户信息
//获取用户信息
//这个方法返回的数据 实际上就是 realm中认证的SimpleAuthticationInfo的第一个参数的数据
User user1= (User) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("user",user);
return "index";
}
改在index.html文件
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
欢迎<span th:text="${user.userName}"></span>大爷再次回来.....
this is index Page And you? <br>
<a href="/logout">退出</a>
</body>
</html>
1.5、授权有很多方式
代码授权
1.5.1过滤器授权
需求:假设在我的首页有一个按钮 这个按钮访问后台数据的时候 addUser接口这个接口必须要用户具有 user:add权限才能访问
1、在realm中查询用户的权限和角色放到缓存中
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//第一步:先获取用户名
User user= (User) principalCollection.getPrimaryPrincipal();
//第二步:通过用户名查询数据库 当前用户具有的权限 以及角色
// ....
// ...
Set<String> perms=new HashSet<>();
perms.add("ad:add");
Set<String> roles=new HashSet<>();
roles.add("seller");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles);
simpleAuthorizationInfo.setStringPermissions(perms);
return simpleAuthorizationInfo;
}
2、在HTML页面上设置访问元素
<a href="/addUser">添加用户</a>
3、在配置文件中配置过滤器授权
maps.put("/addUser","perms[user:add]"); //要请求这个地址 用户必
1.5.2、注解授权
简单的、它要写个注解就可以了
1、在配置文件中配置aop对注解的支持
//下面配置AOP对注解的支持(也就是shiro中注解的支持)
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
2、在方法上使用注解来完成权限的判断
@RequestMapping("toDelete")
@RequiresPermissions({"user:delete"}) //要访问当前的方法必须具有user:delete的权限才能访问
//@RequiresRoles({"seller"})
public String toDelete(){
return "delete";
}
1.5.3、HTML页面上基于Thymeleaf的支持
1、导包
<!--导入thymeleaf对shiro的支持包-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2、配置方言
/*配置shiro-dialect这个方言*/
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
3、使用shiro的标签
<!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>
欢迎<span th:text="${user.userName}"></span>大爷再次回来.....
this is index Page And you? <br>
<a href="/logout">退出</a><br>
<hr>
<a href="/addUser">添加用户(测试过滤器授权)</a>
<br>
<a href="/toDelete">用户删除(测试注解授权)</a>
<!--下面玩下shiro标签库中的标签-->
<!--<shiro:authenticated>
<span>
用户的身份验证是成功的
</span>
</shiro:authenticated>
<shiro:guest>
<span>你是游客</span>
</shiro:guest>
<shiro:hasPermission name="user:add">
<span>用户必须具有某一个权限才能访问</span>
</shiro:hasPermission>
<shiro:hasAllRoles name="buyer,seller">
<span>拥有某一个角色下面才显示</span>
</shiro:hasAllRoles>
<shiro:lacksPermission>
<span>没有某一个权限的时候才能访问</span>
</shiro:lacksPermission>
<shiro:lacksRole name="xxx">
<span>没有某一个角色的时候才能访问</span>
</shiro:lacksRole>
<shiro:notAuthenticated>
<span>没有认证通过才能显示</span>
</shiro:notAuthenticated>
-->
<hr>
<!--下面就是显示用户信息的-->
<shiro:principal property="userName"/> <br>
<!--下面这个标签就表示的是用户已经登陆-->
<shiro:user>
<label>欢迎[<shiro:principal property="userName"/>]登陆</label>
</shiro:user>
</body>
</html>
1.6、缓存的使用
明白一个问题、为什么要使用缓存
演示了每一次 授权的时候 都会去访问咋们的 realm中的授权的方法
这样的话咋们的数据库的压力就会比较大 这种情况下 咋们缓存就应运而生了
缓存是缓存的是咋们的的这个授权信息的(不是其他信息)
1.6.1、导包
<!--下面就是缓存需要的包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
1.6.2、在resources下创建ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
<diskStore path="G:\mytemp" />
<cache name="users"
timeToLiveSeconds="300"
maxEntriesLocalHeap="1000"/>
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<defaultCache name="defaultCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
1.6.3、在ShiroConfig中进行缓存的配置
//下面进行缓存的配置
@Bean
public EhCacheManager ehCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehCacheManager;
}
1.6.4、在安全管理器上面进行配置
//配置的是安全管理器
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置校验的realm对象
logger.info("securityManager的过滤器执行了.....");
securityManager.setRealm(myRealm);
//下面就设置缓存了
securityManager.setCacheManager(ehCacheManager());
return securityManager;
}
1.7、session的管理
1.7.1、在ShiroConfig中进行配置
//Session的管理
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//Session到底的时候是否自动删除
sessionManager.setDeleteInvalidSessions(true);
//设置session的超时时间
sessionManager.setGlobalSessionTimeout(1);
return sessionManager;
}
1.7.2、在SecurityManager
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置校验的realm对象
logger.info("securityManager的过滤器执行了.....");
securityManager.setRealm(myRealm);
//下面就设置缓存了
securityManager.setCacheManager(ehCacheManager());
//设置Session的管理
securityManager.setSessionManager(sessionManager());
return securityManager;
}
1.8、rememeberMe功能的实现
Session+Cookie的模式来做 记住我这个功能
shiro跟咋们的思路也是一样的
1.8.1、配置ShiroConfig
//下面做的就是rememeberMe的这个功能
@Bean
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
//设置咋们的Cookie
rememberMeManager.setCookie(simpleCookie());
return rememberMeManager;
}
/**
* 自定义一个Cookie对象
* @return
*/
@Bean
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie("rememebenrMe");
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
1.8.2、在HTML页面上添加记住我的这个功能
<form action="/login" method="post">
用户名:<input type="text" name="userName"/><span th:text="${userNameError}"></span><br>
密码:<input type="password" name="password"><span th:text="${passwordError}"></span><br>
是否使用记住我的这个功能:<input type="checkbox" name="rememberMe"><br>
<input type="submit" value="登陆">
</form>
1.8.3、在认证的token上设置记住我的这个方法
@RequestMapping(value = "login")
public String login(User user, Model model,boolean rememberMe){
logger.info("接受到前端的数据rememebenrMe="+rememberMe);
//封装成请求对象
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
//设置记住我的这个功能
token.setRememberMe(rememberMe);
//获取登陆的主体对象
Subject subject = SecurityUtils.getSubject();
//登陆
try{
subject.login(token);
}catch (UnknownAccountException err){ //用户名不对
logger.error("用户名不对");
model.addAttribute("userNameError","用户名不对");
return "login";
}catch (IncorrectCredentialsException err){ //说明是密码不对
logger.error("密码不对");
model.addAttribute("passwordError","密码不对");
return "login";
}catch (Exception err){
logger.error("其他问题造成登陆失败:"+err.fillInStackTrace());
model.addAttribute("otherError","其他问题造成登陆失败");
return "login";
}
//首页要显示用户信息
//获取用户信息
//这个方法返回的数据 实际上就是 realm中认证的SimpleAuthticationInfo的第一个参数的数据
User user1= (User) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("user",user);
return "index";
}
1.8.4、在过滤器中配置哪些页面使用了rememberme这个公共之后可以直接访问
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
logger.info("shiro的过滤器执行了.....");
//配置如果认证没有通过的话 那么跳转的这个页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//如果没有 权限访问 那么就跳转到一个友好的也好的页面去提示用户
shiroFilterFactoryBean.setUnauthorizedUrl("/toUnAuthziration");
Map<String,String> maps=new LinkedHashMap<>();
//第一个参数是路劲 第二个参数是过滤器的名字
/**
* 常见的过滤器的名字以及含义
* /**:当前以及目录和后面的所有子目录全部都匹配上
* 127.0.0.1:8080/bobo 127.0.0.1:8080/bobo/xiaobobo
* /* :这个相当于只是匹配当前这一级的节点 127.0.0.1:8080/bobo
* 127.0.0.1:8080/bobo/xiaobobo
* authc:认证的过滤器
* anon: 表示的是/toIndex这个请求 不认证就可以访问 (匿名访问)
* maps.put("/toIndex","anon");
* logout:登陆过滤器
* maps.put("/logout","logout")
* perms:权限控制的
* roles:具有某一个角色才能访问
*
* 注意事项: /** 这个配置一定是最后 一个
*
*/
//maps.put("/toIndex","anon"); //表示的是不需要认证就可以访问
maps.put("/login","anon"); //访问请求的地址
maps.put("/logout","logout"); //这个就是退出功能
//设置哪些鞋面是使用了 rememeberMe功能之后 可以直接访问 不用认证了
maps.put("/addUser","user"); //这个设置的意思:假设我们使用了rememberMe这个功能那么 /addUser这个页面 就可以直接访问不需要认证了
maps.put("/toIndex","user"); //这个设置的意思:假设我们使用了rememberMe这个功能那么 /addUser这个页面 就可以直接访问不需要认证了
maps.put("/toDeleteUser","user"); //这个设置的意思:假设我们使用了rememberMe这个功能那么 /addUser这个页面 就可以直接访问不需要认证了
maps.put("/addUser","perms[user:add]"); //要请求这个地址 用户必须具有 user:add的权限
maps.put("/add","roles[seller]"); //要请求这个地址 用户必须具有 user:add的权限
maps.put("/**","authc"); //所有的请求都必须在用户认证之后才能访问 shiroFilterFactoryBean.setFilterChainDefinitionMap(maps);
//设置这个安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
SpringBoot 整合shiro实现多Realm的控制
场景:假设在我们的系统中 有两个角色 用户 和 管理员
用户有100W 管理员只有 2个
假设是让你设计这个表 你会怎么设计?
经典的五张表(用户和管理员 都是用户? 难道你要设计到一张表 )
现在的管理员登陆:通过用户名查询 用户数据(100W+)
如果是我们能够将这两个角色的数据设计到两张表里面
管理员(2个人) 用户表
管理员的表 用户的表 假设我们都使用shiro来进行认证 ? 怎么办?
设计思路?
就是在用户登陆时候 设置一个 登陆类型
重写 UserNameAndPasswordToken 在执行正式的登陆的时候?根据登陆的类型选择 我们要使用的Realm 最终去进行认证 那么这个功能就搞定了
存在两个问题
1:在哪里去做选择 执行哪一个realm?
ModularRealmAuthenticator.java类进行拓展(直接和realm打交道的)
2:我们的登陆类型如何来定
作业:编写一个用户的基本的CRUD 通过权限控制几个方法的请求 顺便缓存、rememeberme功能实现了
1.1、导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--导入shiro的功能包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
1.2、配置application.properies
#Thymeleaf的配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
1.3、编写登陆页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
下面是User的登陆<br>
<form action="/userLogin" method="post">
用户名:<input type="text" name="userName"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="用户登陆">
</form>
<hr>
下面是Admin的登陆
<form action="/adminLogin" method="post">
用户名:<input type="text" name="userName"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="用户登陆">
</form>
</body>
</html>
1.4、编写user模块登陆成功的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is user index page And you?
</body>
</html>
1.5、编写Admin模块登陆成功的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is admin index page And you?
</body>
</html>
1.6、编写登陆类型的枚举
public enum LoginType {
USER("User"), ADMIN("Admin");
private String type; //定义的是登陆的类型
private LoginType(String type){
this.type=type;
}
@Override
public String toString() {
return this.type.toString();
}
}
1.7、编写校验器
public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {
/**
* 想干一件事
* 就是通过传入数据的类型 来选择使用哪一个Realm
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
//做Realm的一个校验
assertRealmsConfigured();
//获取前端传递过来的token
CustomToken customToken=(CustomToken)authenticationToken;
//现在就可以获取这个登陆的类型了
String loginType = customToken.getLoginType(); // 登陆类型 1:User Admin
//获取所有的realms()
Collection<Realm> realms = getRealms();
//登陆类型对应的所有realm全部获取到
Collection<Realm> typeRealms=new ArrayList<>();
for (Realm realm:realms){
//realm类型和现在登陆的类型做一个对比
if(realm.getName().contains(loginType)){ //就能分开这两个realm
typeRealms.add(realm);
}
}
if(typeRealms.size()==1){
return doSingleRealmAuthentication(typeRealms.iterator().next(),customToken);
}else{
return doMultiRealmAuthentication(typeRealms,customToken);
}
}
}
1.8、编写UserController
@Controller
public class UserController {
//用户登陆的类型
private static final String LOGIN_TYPE= LoginType.USER.toString();
private Logger logger= LoggerFactory.getLogger(UserController.class);
/**
* 用户登陆的方法
* @param user
* @return
*/
@RequestMapping("userLogin")
public String login(User user){
//封装请求对象
CustomToken customToken=new CustomToken(user.getUserName(),user.getPassword(),LOGIN_TYPE);
//获取登陆主体
Subject subject = SecurityUtils.getSubject();
try{
subject.login(customToken);
if(subject.isAuthenticated()){
//说明认证是成功的
return "user_index";
}
}catch (UnknownAccountException err){ //用户名不对
logger.error("用户名不对");
}catch (IncorrectCredentialsException err){//密码不对
logger.error("密码不对");
}catch (Exception err){ //其他错误 造成登陆失败
logger.error("其他错误造成了登陆错误");
}
return "login";
}
}
1.9、编写AdminController
@Controller
public class AdminController {
//用户登陆的类型
private static final String LOGIN_TYPE= LoginType.ADMIN.toString();
private Logger logger= LoggerFactory.getLogger(UserController.class);
/**
* 用户登陆的方法
* @param admin
* @return
*/
@RequestMapping("adminLogin")
public String login(Admin admin){
//封装请求对象
CustomToken customToken=new CustomToken(admin.getUserName(),admin.getPassword(),LOGIN_TYPE);
//获取登陆主体
Subject subject = SecurityUtils.getSubject();
try{
subject.login(customToken);
if(subject.isAuthenticated()){
//说明认证是成功的
return "admin_index";
}
}catch (UnknownAccountException err){ //用户名不对
logger.error("用户名不对");
}catch (IncorrectCredentialsException err){//密码不对
logger.error("密码不对");
}catch (Exception err){ //其他错误 造成登陆失败
logger.error("其他错误造成了登陆错误");
}
return "login";
}
}
1.10、编写User对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 1841757056845722315L;
private int id;
private String userName;
private String password;
}
1.11.编写Admin对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin implements Serializable {
private int id;
private String userName;
private String password;
}
1.12、编写配置文件
@SpringBootConfiguration
public class ShiroConfig {
//拦截的过滤器的配置
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(
@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置下如果认证没有成功的登陆地址
shiroFilterFactoryBean.setLoginUrl("/toLogin");
Map<String,String> maps=new HashMap<>();
maps.put("/toLogin","anon");
maps.put("/userLogin","anon");
maps.put("/adminLogin","anon");
maps.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(maps);
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
//配置下SecurityManager
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置校验器
securityManager.setAuthenticator(authenticator());
List<Realm> realms=new ArrayList<>();
realms.add(userRealm());
realms.add(adminRealm());
//设置Realm
securityManager.setRealms(realms);
return securityManager;
}
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
return userRealm;
}
@Bean
public AdminRealm adminRealm(){
AdminRealm adminRealm = new AdminRealm();
return adminRealm;
}
//下面就是认证器的配置
@Bean
public CustomModularRealmAuthenticator authenticator(){
CustomModularRealmAuthenticator authenticator = new CustomModularRealmAuthenticator();
return authenticator;
}
}
1.13、编写用户的Realm
public class UserRealm extends AuthorizingRealm {
private Logger logger= LoggerFactory.getLogger(AdminRealm.class);
@Override
public String getName() {
return "UserRealm";
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("UserRealm执行了....");
//固定的
//第一步:获取用户名
String userName = (String) authenticationToken.getPrincipal();
//第二步:查询数据库
//查询中....
//查询中....
if(!(userName.equals("xiaobobo"))){
return null;
}
//下面就是查询出来的对象
User user = new User(1, "xiaobobo", "123");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());
return authenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
1.14、编写Admin的Realm
public class AdminRealm extends AuthorizingRealm {
private Logger logger= LoggerFactory.getLogger(AdminRealm.class);
@Override
public String getName() {
return "AdminRealm";
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//固定的
//第一步:获取用户名
String userName = (String) authenticationToken.getPrincipal();
//第二步:查询数据库
//查询中....
//查询中....
if(!(userName.equals("xiaowangzi"))){
return null;
}
logger.info("AdminRealm执行了....");
//下面就是查询出来的对象
Admin admin = new Admin(1, "xiaowangzi", "123");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(admin.getUserName(), admin.getPassword(), getName());
return authenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
1.15、token的拓展类的编写
public class CustomToken extends UsernamePasswordToken {
//定义登陆的类型是为了在后面的校验中 去选择使用哪一个realm
private String loginType;
public CustomToken(String userName,String password,String loginType){
super(userName,password);
this.loginType=loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
}
1.16、登陆页面跳转的Controller编写
@Controller
public class IndexController {
@RequestMapping("toLogin")
public String toLogin(){
return "login";
}
}