SSM整合Shiro框架及简单登录认证使用
一、Shiro 简介
1、Shiro 是什么
Shiro 是一个功能强大和易于使用的Java安全框架。
Shiro 为开发人员提供一个直观而全面的解决方案:认证、授权、加密、会话管理、Web集成、缓存等。
Apache Shiro 和 Spring Security 是同类型的框架,主要用来做安全,也就是我们俗称的权限校验(控制),Shiro 扩展更简单和灵活。
2、Shiro 能干什么
![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDAyODU0_size_16_color_FFFFFF_t_70][]
shiro 四个主要的功能
- Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
- Authorization:授权,即权限验证,判断某个已经认证过的用户是否拥有某些权限访问某些资源,一般授权会有角色授权和权限授权;
- SessionManager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的,web 环境中作用是和 HttpSession 是一样的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
shiro 的其它几个特点
- Web Support:Web支持,可以非常容易的集成到Web环境;
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
- Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
- Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
3、shiro 架构
![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDAyODU0_size_16_color_FFFFFF_t_70 1][]
从图中我们可以看到不管是任何请求都会经过 SecurityManager 拦截并进行相应的处理,shiro 几乎所有的功能都是由 SecurityManager 来管理。
其中:
- Subject:主体,相当与是请求过来的”用户”
- SecurityManager: 是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行拦截并控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理
- Authenticator:认证器,负责主体认证的,即确定用户是否登录成功,我们可以使用 Shiro 提供的方法来认证,有可以自定义去实现,自己判断什么时候算是用户登录成功
- Authrizer:授权器,即权限授权,给 Subject 分配权限,以此很好的控制用户可访问的资源
- Realm:一般我们都需要去实现自己的 Realm ,可以有1个或多个 Realm,即当我们进行登录认证时所获取的安全数据来源(帐号/密码)
- SessionManager:为了可以在不同的环境下使用 session 功能,shiro 实现了自己的 sessionManager ,可以用在非 web 环境下和分布式环境下使用
- SessionDAO:对 session 的 CURD 操作
- CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;
- Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
二、SSM 与 Shiro 的整合(实现登录认证)
SSM整合看我另一篇文章:[https://blog.csdn.net/qq\_42402854/article/details/84331025][https_blog.csdn.net_qq_42402854_article_details_84331025]
1、在 maven 项目中引入 Shiro 依赖:
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 引入ehcache的依赖,给shiro做缓存权限用的 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
2、在 web.xml 中配置 Shiro 的拦截器:
<!-- 配置 shiro 拦截器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3、在 spring.xml 中配置 Shiro 的核心组件和拦截规则:
1)核心组件,并配置缓存和自己实现的Realm
<!-- 配置 shiro 的核心组件:securityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 配置缓存 -->
<property name="cacheManager" ref="cacheManager"/>
<!-- 配置域realm,用户名,密码,角色都保存在域里:实现从数据库中获取用户信息,需要我们自己创建一个类(实现Realm接口) -->
<property name="realm" ref="shiroRealm"/>
</bean>
<!-- 配置ehcache缓存bean,导入ehcache并新建配置文件 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
</bean>
<!-- 配置自己域realm实现 -->
<bean id="shiroRealm" class="cn.jq.ssm.service.shiro.ShiroRealm"></bean>
public class ShiroRealm extends AuthenticatingRealm\{ ... \} **后面6有写**(**Realm 接口 有很多实现类**)
2)Shiro 拦截规则:
<!-- 配置shiro的一些拦截规则,id必须和web.xml中的 shiro 拦截器名一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager" />
<!-- 身份认证失败,则跳转到登录页面的配置 -->
<property name="loginUrl" value="/login" />
<!-- 登录成功后的页面 -->
<property name="successUrl" value="/admin/index" />
<!-- 权限认证失败,则跳转到指定页面 -->
<property name="unauthorizedUrl" value="/unauthorized" /> <!-- 登录后访问没有权限的页面后跳转的页面 -->
<!-- Shiro连接约束配置,即过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
<!-- 注意:规则是有顺序的,从上到下,拦截范围必须是从小到大的 -->
<!-- url = 拦截规则(anon为匿名,authc为要登录后,才能访问,logout登出过滤) -->
/login = anon
/logout = logout
/admin/** = authc
/**= anon
</value>
</property>
</bean>
4、在 springmvc.xml 中配置 Shiro 的注释:
使用 shiro 注解一般在 controller 中
<!-- 配置shiro开启注解支持 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
基本整合 Shiro 的配置可以了,注意之间的 id 名与 ref 引用名要一致
接下来实现 登录认证
5、LoginController 实现 访问控制:
登录 index.jsp 页面,访问 url 可以是 项目根路径(主要是注销是自动返回web根目录),也可以是 /login
在用户登录 login 方法中
1)获取到shiro的认证核心组件Subject接口的对象(这个对象封装了登录用户对象信息),并调用Subjent接口的实现对象的login方法来验证用户名和密码,
2)注意:Subjent接口的实现对象的 login 方法,实质上是将参数 token(含有登录用户对象信息)传到我们自定义的**ShiroRealm** 类中的实现方法里处理
@Controller
public class LoginController {
@GetMapping(value= {"/","/login"})
public String login() {
return "login";
}
@PostMapping("/login")
public String login(User user) {
//使用 shiro 登录验证
//1 认证的核心组件:获取 Subject 对象
Subject subject = SecurityUtils.getSubject();
//2 将登陆表单封装成 token 对象
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPazzword());
try {
//3 让 shiro 框架进行登录验证:
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
return "loginError";
}
return "redirect:/admin/index";
}
@GetMapping("/admin/index")
public String admin(Model model) {
System.out.println("admin");
return "admin/index";
}
}
和 Shiro 拦截规则 的跳转 url 保持一致,jsp页面简单写了几个字
![20190228103721872.png][]
6、自定义 ShiroRealm 实现:
如果实现 Realm 接口,参数 token 会被传递到 supports 方法上
shiro 框架做了一些现成的Realm接口的实现类,比如AuthenticingRealm类等
简单登录认证我们 继承 AuthenticatingRealm 类,参数 token 会被传递到 doGetAuthenticationInfo 方法上
public class ShiroRealm extends AuthenticatingRealm{
@Autowired
private UserMapper userMapper;
/**
* 登录的验证实现方法
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken token2 = (UsernamePasswordToken) token;
String username = token2.getUsername();
User user = userMapper.getUserByUsername(username);
if(user == null) {
throw new UnknownAccountException("用户名或密码有误!");
}
if(user.getStatus() == 0) {
throw new UnknownAccountException("用户名已被禁用,请联系系统管理员!");
}
/**
* principals: 可以使用户名,或d登录用户的对象
* hashedCredentials: 从数据库中获取的密码
* credentialsSalt:密码加密的盐值
* RealmName: 类名(ShiroRealm)
*/
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPazzword(), null, getName());
return info; //框架完成验证
}
}
7、退出登录
简单写了一个超链接, shiro 拦截规则,退出登陆后自动返回 web 根目录( / 访问登录页面5)
<a href="${pageContext.request.contextPath }/logout">注销</a>
三、shiro 登录步骤分析
问题:
1)对于 二中 5、6步 为什么调用 subject.login(token); 会执行自定义 Realm 的实现方法?
2)return info; 框架如何完成验证?
1、问题一:
1)表单提交后进入login方法后 调用 subject.login(token);
![20190301103821902.png][]
2)Subject 是通过 securityManager 调用 login() 方法 来进行登录操作
![20190301104227765.png][]
![20190301104312474.png][]
3) DefaultSecurityManager 类
该方法里会验证登录信息,如果登录通过会返回登录信息,如不通过则抛出异常;authenticate 方法定义在父类AuthenticatingSecurityManager 中;
![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNDAyODU0_size_16_color_FFFFFF_t_70 2][]
4)AuthenticatingSecurityManager 类
5)AbstractAuthenticator 类
6)ModularRealmAuthenticator 类
4、5 的 authenticate 方法 都交给了 这个类的 doAuthenticate 方法执行,
在这里,终于见到 Realm。这个方法就是在判断程序中定义了几个realm,分别都单个realm和多个realm的处理方法。
**我们是单个realm的处理方法 doSingleRealmAuthentication;**
7)ModularRealmAuthenticator 类
红色标记部分,就是真正的调用Reaml来实现登录,点进去
8)AuthenticatingRealm 类
第一个标记是从缓存中查看,该用户是否已经登录。如果已经登录怎么直接完成了登录流程;
第二个标记是根据用户信息,去获取数据库保存的该用户的密码等信息;
第三个标记是利用第二个标记得到的数据库该用户的信息和本次登录该用户的信息经行比较,如果正确则验证通过,返回用户信息。如果不通过则验证失败,登录不成功,抛出错误
2、问题二:
简单点分析:
在 UsernamePasswordToken 类的 方法打断点
在 SimpleCredentialsMatcher 类 打断点:
注意:
shiro进行对比密码的是一个接口:CredentialsMatcher
此接口的实现类:
SimpleCredentialsMatcher:没有对密码进行加密,
HashedCredentialsMatcher: 实现了对密码的加密
返回结果后,如果密码对比正确,跳转到登录成功后的页面,密码对比不正确,抛出异常:IncorrectCredentialsException
到此,整合与简单登录认证结束
参考文章:
https://www.jianshu.com/p/a956006bceee
shiro登录步骤源码分析
还没有评论,来说两句吧...