Spring Boot整合Shiro进行权限认证管理
很多项目进行权限验证的时候,往往面临技术选型的难题。是选择Spring Security,还是选择Apache Shiro。Shiro同Spring Security相比要简单很多,更加轻量级一些。Shiro可以进行权限身份认证、CAS单点登录、OAuth 2.0授权等功能,笔者最近一周仔细研究了这个框架,分享一下。
一、Shiro简介
Shiro的基本功能点如图:
Authentication用来进行身份认证、登录,验证用户是不是拥有相应的身份。
Authorization用来进行权限验证,进行验证已经身份认证的用户是否具有某个权限,验证该用户对于某个资源是否具有相关权限。
Session Manager用来进行会话管理,如果用户没有退出,则一直保持会话,可以用来做单点登录。
Cryptography用来进行对数据加密,例如对密码加密存储数据库。
Web Support用来支持Web模块。
Caching用来提供缓存功能。
Concurrency表明Shiro可以支持多线程、高并发操作。
Testing提供对测试的支持。
Run As一个用户以其他用户的身份进行访问。
Remember Me提供记住用户的功能,无需再次登录,就像Cookie一样。
二、Shiro主体架构
Shiro从应用程序的角度看架构如图:
应用代码与外界直接交互的对象是Subject,Shiro的核心也就是Subject,分析下架构图每个API的含义:
Subject表示主体,代表了与当前应用交互的对象,所有的Subject都绑定到SecurityManager;Subject只是一个入口,SecurityManager才是实际的执行者;
SecurityManager是安全管理器,所有跟安全相关的操作都会与SecurityManager交互,管理着所有的Subject;SecurityManager是Shiro的核心,与所有的其他组件进行交互,可以类比于Spring MVC中的DispatcherServlet;
Realm表示域,提供对Shiro中的安全数据(如用户、角色、权限),Realm为SecurityManager的用户身份认证提供用户比较;Realm也可以提供用户相应的角色/权限来验证用户能否进行操作;Realm可以比作安全数据源DataSource,对于我们而言:
1.应用代码通过Subject进行认证和授权,Subject又委托给SecurityManager;
2.我们需要为Shiro的SecurityManager注入Realm,从而让SecurityManager得到合法的用户及其权限进行判断。
所以,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
三、Shiro身份验证实例
1.首先准备用户的一些身份/凭据(shiro.ini):
2.测试用例认证功能核心代码:
代码首先通过new IniSecurityManagerFactory 并指定ini 配置文件来创建一个 SecurityManager 工厂;
接着获取SecurityManager并绑定到SecurityUtils;
接着通过SecurityUtils得到Subject,自动绑定到当前线程;如果在Spring MVC中的Web项目里面,在请求结束时需要解除绑定;然后获取用户身份的Token,用户名、密码信息;
调用subject.login进行登录,自动委托给SecurityManager.login方法进行登录;
如果身份验证失败请捕获 AuthenticationException 或其子类,常见的如: DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如 “用户名 / 密码错误” 而不是 “用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
最后通过subject.logout退出,自动委托给SecurityManager.logout方法退出。身份认证步骤:1.收集用户身份凭证,用户名/密码;2.通过Subject.login登录,如果登录失败得到AuthenticationException异常,提示错误信息;否则登录成功;3.调用subject.logout退出认证。身份认证流程:
首先调用 Subject.login(token) 进行登录,自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;
SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。
Realm:域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。如我们之前的 ini 配置方式将使用 org.apache.shiro.realm.text.IniRealm。自定义 Realm 实现:
然后在ini配置文件指定自定义Realm实现:通过 $myRealm1 来引入之前的 realm 定义。这样就可以使用自定义Realm安全数据源了。
Authenticator 及 AuthenticationStrategy
Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验证核心的入口点:
如果验证成功,将返回 AuthenticationInfo 验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException 实现。
四、上手指南
在Spring Boot工程里面引入以下Maven依赖,来引入Shiro:
引入的关键点是shiro-spring坐标,通过为Spring Boot工程加入Shiro功能。
application.yml配置文件配置如下:
JPA配置:
Thymeleaf来加入前端页面:
- index.html :首页
- login.html :登录页
- userInfo.html : 用户信息页面
- userInfoAdd.html :添加用户页面
- userInfoDel.html :删除用户页面
- 403.html : 没有权限的页面
除去登录页之外,其他都比较简单,如下:
五、准备数据库文件
数据库表结构为:
用户信息:
用户角色:
用户权限:
权限角色:
用户角色:
插入数据库脚本为:
对应JPA实体类:
用户信息UserInfo:
角色信息SysRole:
权限信息SysPermission:
六、Shiro在Spring Boot项目中的Config配置
配置DataSource:
配置entityManagerFactory,并设置JPA扫描:
Apache Shiro 核心通过 Filter 来实现,就好像 SpringMvc 通过 DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过 URL 规则来进行过滤和权限校验,所以我们需要定义一系列关于 URL 的规则和访问权限。
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map
// 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/static/**", "anon"); //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout");
//:这是一个坑呢,一不小心代码就不好使了; // filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); //未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean;}
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm());
return securityManager;
}
完整的ShiroConfig代码如下:
在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在 Shiro 中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。通常情况下,在 Realm 中会直接从我们的数据源中获取 Shiro 需要的验证信息。可以说,Realm 是专用于安全框架的 DAO. Shiro 的认证过程最终会交由 Realm 执行,这时会调用 Realm 的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
- 1、检查提交的进行认证的令牌信息
- 2、根据令牌信息从数据源(通常为数据库)中获取用户信息
- 3、对用户信息进行匹配验证。
- 4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
- 5、验证失败则抛出AuthenticationException异常信息。
而在我们的应用程序中要做的就是自定义一个 Realm 类,继承AuthorizingRealm 抽象类,重载 doGetAuthenticationInfo(),重写获取用户信息的方法。
MyShiroRealm完整代码如下:
身份认证主要方法:
七、业务实现
用户信息DAO层:
用户信息Service层:
用户信息Controller层:
用户登录实现核心代码:
@RequestMapping("/login")
public String login(HttpServletRequest request, Map
throws Exception{
System.out.println("HomeController.login()"); // 登录失败从request中获取shiro处理的异常信息。
// shiroLoginFailure:就是shiro异常类的全类名.
String exception = (String) request.getAttribute("shiroLoginFailure");
System.out.println("exception=" + exception);
String msg = "";
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) { System.out.println("UnknownAccountException -- > 账号不存在:");
msg = "UnknownAccountException -- > 账号不存在:"; }
else if (IncorrectCredentialsException.class.getName().equals(exception))
{ System.out.println("IncorrectCredentialsException -- > 密码不正确:");
msg = "IncorrectCredentialsException -- > 密码不正确:";
} else if ("kaptchaValidateFailed".equals(exception)) {
System.out.println("kaptchaValidateFailed -- > 验证码错误");
msg = "kaptchaValidateFailed -- > 验证码错误";
} else
{ msg = "else >> "+exception;
System.out.println("else -- >" + exception);
}
}
map.put("msg", msg); // 此方法不处理登录成功,由shiro进行处理
return "/login";
}
HomeController完整代码如下:
八、测试
1、编写好后就可以启动程序,访问http://localhost:8480/userInfo/userList页面,由于没有登录就会跳转到http://localhost:8480/login页面
。登录之后就会跳转到 index 页面,登录后,直接在浏览器中输入http://localhost:8480/userInfo/userList访问就会看到用户信息。上面这些操作时候触发MyShiroRealm.doGetAuthenticationInfo()这个方法,也就是登录认证的方法。
2、登录admin账户,访问:http://localhost:8480/userInfo/userAdd显示用户添加界面,访问http://localhost:8480/userInfo/userDel显示403没有权限。上面这些操作时候触发MyShiroRealm.doGetAuthorizationInfo()这个方面,也就是权限校验的方法。
3、修改 admin不 同的权限进行测试
Shiro处理权限验证确实很强大,今天只分享了冰山一角。
本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.sosokankan.com/article/1990139.html