综述:之前做过一些小项目,都是手动进行安全和权限验证,相当的麻烦,而且权限验证又是实际开发时必不可少的步骤,因此我们可以借助一些框架来实现,今天开始学习Shiro权限框架
一、Shiro简介
Apache Shiro 是 Java 的一个安全框架,Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:
Authentication:
身份认证 / 登录,验证用户是不是拥有相应的身份;
Authorization:
授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:
会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography:
加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:
Web 支持,可以非常容易的集成到 Web 环境;
Caching:
缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
Concurrency:
shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:
提供测试支持;
Run As:
允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:
记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
注意:
Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可。
二、Shiro API
Shiro 的对外 API 核心是 Subject。
Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,可以把它看成SpringMVC中的 DispatcherServlet 前端控制器;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理
Realm:域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
也就是说对于我们而言,最简单的一个 Shiro 应用:
应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。
subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。
securityManager:安全管理器,主体进行认证和授权都 是通过securityManager进行。
authenticator:认证器,主体进行认证最终通过authenticator进行的。
authorizer:授权器,主体进行授权最终通过authorizer进行的。
sessionManager:web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。
SessionDao: 通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
cache Manager:缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
realm:域,领域,相当于数据源,通过realm存取认证、授权相关数据。
cryptography:密码管理,提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
比如md5散列算法。
以上内容转载自:
Shiro基础知识——简介
三、Shiro与Spring整合
1、首先在pom.xml中添加依赖
2、配置web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1" metadata-complete="true">
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mybatis.xml,classpath:spring-mvc.xml,classpath:springshiro.xml</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 设置拦截 !-->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>log4jContextName</param-name>
<param-value>log4jContext</param-value>
</context-param>
<!-- 添加shiro的过滤器代理!-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<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>
</web-app>
3、第三步就是编写springshrio.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
default-lazy-init="true">
<!-- 配置安全管理器securityManager, 缓存技术: 缓存管理 realm:负责获取处理数据 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myShiroRealm" />
<!-- 加入缓存管理 -->
<property name="cacheManager" ref="cacheManager" />
<!-- 加入remember管理 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
<!-- 会话管理 -->
<!--<property name="sessionManager" ref="sessionManager"/>-->
<!--<property name="sessionMode" value="native"/>-->
</bean>
<!-- 缓存管理器 -->
<!--<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">-->
<!-- <!– cache配置文件 –>-->
<!-- <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>-->
<!--</bean>-->
<!-- 会话cookie模板 -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- 写到cookie的name值 -->
<constructor-arg value="sid"/>
<!-- 设置js是否可以访问cookie,true 不能访问 -->
<property name="httpOnly" value="true"/>
<property name="name" value="rememberMe"/>
<!-- 保存时长10分钟,以秒为单位 -->
<property name="maxAge" value="600"/>
</bean>
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- ipherKey是加密rememberMe cookie 的密钥;默认AES算法 -->
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}" />
<!-- 引入上面定义的cookie模板 -->
<property name="cookie" ref="rememberMeCookie" />
</bean>
<!-- 項目自定义的Realm,从数据库中获取用户的安全数据 -->
<bean id="myShiroRealm" class="com.stitp.shiro.UserRealm">
<!-- 配置缓存管理器-->
<property name="cacheManager" ref="cacheManager" />
<!-- 配置加密器 -->
<!--<property name="credentialsMatcher">-->
<!--<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">-->
<!--<property name="hashAlgorithmName" value="MD5"/> <!– 加密算法的名称 –>-->
<!--<property name="hashIterations" value="1024"/> <!– 配置加密的次数 –>-->
<!--</bean>-->
<!--</property>-->
</bean>
<!-- 用户授权信息Cache -->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
<!-- 必须配置lifecycleBeanPostProcessor:管理Shiro中常见的对象 -->
<!-- 保证实现了Shiro内部lifecycle函数的bean执行,同时也把Shiro的说明周期交给spring管理 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!-- shiro的核心配置: 配置shiroFileter id名必须与web.xml中的filtername保持一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 调用我们配置的权限管理器 -->
<property name="securityManager" ref="securityManager" />
<!-- 没有登录重定向到登录页面 -->
<property name="loginUrl" value="/login.jsp" />
<!--登录成功界面-->
<property name="successUrl" value="/success.jsp" />
<!-- 没有权限跳转的页面 -->
<property name="unauthorizedUrl" value="/index.jsp" />
<!-- shiro过滤器的具体配置 -->
<!-- anon 匿名访问,authc-必须要认证以后才能访问的 -->
<!--authc 必须身份认证通过才能访问-->
<!-- user 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查-->
<property name="filterChainDefinitions">
<value>
<!-- 具体配置需要拦截哪些 URL, 以及访问对应的 URL 时使用 Shiro 的什么 Filter 进行拦截. -->
/login.jsp = anon
/register.jsp = anon
/fail.jsp = authc
/success.jsp = user
</value>
</property>
</bean>
</beans>
各个步骤的用处都已经注释到了上面。
4、下面实现自定义Realm,继承AuthorizingRealm
package com.stitp.shiro;
import com.stitp.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.springframework.beans.factory.annotation.Autowired;
public class UserRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return new SimpleAuthorizationInfo();
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
// System.out.println(username);
String password = userService.findByName(username);
System.out.println(password);
if (password == null) {
throw new UnknownAccountException(); //没有找到账号
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
username, //用户名
password, //密码
getName() //realm name
);
return authenticationInfo;
}
}
5、实现Controller
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(
User user,
@RequestParam(value = "rememberme",required = false) boolean remember,
Model model) {
String username = user.getUsername();
String password = user.getPassword();
System.out.println(username);
System.out.println(remember);
String error = null;
if (username != null && password != null) {
//初始化
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
System.out.println(token + "----------");
try {
if(remember)
token.setRememberMe(true); //这两步是记住我功能实现的关键步骤
//登录,即身份校验,由通过Spring注入的UserRealm会自动校验输入的用户名和密码在数据库中是否有对应的值
subject.login(token);
return "/success.jsp";
}catch (Exception e){
e.printStackTrace();
error = "未知错误,错误信息:" + e.getMessage();
}
} else {
error = "请输入用户名和密码";
}
//登录失败,跳转到login页面,这里不做登录成功的处理,由
model.addAttribute("error", error);
return "/fail.jsp";
}
由上图可知,记住我功能可以使用
至此,利用SSM + Shiro实现基本的权限认证和记住我功能简单实现完成。
注意:在刚开始的时候可能会注入不到userService,这是要在web.xml设置
spring-mvc.xml的加载顺序优先于springshiro.xml,具体操作参见:
Shiro解决无法注入Service问题(包括Spring MVC和Spring Boot)
记住我功能实现可参阅:
shiro教程:记住我功能
另外,推荐阅读:
Shiro系列相关文章(一)
Shiro系列相关文章(二)