一 前言
本篇内容是关于shiro权限框架的使用,当然知识追寻者不会手把手傻瓜式的把代码给你,而是先要让你理解权限的核心是什么,shiro进行权限认证的流程,结合核心代码展现方式给大家;
公众号:知识追寻者
知识追寻者(Inheriting the spirit of open source, Spreading technology knowledge;)
二 shio 的认证流程
2.1 认证与授权介绍
首先,需要强调一下,每个权限框架的核心功能就是 认证
,与授权
,如果读者有着2个概念,学习权限相关的知识将会无比简单;
简单说下认证,认证就是你给服务端一段信息,这段信息服务端收到会跟之前你注册的信息进行比对,比如在无任何加密的情况下将 账号 , 密码 输入 form表单发送请求到服务端,服务端根据你之前注册的账号密码进行比对,如果比对成功,说明认证成功,否则认证失败;复杂一点的认证就是 将你的账号 ,密码进行封装为另一种形式通常称为凭证,签名之类, 再将这种形式凭证的东西发给你,然后每次请求你都带上凭证而后端根据你发送的凭证信息进行解密比对,比对成功就说明认证成功,否则认证失败;
简单说下授权,现在的权限设计基本都是基于 用户 角色 , 权限 方式进行设计, 故一个用户拥有什么样的权限都基于拥有什么样的角色,我们可以给每个角色赋予不同的权限,然后认证成功之后就可以给用户进行授权(也就是将用户拥有的角色,权限返回出去就ok,每次请求的时候附带上权限信息即可,服务端再根据附带的权限信息进行判别);
2.2 shiro的主要概念与结构
- Subject 当前主体,你可以理解为当前用户;
- SecurityManag: 主体管理器,简单理解为管理着许多用户信息;
- Authenticator : 认证,在登陆或者发送亲求的时候进行认证;
- Authorizer : 授权,权限控制,赋予用户有哪些用户权限信息;
- SessionManager : 会话管理器,
- CacheManager : 缓存管理器
- Cryptography : 加密,适用于企业级开发,提供算法加密;
- Realms : 是shio和应用全安数据之间的连接桥梁;在这边会进行 认证和授权工作;
有关于shiro特色如下,也就是对上面的内容进行分块
更多的介绍信息看官网 : https://www.infoq.com/articles/apache-shiro/
2.3 shiro 认证流程
大致的认证流程如下;其实我们经常使用的没这么复杂, 首先就是 用户发送请求 , 我们拦截请求,在将请求 信息 通过 security manager , 其 内部就到了 Realm 进行 认证 与授权, 最后将请求到达服务端,服务端封装好数据返回给前端; ok 很简单哟;
三 springboot 整合shiro
这套整合方案 是根据 官方 的步骤整个 将 xml 的 bean 替换为java注解即可 ; 有兴趣的读者看官网
http://shiro.apache.org/spring.html
3.1 pom.xml
本次书写过程,知识追寻者 忽略了建表操作,读者知道 思路如何就很容易实现;
先看下引入的依赖,有需要建表的读者把 >mybatis-spring-boot-starter 注解打开即可;关键是引入shiro核心依赖
<dependencies>
<!-- MyBatis -->
<!-- <dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>-->
<!--web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Shiro 核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.2 shiro配置如下
- ShiroFilterFactoryBean 是配置拦截器,用户拦截url, anno 表示匿名,任何用户都可以访问; authc表示授权,需要拥有授权信息的用户才可以访问,
- SecurityManager 是主体管理器,管理许多的主体,并且需要将我们实现的Realm注入其中,进行认证与授权;
- ShiroRealm 注入了HashedCredentialsMatcher ,也就是产生凭证的地方,其加密方式与需与用户注册时的方式一致,否则会在Realm中认证失败;
- AuthorizationAttributeSourceAdvisor ,APO支持,主要是需要使用到 @RequiresRoles, @RequiresPermissions注解的时候 才起作用;
- LifecycleBeanPostProcessor ,Shiro bean的生命周期,看官网的意思是AuthorizationAttributeSourceAdvisor 的实现要基于其才行;
/**
* @Author lsc
* <p> </p>
*/
@Configuration
public class ShiroConfiguration {
// http://shiro.apache.org/spring.html
// shiro授权拦截
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 创建一个map , 配置拦截信息
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 匿名用户可访问
filterChainDefinitionMap.put("login/**", "anon");
filterChainDefinitionMap.put("register/**", "anon");
// 认证用户可访问
filterChainDefinitionMap.put("/**", "authc");
// 登陆url
shiroFilterFactoryBean.setLoginUrl("/user/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/* *
* @Author lsc
* <p>安全管理器,管理多个subject </p>
* @Param []
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(Realm());
return securityManager;
}
/* *
* @Author lsc
* <p>认证信息 </p>
* @Param []
*/
@Bean
public ShiroRealm Realm() {
// 我们实现的认证授权类
ShiroRealm realm = new ShiroRealm();
// 这边配置了加密设置,在用户登陆时创建密码的方式也要一致
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
/* *
* @Author lsc
* <p> 加密凭证</p>
* @Param []
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 采用md5加密
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 循环加密2次
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
/* *
* @Author lsc
* <p> 开启Shiro-aop注解支持,比如使用到 @RequiresRoles, @RequiresPermissions ,
* 其依赖于shiro生命周期</p>
* @Param [securityManager]
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/* *
* @Author lsc
* <p> shiro生命周期</p>
* @Param []
*/
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanProcessor() {
return new LifecycleBeanPostProcessor();
}
}
3.3 ShiroRealm
ShiroRealm 实现了 AuthorizingRealm ,其认证AuthenticationInfo方法; 授权AuthorizationInfo 方法, 代码步骤非常完整,不在过多讲解;
/**
* @Author lsc
* <p> </p>
*/
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private SysUserService userService;
// 简单重写获取授权信息方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取授权信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 获取用户
SysUserEntity userEntity = (SysUserEntity) principalCollection.getPrimaryPrincipal();
// 通过用户 - 角色 - 权限的关联 获取 角色 权限 此处省略
//.................
// 将角色的权限放入集合
Set<String> rolesSet = new HashSet<>();
// 将权限 信息放入集合
Set<String> permsSet = new HashSet<>();
// 权限授权
simpleAuthorizationInfo.setStringPermissions(permsSet);
// 角色授权
simpleAuthorizationInfo.setRoles(rolesSet);
return simpleAuthorizationInfo;
}
// shiro获取认证信息,即根据 token 中的用户名从数据库中获取密码、盐等并返回
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户名
String userName = token.getPrincipal().toString();
// 查询获取加密后的密码
SysUserEntity user = userService.getUserByUserName(userName);
String password= user.getPassword();
// 盐值
String salt = user.getSalt();
// 进行验证
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, password, ByteSource.Util.bytes(salt), getName());
// 认证通过
return authenticationInfo;
}
}
3.4 注册于登陆方法
登陆方法中 SecurityUtils.getSubject(); 获取用户主体信息,将 用户名 和密码 传给 UsernamePasswordToken 调用 subject.login(usernamePasswordToken); 就会到realm中进行认证;
注册方法最重要的就是加密和方式和配置类一致,这边多了给盐值,也就是给密码混淆,使安全性能更加友好,故在realm认证的时候也需要盐值;
@Autowired
SysUserService sysUserService;
@PostMapping(value = "login")
public ResponseEntity login(@RequestBody SysUserEntity sysUserEntity) {
String username = sysUserEntity.getUsername();
Subject subject = SecurityUtils.getSubject();
// shiro认证
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, sysUserEntity.getPassword());
// jwt 集成 shiro认证
SysUserEntity user = sysUserService.getUserByUserName(username);
Map map = new HashMap();
if (user==null){
System.out.println("用户名不能为空");
// 抛异常
}
try {
// shiro认证认证
subject.login(usernamePasswordToken);
map.put("name",user.getName());
} catch (AuthenticationException e) {
System.out.println("认证失败"+e.getMessage());
}
return ResponseEntity.ok(map);
}
@PostMapping("register")
public ResponseEntity unauth(@RequestBody SysUserEntity sysUserEntity){
SysUserEntity userByUserName = sysUserService.getUserByUserName(sysUserEntity.getUsername());
if (userByUserName!=null){
System.out.println("用户名不能为空");
// 抛异常
}
// 加密方式
String hashAlgorithmName = "MD5";
String credentials = sysUserEntity.getPassword();
// 加密次数
int hashIterations = 2;
// 生成盐,默认长度 16 位
String salt = new SecureRandomNumberGenerator().nextBytes().toString();
ByteSource credentialsSalt = ByteSource.Util.bytes(salt);
// 加密后的密码
SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials, credentialsSalt, hashIterations);
// 设置加密后的密码
sysUserEntity.setPassword(simpleHash.toString());
// 设置盐
sysUserEntity.setSalt(salt);
// r入库
sysUserService.addSysUser(sysUserEntity);
return ResponseEntity.ok(sysUserEntity);
}