目录
概述
认证授权及鉴权
Shiro框架的核心组件
基本流程
spring boot+shiro+mybatisPlus+...实现用户登录
step1:准备工作
(1)坐标
(2)连接数据库
(3)JavaBean
(4)dao数据访问层
(5)密码工具类 DigestsUtil
(6)配置类
step2:认证功能
step3:授权鉴权
概述
shiro是apache下一个开源框架,它将软件系统的安全认证功能抽离出来,组成一个通用的安全认证框架,其功能主要有身份认证,授权鉴权,会话管理等功能。
认证授权及鉴权
对于认证授权及鉴权,可以通过这样一个例子来理解,比如说我买了一张去国外的机票,现在要登机,那我在取机票的时候,出示的身份证就是认证的过程,工作人员在核实你确实是本人之后,会把机票给你,这个就是授权,当你拿着张机票去登记的时候,工作人员还要查你的机票,看你是不是我这个航班的机票,这个就是鉴权。理解之后,再看下面的概念就很具体了。
身份认证是指判断一个用户是否为合法用户的过程,最常用的身份认证方式是系统通过判断用户输入的用户名密码和数据库中存储的用户名密码是否一致。由此确认该用户是否为合法用户。
授权也叫访问控制,它是指控制谁能够访问哪些资源。用户认证成功后,系统会为其分配对应的权限,访问资源时,会校验其是否有权限访问这个校验的过程就是鉴权。
Shiro框架的核心组件
- Subject(主体)
即外部应用与subject进行交互,subject将用户作为当前操作的主体,这个主体:可以是一 个通过浏览器请求的用户,也可能是一个运行的程序 。比如说我今天要做一个登录功能的认证,那我的Subject就是当前登录的用户。
- SecurityManager(权限管理器)
权限管理器是Shiro的核心,负责管理所有Subject并通过Authenticator完成认证,以及Authorizer完成授权。
- Authenticator(认证器)
登录时进行身份认证
- Authorizer(授权器)
当用户通过认证,访问资源时,对用户们进行授权操作
- Realm(数据源)
Realm用于完成数据库的读取,并在其中完成授权校验相关操作
基本流程
1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager。
2、SecurityManager接着会委托给内部组件Authorizer;
3、Authorizer再将其请求委托给我们的Realm去做;Realm才是真正干活的;
4、Realm将用户请求的参数封装成权限对象。再从我们重写的doGetAuthorizationInfo方法中获取从 数据库中查询到的权限集合。
5、Realm将用户传入的权限对象,与从数据库中查出来的权限对象,进行一一对比。如果用户传入的 权限对象在从数据库中查出来的权限对象中,则返回true,否则返回false。 进行授权操作的前提:用户必须通过认证。
OK,理论结束,实践开始。。。。
spring boot+shiro+mybatisPlus+...实现用户登录
step1:准备工作
在这一部分需要先搭建一个项目的基本框架,与数据库的交互以及数据库表。在数据库方面一共需要五张表,表的结构在级联查询中已经提到,这里不再赘述,链接附在这里CSDN
需要特别关注一点的是,对于表中密码这部分的数据,我使用sha-1算法进行了加密,一部分原因是为了安全系数更高些,另一部分原因是shiro框架支持多种算法,例如sha系列算法,以及md5Hash,md2Hash,在这里刚好也使用一下。
(1)坐标
首先需要搭建一个springBoot 项目,然后添加需要的坐标,比如数据持久层的mybatisPlus,管理javaBean的lombok,以及shiro框架相关坐标
<properties>
<java.version>1.8</java.version>
<shiro.version>1.3.2</shiro.version>
</properties>
<dependencies>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
</dependencies>
(2)连接数据库
在resource资源文件下的application.properties文件中配置4想数据库连接的基本信息
(3)JavaBean
作用:ORM架构中的O,与数据库表中的字段进行一一对应。
在数据库中我们设计了5张表,但在实体类这里,不涉及关系表,关系表只在查数据的时候使用,比如我在判断该用户有没有某种权限的时候,根据登录用户输入的用户名查询用户的基本信息,拿着基本信息中的id查询用户权限,这个时候就需要使用到关系表,
(4)dao数据访问层
数据的交互基于两个查询操作,一个是查询基本信息也就是user表总的信息,另一个是查询详细信息,包括角色信息和权限信息
(5)密码工具类 DigestsUtil
作用:获取明文密码的密文和盐值,这是由于数据库中没有数据,需要先造几条数据供使用
对于输入的明文,在获取密文密码前需要先获取盐值,所以调用generateSalt方法生成随机的盐值密文。其次调用show方法进行加密。然后存到map集合中进行返回,到这里我们需要做的就是把数据存到数据库中。
public class DigestsUtil {
//确定要使用的加密算法
public static final String SHA1 = "SHA-1";
//加密次数
public static final Integer COUNTS =369;
/**
* @Description sha1方法,根据明文和盐值进行加密
* @param input 需要散列字符串
* @param salt 盐字符串
* @return
*/
public static String show(String input, String salt) {
return new SimpleHash(SHA1, input, salt,COUNTS).toString();
}
/**
* @Description 随机获得salt字符串
* @return
*/
public static String generateSalt(){
SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
return randomNumberGenerator.nextBytes().toHex();
}
/**
* @Description 生成密码字符密文和salt密文
* @param
* @return
*/
public static Map<String,String> entryptPassword(String passwordPlain) {
Map<String,String> map = new HashMap<>();
String salt = generateSalt();
String password =show(passwordPlain,salt);
map.put("salt", salt);
map.put("password", password);
return map;
}
public static void main(String[] args) {
//用户测试数据
String name = "xixi";
String pwd = "12345";
Map map = entryptPassword(pwd);
System.out.println(map.toString());
}
}
(6)配置类
作用:定义shiro框架的基本配置,包括拦截规则,登录页面等等,具体功能在方法上有备注
@Configuration
public class ShiroConfiguration {
/**
* 1.创建shiro自带cookie对象
*/
@Bean
public SimpleCookie sessionIdCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
//2.创建自定义的realm,用于登录认证和授权
@Bean
public MyRealm getRealm() {
return new MyRealm();
}
/**
* 3.创建会话管理器
*/
@Bean
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setGlobalSessionTimeout(3600000);
return sessionManager;
}
//4.创建安全管理器
@Bean
public SecurityManager defaultWebSecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(getRealm());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 5.保证实现了Shiro内部lifecycle函数的bean执行
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 6.开启对shiro注解的支持
* AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 7.配合DefaultAdvisorAutoProxyCreator实现使用注解进行权限校验,例如:@RequireRoles、@RequirePermissions等
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager());
return authorizationAttributeSourceAdvisor;
}
//8.配置shiro的过滤器工厂再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
@Bean
public ShiroFilterFactoryBean shiroFilter() {
//1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器
filterFactory.setSecurityManager(defaultWebSecurityManager());
//3.通用配置(跳转登录页面,为授权跳转的页面)
filterFactory.setLoginUrl("/autherror");//跳转url地址
//4.设置过滤器集合
//key = 拦截的url地址
//value = 过滤器类型
Map<String,String> filterMap = new LinkedHashMap<>();
//key:请求规则 value:过滤器名称
filterMap.put("/login","anon");//当前请求地址可以匿名访问
filterMap.put("/user/**","authc");//当前请求地址必须认证之后可以访问
//在过滤器工程内设置系统过滤器
filterFactory.setFilterChainDefinitionMap(filterMap);
return filterFactory;
}
}
至此,准备阶段就结束了,可以进行shiro框架功能的测试。
step2:认证功能
认证流程在前文已经叙述过,这里不再赘述,下文主要描述怎么利用shiro实现认证。首先用户在登录界面输入用户名和密码,因为我们在数据库里存的是密文,所以需要先判断该用户名和密码,这个时候,根据我们在配置类中自定义的Realm类中的doGetAuthenticationInfo方法进行认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//将参数AuthenticationToken 转换为用户名密码令牌
UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
//获取令牌中的用户名
String username = token.getUsername();
//按照该用户名查询数据库,若数据库中没有数据,说明该用户不存在,返回null,若存在,进行验证
User user = userService.findUserByName(username);
if(user!=null){
//SimpleAuthenticationInfo:该类是shiro提供的对象,可以根据传入的参数进行解密操作
/**参数含义
*参数1:安全对象,后续用于鉴权
*参数2:从数据库中查到的密文密码
*参数3:从数据库中查到的盐值的字节数组
*参数4:realm类名
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), "myRealm");
return info;
}
return null;
}
这个时候就有人有问题了,这个参数authenticationToken里面的数据是怎么来传过来的?🆗,解释一下,在我们访问登录页面输入数据时,在控制层中,会先进行判断,若不为空,将数据封装成UsernamePasswordToken对象,再获取Subject对象,调用login方法进行登录验证,否则处理空异常。
这个时候,又有人会有问题了,之前设置了加密算法和加密次数,那shiro框架在解密的时候怎么知道你用的是哪个加密算法,加密了多少次啊?🆗再解释一下,在Realm类中其实还有一个方法initCredentialsMatcher(),它是用来指导密码的加密算法和迭代次数的,写完后告诉shiro你的这个实现类是谁,shiro就知道该去回调哪个方法了。
step3:授权鉴权
这个功能主要是用户在访问某个功能鉴权的的时候使用,也就说,每当我访问一个新的功能进行鉴权的时候,都需要重新执行授权方法,该方法同样定义在Realm类中
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取认证通过的用户对象
User user = (User) principalCollection.getPrimaryPrincipal();
//根据用户id查询该用户的详细信息
User userDetail = userService.findUserDetailById(user.getId());
//定义存储角色的集合
HashSet<String> roles = new HashSet<>();
//定义存储权限的集合
HashSet<String> perms = new HashSet<>();
//对角色集合进行遍历,将每一个角色对应的的权限存入权限集合
for (Role role : userDetail.getRoles()) {
for (Permission perm : role.getPermissions()) {
perms.add(perm.getCode());
}
}
//封装角色权限对象返回
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(perms);
info.setRoles(roles);
return info;
}
对于后续的鉴权,我使用注解解决
在资源上使用@RequiresPermissions注解,那么当用户访问该资源时判断用户有没有相应的权限 ,若有,可以访问资源,若无,抛出异常。对于异常情况的处理,使用全局异常处理器@ControllerAdvice进行捕获