SpringBoot 整合 Shiro
### 依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.1</version>
</dependency>
注入shiro核心过滤器
Shiro与Springboot整合有个核心过滤器 用来拦截所有请求 名字叫做ShiroFilter 但是一般注入的是它的子类 通过工厂格式拿到
@Bean
public ShiroFilterFactoryBean getShiroFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager)
{
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//修改默认 认证页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
//页面保护相关信息
Map<String,String> map=new HashMap<>();
map.put("/hello","authc");
map.put("/user/*","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean ;
}
当然了 我们知道 shiro的 核心认证授权功能都是通过SecurityManager实现的 所以在构造方法中注入 ShiroFilter 主要配置的有如下内容
- 修改默认的认证页面 这里设置为login.html 默认的是 login.jsp
- 然后就是授权相关的信息了 这里通过一个map来保存相关配置 控制哪些资源需要什么样的权限来访问
注入shiro安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm)
{
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
安全管理器没有什么好配置的 主要是设置一下自定义的Realm 这个Realm实现了认证和授权相关的具体 逻辑
Realm
自定义Realm
通过继承AuthorizingRealm 重写do认证和do授权两个方法进行
package cn.duckflew.demo.realm;
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;
public class CustomerRealm extends AuthorizingRealm
{
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
{
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException
{
System.out.println("开始认证");
String principal = (String) authenticationToken.getPrincipal();
if (principal.equals("xiaochen"))
{
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("xiaochen", "9c074aff230a802bf52901cddd5c81da", ByteSource.Util.bytes("salt"), this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
-
其中 AuthenticationInfo是认证相关的信息 如果查询到有这个用户 则返回相应的 对象进行下一步的密码校验
如果查不到用户这里就返回空表示没有这个用户 会抛出 UnknownAccount异常
-
AuthorizationInfo是授权相关的信息 返回一个用户信息+权限相关的类 表示这个用户具有哪些权限 其中的参数是用户的身份集合 principalCollection 一个用户可以有很多个身份 例如手机号 邮箱 用户名等等 但是只能有一个主要的身份 这个叫做prinmaryPrincipal 可以通过这个集合拿到 拿到用户主身份 查询数据库 得到用户相关的权限信息
注入Realm
@Primary
@Bean
public Realm getRealm()
{
CustomerRealm realm = new CustomerRealm();
/**
* 新建凭证管理器
*/
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher("md5");
hashedCredentialsMatcher.setHashIterations(1024); //设置散列次数
realm.setCredentialsMatcher(hashedCredentialsMatcher);//注入凭证匹配器
return realm;
}
开始认证的主要步骤
public String login(String username, String password)
{
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try
{
subject.login(usernamePasswordToken);
return "认证成功"+username;
} catch (IncorrectCredentialsException e)
{
return "密码错误";
}
catch (UnknownAccountException e)
{
return "用户名错误";
}
}
由于我们在Spring容器已经注入了安全管理 SecurityUtils会自动帮我们注入 所以可以直接获取到Subject 我们对这个subject.login 方法
进入认证流程 根据前端传来的账号密码相关信息 封装一个UsernamePasswordToken传入login中 这个类是AuthenticationToken
的子类 login方法需要的参数也是这个类
以上就是认证的主要流程 此外考虑到用户的密码安全问题 密码采用md5+salt 再加 多次哈希散列化 的方法 数据库只保存用户的密文密码 并把盐保存在密码中(虽然黑客能拿到数据库密码 并且也能拿到盐 但是这并不代表加盐是无用的 因为加盐的策略是由我们来规定的 可以选择加明文尾部或者首部 或者任意第几个字符之间 这增加了黑客的碰撞成本)
这样设置了密文之后 我们需要在shrio认证的相关配置里面增加 一些相关的配置 来使用shiro进行密文匹配 因为默认的匹配规则是根据明文匹配的
我们需要自定义一个密文匹配器 也叫凭证匹配器
这个在构造Realm的时候我们已经构造了
@Primary
@Bean
public Realm getRealm()
{
CustomerRealm realm = new CustomerRealm();
/**
* 新建凭证管理器
*/
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher("md5");
hashedCredentialsMatcher.setHashIterations(1024); //设置散列次数
realm.setCredentialsMatcher(hashedCredentialsMatcher);//注入凭证匹配器
return realm;
}