1.shiro简介
1.1.基本功能点
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。
· Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
· Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
· Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
· Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
· Web Support:Web 支持,可以非常容易的集成到 Web 环境;
· Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
· Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
· Testing:提供测试支持;
· Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
· Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可
1.2.Shiro的架构
1.2.1.外部
我们从外部来看 Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成工作。如下图
可以看到:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject;其每个 API 的含义:
Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
也就是说对于我们而言,最简单的一个 Shiro 应用:
1.应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
2.我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。
1.2.2.内部
接下来我们来从 Shiro 内部来看下 Shiro 的架构,
Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;
SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);
SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密 / 解密的。
2.shiro组件
2.1.身份验证
身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。
在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名 / 密码 / 手机号。
credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。
最常见的 principals 和 credentials 组合就是用户名 / 密码了。接下来先进行一个基本的身份认证。
另外两个相关的概念是之前提到的 Subject 及 Realm,分别是主体及验证主体的数据源。
pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--当前工程的坐标-->
<groupId>com.zh</groupId>
<artifactId>ZHSQ</artifactId>
<!-- 打包时需要的依赖-->
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/>
</parent>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 获取首字母的依赖-->
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!--mybatis依赖包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok 依赖,子工程中假如需要lombok,不需要再引入-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope><!--provided 表示此依赖仅在编译阶段有效-->
</dependency>
<!--单元测试依赖,子工程中需要单元测试时,不需要再次引入此依赖了-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!-- <scope>test</scope><!–test表示只能在test目录下使用此依赖–>-->
<exclusions>
<exclusion><!--排除一些不需要的依赖-->
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!--其它依赖...-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.9</version>
</dependency>
<!--redis应用依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--通过maven-compiler-plugin插件设置项目
的统一的jdk编译和运行版本-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.9.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<includeSystemScope>true</includeSystemScope>
<mainClass>com.zh.zhsqApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
登录/登出 controller
使用Subject currentUser = SecurityUtils.getSubject();//获取当前用户信息
能在当前任何方法中都能获取到用户的对象信息。
//登录页面 当用户输入账号密码后 会被拦截 进行认证。
//在 MyRealm 进行认证。会调用UserService进行校验账号密码。
//如果认证成功则会继续下面的操作
//当认证成功后,访问其他功能页面 则会进行授权,授权绑定
//使用注解的形式进授权访问.
@RequestMapping("/common")
@RestController
public class LoginController {
private Logger log = LoggerFactory.getLogger(LoginController.class);
@PostMapping("/login")
public Object login(UserBean userBean) { //前端传递 账号密码进行进来
Map<String, String> errorMsg = new HashMap<>();
Subject currentUser = SecurityUtils.getSubject();//获取当前用户信息
if (!currentUser.isAuthenticated()) {//判断是否完成认证
UsernamePasswordToken token = new UsernamePasswordToken(userBean.getUserName(), userBean.getUserPass());
token.setRememberMe(true);
try {
currentUser.login(token);//通过抛出的异常来判断用户登录结果
//存入session中
currentUser.getSession().setAttribute("currentUser", currentUser.getPrincipal());//存入对象
return "login Succeed";
} catch (UnknownAccountException uae) { //用户不存在
log.info("There is no user with username of " + token.getPrincipal());
errorMsg.put("errorMsg", "用户不存在");
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
errorMsg.put("errorMsg", "密码不正确");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
errorMsg.put("errorMsg", "用户已锁定");
} catch (AuthenticationException ae) {
log.info("登录失败", ae);
errorMsg.put("errorMsg", "登录失败");
}
}
return errorMsg;
// }else {
//
//
// }
}
@RequestMapping("/getCurrentUser")
public Object getCurrentUser(){ //获取当前用户
Subject currentUser = SecurityUtils.getSubject();
//获取存入session中的数据
Session session = currentUser.getSession();
return session.getAttribute("currentUser");
}
@RequestMapping("/logout") //退出
public void logout(){
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();
}
@RequestMapping("/unauthorized")
public String unauthorized(){
return "未授权";
}
}
认证和授权
授权
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
主体
主体,即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
资源
在应用中用户可以访问的任何东西,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
权限
安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面
查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)
打印文档等等。。。
如上可以看出,权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro 不会去做这件事情,而是由实现人员提供。
Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的),后续部分介绍。
角色
角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
隐式角色:
即直接通过角色来验证用户有没有操作权限,如在应用中 CTO、技术总监、开发工程师可以使用打印机,假设某天不允许开发工程师使用打印机,此时需要从应用中删除相应代码;再如在应用中 CTO、技术总监可以查看用户、查看权限;突然有一天不允许技术总监查看用户、查看权限了,需要在相关代码中把技术总监角色从判断逻辑中删除掉;即粒度是以角色为单位进行访问控制的,粒度较粗;如果进行修改可能造成多处代码修改。
显示角色:
在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合;这样假设哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除即可;无须修改多处代码;即粒度是以资源/实例为单位的;粒度较细。
当用户登录时会进行调用认证中心进行认证。而授权是访问的接口需要权限是才会进行调用。
下面的授权代码不会一直进行授权调用,因为绑定了资源。
import com.tedu.bean.UserBean;
import com.tedu.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRealm extends AuthorizingRealm { //继承 Realm的子类 授权认证
private Logger logger = LoggerFactory.getLogger(MyRealm.class);
@Autowired
private UserService userService;
@Override //授权 需要进行权限判断是时候才会去授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("entered MyRealm doGetAuthorizationInfo method");
//获取当前用户
UserBean user = (UserBean)principalCollection.asList().get(0);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//需要绑定角色、资源
authorizationInfo.addRoles(user.getUserRoles());
authorizationInfo.addStringPermissions(user.getUserPerms());
return authorizationInfo;
}
@Override //认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info("》》》》》entered MyRealm doGetAuthenticationInfo method");
//获取当前用户信息
UsernamePasswordToken userToken = (UsernamePasswordToken) token; //获取的是loginController中 的token
String username = userToken.getUsername();//获取账号
//获取数据库中的用户,进行比对,认证 shiro会帮我们比对
UserBean userBean = userService.queryUserByName(username);
//如果没有查到,表示没有这个用户
if (null == userBean){
return null;//后面处理的过程中 会抛出 UnknownAccountException异常
}
//加盐 一般可以使用账号 或者指定
ByteSource salt = ByteSource.Util.bytes("salt");
//完成认证流程 讲对象信息传递过去 中间是加盐,不加可以直接去掉
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userBean,userBean.getUserPass(),salt,"myRealm");
return simpleAuthenticationInfo;
}
}
ShiroConfig shiro必要的配置
在配置中进行配置
尤其是请求过滤器中进行配置让代码更简单。
可以设置跳转的接口路径页面等,必要要进行校验的路径等。功能和springmvc中的拦截器非常相似。
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AllSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.HashMap;
@Configuration //使用Shiro必要的配置
public class ShiroConfig {
// @Bean //代表系统资源 因为前面已经注入spring了 所以进行注掉
// public Realm myRealm(){
// return new MyRealm();
// }
//1. SecurityManager 流程控制
@Bean //账号 手机号引入
public DefaultWebSecurityManager mygetSecurityManager(AuthorizingRealm myRealm,AuthorizingRealm mobileRealm){ //引用了realm对象
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//进行加密 使用MD5的方式
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("MD5");//对应hash接口的实现类
//设置密码进行迭代的次数 更加安全 密码加盐:可以使用账号和密码拼接 然后生成md5 更安全
matcher.setHashIterations(3);
myRealm.setCredentialsMatcher(matcher); //加密后 再跟数据库中的密文进行比对
// securityManager.setRealm(myRealm);
securityManager.setRealms(Arrays.asList(myRealm,mobileRealm));//可以通过账号 手机号进行认证
//进行定义认证策略
/**
* AllSuccessfulStrategy() 需要全部Realm认证成功,才能最终认证成功
* AtLeastOneSuccessfulStrategy() 至少有一个认证成功
* FirstSuccessfulStrategy() 第一个认证成功后即返回认证成功,后面不再进行认证
*/
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());//所有认证都必须通过
authenticator.setRealms(Arrays.asList(myRealm,mobileRealm));
securityManager.setAuthenticator(authenticator);
return securityManager;
}
//2. ShiroFilterFactoryBean 请求过滤器
@Bean //此处引用了上面的方法 通过spring @Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager mygetSecurityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(mygetSecurityManager);
//配置路径过滤器
HashMap<String,String> filterMap = new HashMap<>();
//key是ant路径,value配置shiro的默认过滤器
//shiro的默认过滤器,配置DefaultFilter 中的key
//auth, authc , perms , roles
//表示两个路径 都需要登录才可以访问
// filterMap.put("/mobile/**","authc");
// filterMap.put("/salary/**","authc");
//因为每次controller都要进行权限校验,所以简单点 可以在过滤器中进行校验。会跳转到未授权页面
// filterMap.put("/salary/**","authc,perms[mobile]");
// filterMap.put("/mobile/**","authc,perms[salary]"); 后面使用了注解的形式配置权限
//退出 也可以在此进行。另一个接口就可以不使用了
// filterMap.put("/common/logout","logout");
factoryBean.setFilterChainDefinitionMap(filterMap);//进行启用路径设置过滤器
factoryBean.setLoginUrl("/index.html"); //登录页
// factoryBean.getSuccessUrl(); //登录成功页
factoryBean.setUnauthorizedUrl("/common/unauthorized");//未经授权页
return factoryBean;
}
}
创建用户类,验证账号service.
用户User。账号,密码,权限资源等信息
public class UserBean {
private String userName;//账号
private String userPass;//密码
private String userMobile;//手机号 邮箱
private List<String> userRoles;//角色
private List<String> userPerms;//权限
public String getUserName() {
return userName;
}
public UserBean(){
}
public UserBean(String userName, String userPass, List<String> userRoles, List<String> userPerms) {
this.userName = userName;
this.userPass = userPass;
this.userRoles = userRoles;
this.userPerms = userPerms;
}
public UserBean(String userName, String userPass,String userMobile, List<String> userRoles, List<String> userPerms) {
this.userName = userName;
this.userPass = userPass;
this.userMobile=userMobile;
this.userRoles = userRoles;
this.userPerms = userPerms;
}
public String getUserPass() {
return userPass;
}
public List<String> getUserRoles() {
return userRoles;
}
public void setUserMobile(String userMobile){ this.userMobile=userMobile;}
public List<String> getUserPerms() {
return userPerms;
}
public String getUserMobile() {
return userMobile;
}
}
模拟数据库中的数据
@Component
public class TestDate {
//模拟数据库中的数据
private List<UserBean> allUsers;
public List<UserBean> getAllUsers(){
if (null == allUsers){
allUsers = new ArrayList<>();
allUsers.add(new UserBean("admin","admin","13888888888", Arrays.asList("admin"),Arrays.asList("mobile","salary")));
allUsers.add(new UserBean("manager","manager", "13888888888",Arrays.asList("manager"),Arrays.asList("salary")));
allUsers.add(new UserBean("worker","worker","13888888888", Arrays.asList("worker"),Arrays.asList("")));
}
return allUsers;
}
}
验证账号密码。手机号,账号两个。用户可以通过手机号或者自己的账号登录。
import com.tedu.bean.UserBean;
import com.tedu.util.TestDate;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserService { //得到用户账号 进行验证
@Autowired
private TestDate testDate;
//根据用户名查找数据库中的用户
public UserBean queryUserByName(String username){
UserBean userBean = new UserBean();
List<UserBean> queryUsers = testDate.getAllUsers().stream()
.filter(user -> username.equals(user.getUserName()))//进行过滤 条件是名字相等
.collect(Collectors.toList());
//进行判断 查到数据
if (null == queryUsers && queryUsers.size()>0){
try {
userBean = (UserBean) BeanUtils.cloneBean(queryUsers.get(0));
return userBean;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
//手机号查找用户信息
public UserBean queryUserByMobile(String userMobile) {
UserBean userBean = new UserBean();
List<UserBean> queryUsers = testDate.getAllUsers().stream()
.filter(user -> userMobile.equals(user.getUserMobile()))//进行过滤 条件是名字相等
.collect(Collectors.toList());
//进行判断 查到数据
if (null == queryUsers && queryUsers.size()>0){
try {
userBean = (UserBean) BeanUtils.cloneBean(queryUsers.get(0));
return userBean;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
使用注解的形式设置接口的权限
MobileController
调用下面的接口时就会去验证权限是否匹配。
import org.apache.shiro.authz.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController //资源访问
@RequestMapping("/mobile")
public class MobileController {
//第三种权限方式(方便),第二种是在配置中进行(冗余),第一种就行每个接口进行校验非常麻烦
// @RequiresAuthentication 需要完成用户登录
// @RequiresGuest 未登录用户可以访问,登录后就不能访问
// @RequiresPermissions("mobile") 需要有对应资源权限
// @RequiresRoles("") 需要有对应的角色
// @RequiresUser 需要完成用户登录并且完成了记住我的功能
@RequiresPermissions("mobile") //此处不需要登录的注解 因为没登录就不会获取这些数据信息
@RequestMapping("/query")
public String query(){
//
// Subject currentUser = SecurityUtils.getSubject();
// if (currentUser.isPermitted("mobile")){ //进行用户判断是否有当前的资源
// return "mobile";
// }
// return "error";
return "mobile";
}
}
SalaryController
@RequestMapping("/salary")
@RestController
public class SalaryController {
@RequiresPermissions("salary")
@RequestMapping("/query")
public String query(){
// Subject currentUser = SecurityUtils.getSubject();
// if (currentUser.isPermitted("salary")){ //进行用户判断是否有当前的资源
// return "salary";
// }
// return "error";
return "salary";
}
}
访问后没有权限,会直接跳转到 未经授权页面
异常配置
/**
* spring 提供的 异常处理配置
* 提供对应的异常类型即可
*/
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(AuthorizationException.class)
public Object shiroHandler(){
return "请先获取对应的资源,再进行访问";
}
}
加密加盐
在用户进行注册是就应该对密码进行加密加盐。所以比对时也要对用户输入的密码进行加密加盐
流程控制中进行配置加密加盐验证
认证中进行的加密加盐操作
对密码进行加密加盐的方法。存入数据库时进行操作。
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
public class PasswordEncoder {
//密码加密 md5格式
public static String encoder(String password){
SimpleHash simpleHash = new SimpleHash("MD5", ByteSource.Util.bytes(password), ByteSource.Util.bytes("salt"), 3);
System.out.println(simpleHash);
return simpleHash.toString();
}
}
添加手机号认证
当用户的账号不限于一种时可以使用多个。就需要添加对应的认证
手机号认证
import com.tedu.bean.UserBean;
import com.tedu.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
@Configuration//客户可以通过手机号 进行认证 会自动寻找认证中心,有多少个找多少个。配置认证策略即可
public class MobileRealm extends AuthenticatingRealm {
private Logger log = LoggerFactory.getLogger(MobileRealm.class);
@Autowired
private UserService userService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
log.info("》》》》》entered MyRealm doGetAuthenticationInfo method");
//获取当前用户信息
UsernamePasswordToken userToken = (UsernamePasswordToken) token; //获取的是loginController中 的token
String username = userToken.getUsername();//获取账号
//获取数据库中的用户,进行比对,认证 shiro会帮我们比对
UserBean userBean = userService.queryUserByMobile(username);//手机号认证
//如果没有查到,表示没有这个用户
if (null == userBean){
return null;//后面处理的过程中 会抛出 UnknownAccountException异常
}
//加盐 一般可以使用账号 或者指定
ByteSource salt = ByteSource.Util.bytes("salt");
//完成认证流程 讲对象信息传递过去 中间是加盐,不加可以直接去掉
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userBean,userBean.getUserPass(),salt,"myRealm");
return simpleAuthenticationInfo;
}
}
认证策略
因为账号有多种,不可能让用户都需要进行认证,所以就定义认证策略。
* AllSuccessfulStrategy() 需要全部Realm认证成功,才能最终认证成功
* AtLeastOneSuccessfulStrategy() 至少有一个认证成功
* FirstSuccessfulStrategy() 第一个认证成功后即返回认证成功,后面不再进行认证
在 shiroconfig 流程控制中进行配置策略