SpringBoot 与Shiro 整合系列(三)多Realm验证和认证策略
系列(三)讲述 SpringBoot整合Shiro 实现多Realm验证以及认证策略
目录
shiro是一个很好的登陆以及权限管理框架,但是默认是单realm单数据表,但是很多业务需求单realm是很难实现登陆以及权限管理的功能(比如业务中用户分布在不同的数据表),这个时候就需要使用多个Realm。
一、使用场景
存在这样一种场景,我们可能会把安全数据放到不同的数据库,比方说 mysql里有,oracle里也有,mysql里面的加密算法是MD5,oracle里面的加密算法是SHA1。这个时候我们进行用户认证时,就需要同时访问这两个数据库,就需要多个Realm。如果有多个Realm的话,还需要涉及到认证策略的问题。
二、多Realm验证
多重认证,主要的类是ModularRealmAuthenticator,他有两个需要配置的属性,一个是Collection(用于存储Realm),另一个是AuthenticationStrategy(用于存储验证的策略 )。
通过查看源码可以看到 ModularRealmAuthenticator.class 中的 doAuthenticate方法:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {//一个realm
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {//多个realm
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
从上面doAuthenticate方法中可以看到:
如果有一个Realm 使用的是:
- doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
如果有多个Realm 使用的是:
- doMultiRealmAuthentication(realms, authenticationToken);
所以我们可以配置多个Realm 给到 ModularRealmAuthenticator 这个bean,将ModularRealmAuthenticator 单独配置为一个bean,将这个bean 配置给SecurityManager。
配置流程:
1.在原有代码的基础上,添加第二个Realm SecondRealm.java
SecondRealm.java 中的 加密算法 为 SHA1
package com.koncord.shiro;
import org.apache.shiro.SecurityUtils;
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.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import com.koncord.model.User;
import com.koncord.service.UserService;
/**
* 自定义 Realm
* @author Administrator
*
*/
public class SecondRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("SecondRealm 执行授权逻辑");
//给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加资源的授权字符串
//info.addStringPermission("user:add");
//导数据库查询当前登录用户的授权字符串
//获取当前登录用户
Subject subject=SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
User sbUser=userService.findUserByName(user.getName());
info.addStringPermission(sbUser.getPerms());
return info;
}
/**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("SecondRealm 执行认证逻辑");
//编写shiro 判断逻辑,判断用户名和密码
//1.判断用户名
UsernamePasswordToken userToken=(UsernamePasswordToken) token;
//根据用户名获取用户信息
User user=userService.findUserByName(userToken.getUsername());
if(user == null){
//用户不存在
return null;//shiro底层会抛出 UNknowAccountException
}
//2.判断密码 第一个参数:需要返回给 subject.login方法的数据 第二个参数:数据库密码 第三个参数:realm的名字
// return new SimpleAuthenticationInfo(user, user.getPassword(), "");
//根据用户的情况,来构建AuthenticationInfo对像并返回,通常使用的实现类是SimpleAuthenticationInfo
//以下信息是从数据库获取的
//1).principal:认证的实体类信息。可以是username,也可以是数据表对应的用户的体类对象
Object principal=user;
//2).credentials:密码(数据库获取的用户的密码)
Object credentials=user.getPassword();
//3).realmName:当前realm对象的name,调用父类的getName()方法即可
String realmName=getName();
//4).盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getName());//盐值 要唯一
return new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
}
public static void main(String[] args) {
String hashAlgorithmName="SHA1";//加密算法(与配置文件中的一致)
String credentials="111111";//密码
ByteSource salt=ByteSource.Util.bytes("zhangsan");//盐值
// ByteSource salt=null;//盐值
int hashIterations=1024;//加密次数(与配置文件中的一致)
System.out.println(new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations));
}
}
第一个自定义Realm的代码:
package com.koncord.shiro;
import org.apache.shiro.SecurityUtils;
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.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import com.koncord.model.User;
import com.koncord.service.UserService;
/**
* 自定义 Realm
* @author Administrator
*
*/
public class UserRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("UserRealm 执行授权逻辑");
//给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加资源的授权字符串
//info.addStringPermission("user:add");
//导数据库查询当前登录用户的授权字符串
//获取当前登录用户
Subject subject=SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
User sbUser=userService.findUserByName(user.getName());
info.addStringPermission(sbUser.getPerms());
return info;
}
/**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("UserRealm 执行认证逻辑");
//编写shiro 判断逻辑,判断用户名和密码
//1.判断用户名
UsernamePasswordToken userToken=(UsernamePasswordToken) token;
//根据用户名获取用户信息
User user=userService.findUserByName(userToken.getUsername());
if(user == null){
//用户不存在
return null;//shiro底层会抛出 UNknowAccountException
}
//2.判断密码 第一个参数:需要返回给 subject.login方法的数据 第二个参数:数据库密码 第三个参数:realm的名字
// return new SimpleAuthenticationInfo(user, user.getPassword(), "");
//根据用户的情况,来构建AuthenticationInfo对像并返回,通常使用的实现类是SimpleAuthenticationInfo
//以下信息是从数据库获取的
//1).principal:认证的实体类信息。可以是username,也可以是数据表对应的用户的体类对象
Object principal=user;
//2).credentials:密码(数据库获取的用户的密码)
Object credentials=user.getPassword();
//3).realmName:当前realm对象的name,调用父类的getName()方法即可
String realmName=getName();
//4).盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getName());//盐值 要唯一
return new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
}
public static void main(String[] args) {
String hashAlgorithmName="MD5";//加密算法(与配置文件中的一致)
String credentials="111111";//密码
ByteSource salt=ByteSource.Util.bytes("admin");//盐值
// ByteSource salt=null;//盐值
int hashIterations=1024;//加密次数(与配置文件中的一致)
System.out.println(new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations));
}
}
2.修改ShiroConfig 配置类
2.1 创建第2个Realm:
/**
* 创建第二个Realm
*/
@Bean
public SecondRealm secondRealm(){
SecondRealm secondRealm = new SecondRealm();
//设置 凭证匹配器
secondRealm.setCredentialsMatcher(hashedCredentialsMatcherSHA());
return secondRealm;
}
/**
* 配置 凭证匹配器 ,加密算法为 SHA1
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcherSHA(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法
hashedCredentialsMatcher.setHashAlgorithmName("SHA1");
//设置加密次数,比如两次,相当于SHA1(SHA1())
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
2.2 修改securityManager方法,添加多realms:
/**
* 2.创建 DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager =new DefaultWebSecurityManager();
// //关联realm
// securityManager.setRealm(userRealm);
//添加多realms
List<Realm> realms =new ArrayList<Realm>();
realms.add(userRealm);
realms.add(secondRealm());
securityManager.setRealms(realms);
return securityManager;
}
ShiroConfig 配置类完整代码:
package com.koncord.shiro;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
/**
* Shiro配置类
* @author Administrator
*
*/
@Configuration
public class ShiroConfig {
/**
* 3.创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截
* 常用的过滤器:
* anon:无需认证(登录)可以访问
* authc:必须认证才可以访问
* user:如果使用rememberMe的功能可以直接访问
* perms:该资源必须得到资源权限才可以访问
* role:该资源必须得到角色权限才可以访问
*/
Map<String, String> filterMap = new LinkedHashMap<String, String>();
// filterMap.put("/add", "authc");
// filterMap.put("/update", "authc");
filterMap.put("/testThymeleaf", "anon");
filterMap.put("/login", "anon");
//授权过滤器
//注意:当前授权拦截后,shiro会自动跳转到未授权页面
filterMap.put("/add", "perms[user:add]");
filterMap.put("/update", "perms[user:update]");
filterMap.put("/*", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//设置登录跳转链接
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//设置未授权提示页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
return shiroFilterFactoryBean;
}
/**
* 2.创建 DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager =new DefaultWebSecurityManager();
// //关联realm
// securityManager.setRealm(userRealm);
//添加多realms
List<Realm> realms =new ArrayList<Realm>();
realms.add(userRealm);
realms.add(secondRealm());
securityManager.setRealms(realms);
return securityManager;
}
/**
* 1.创建Realm
*/
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
//设置 凭证匹配器
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* 创建第二个Realm
*/
@Bean
public SecondRealm secondRealm(){
SecondRealm secondRealm = new SecondRealm();
//设置 凭证匹配器
secondRealm.setCredentialsMatcher(hashedCredentialsMatcherSHA());
return secondRealm;
}
/**
* 配置ShiroDialect,用于thymeleaf和shiro标签配合使用
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
/**
* 配置 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了,
* 所以我们需要修改下doGetAuthenticationInfo中的代码;)
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置加密次数,比如两次,相当于md5(md5())
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
/**
* 配置 凭证匹配器 ,加密算法为 SHA1
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcherSHA(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法
hashedCredentialsMatcher.setHashAlgorithmName("SHA1");
//设置加密次数,比如两次,相当于SHA1(SHA1())
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
}
这样流程就配置完成了!
3.测试
打断点,进行debug调试,看下运行效果
(1)在UsernamePasswordToken.class 的
处打断点,会看到断点停两次。
(2)在ModularRealmAuthenticator.class的
处,会看到 Realm的个数。
(3)由于两种都使用的HashedCredentialsMatcher 时的两种算法:
测试成功!!!
三、认证策略
如果有多个Realm,怎样才能认证成功,这就我们所谓的认证策略。
1.概念
(1)当一个应用程序配置了两个或两个以上的Realm 时,ModularRealmAuthenticator 依靠内部的AuthenticationStrategy 组件来判定认证的成功或失败。
AuthenticationStrategy是一个无状态的组件,它在身份验证尝试中被询问4 次(这4 次交互所需的任何必要的状态将被作为方法参数):
- 在任何Realm 被调用之前被询问;
- 在一个单独的Realm 的getAuthenticationInfo 方法被调用之前立即被询问;
- 在一个单独的Realm 的getAuthenticationInfo 方法被调用之后立即被询问;
- 在所有的Realm 被调用后询问。
(2)认证策略的另外一项工作就是聚合所有Realm的结果信息封装至一个AuthenticationInfo实例中,并将此信息返回,以此作为Subject的身份信息。
2.认证策略的使用
认证策略主要使用的是 AuthenticationStrategy 接口。
这个接口有三个实现类(认证策略的实现):
策略 | 意义 |
---|---|
AllSuccessfulStrategy | 所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了 |
AtLeastOneSuccessfulStrategy(默认) | 只要有一个Realm验证成功即可,和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息 |
FirstSuccessfulStrategy | 只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略; |
ModularRealmAuthenticator内置的认证策略默认实现是AtLeastOneSuccessfulStrategy 方式,因为这种方式也是被广泛使用的一种认证策略。
2.1 默认使用
验证下AtLeastOneSuccessfulStrategy:通过源码的方法验证
①为了看效果,修改下 SecondRealm的认证消息的返回值
SimpleAuthenticationInfo info= new SimpleAuthenticationInfo("SecondRealmName", credentials, credentialsSalt, realmName);
②debug方式启动项目,输入 用户名和密码,点击submit,进入如下断点,可以看到 认证策略:
默认AtLeastOneSuccessfulStrategy策略下,有一个Realm认证成功就可以了。就算另一个Realm认证失败也没关系。
2.1 切换认证策略
(1)如何切换认证策略?
比如:切换成 AllSuccessfulStrategy 即所有认证策略都通过了,才算认证成功。
答:可以看出 认证策略是ModularRealmAuthenticator 类的一个属性 authenticationStrategy,即在ShiroConfig配置类中添加配置:
/**
* 系统自带的Realm管理,主要针对多realm
*/
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
//设置认证策略
modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
return modularRealmAuthenticator;
}
并在securityManager方法中设置这个modularRealmAuthenticator()方法:
即可。
(2)验证AllSuccessfulStrategy:
打断点,debug重启项目,进行验证:发现第二个Realm报异常
登录失败
上一篇:SpringBoot 与Shiro 整合系列(二)实现MD5盐值加密
延伸:
springboot整合shiro实现多realm不同数据表登陆
Spring Boot 集成Shiro的多realm配置
相关文章
- Springboot项目使用junit-test(@Test)报错原因汇总
- SpringBoot项目打瘦包
- SpringBoot配置图片访问404SpringBoot配置图片访问路径springboot如何访问图片
- SpringBoot @Value 解析集合
- SpringBoot入门-集成ElasticSearch(一)
- 【Springboot实战项目】超好的实战项目第二波,赶紧收藏起来
- SpringBoot启动过程解析(简化)
- SpringBoot集成RabbitMQ简单举例
- 搞定SpringBoot多数据源(2):动态数据源
- SpringBoot 与Shiro 整合系列(一)用户认证与授权实战
- 秒懂如何使用SpringBoot+Junit4进行单元测试
- 【springBoot】SpringBoot修改启动logo图案
- SpringBoot之profile的使用
- springBoot引入laydate
- springboot实现定时任务的方式