这篇博客中没有什么高深的技术,只是一个简单的Shiro框架的搭建...因为之前一直没找到正确的搭建方法、或是因为报错搭建失败...所以我写下了这篇。希望可以给有需要的人帮助。
我用的编辑工具是Idea。
首先,我们从三个方面来认识Shiro。
1:什么是Shiro?
Shiro是Apache旗下的一个开源的安全框架,我认为他的主要功能就是2点:认证、授权。
认证:从字面意思上来看也就是那样,认证、辨别你的身份。
授权:也从字面意思上开看的话是授予、赋予权限。
(- -...我感觉我在说废话...)
2:Shiro有什么用?
上面也说了,主要是为了认证、授权,当然,仅仅是个人初步的理解(挠头)...欢迎指教。
3:Shiro的组件?
虽然百度一下就有一大堆,但是我还是写出来吧...至少我又写了一遍,加深了一点印象...
Subject:主体,可以把它看做是任何可以与应用交互的对象;
SecurityManager:安全管理器,是Shiro的核心,管理所有的Subject;
Authenticator:认证器,负责主体的认证;
Authrizer:授权器,可以给认证完的主体赋予所拥有的相应的权限;
Realm:数据源,使用期间认证和授权需要从这里获取数据;
SessionManager:Shiro自己抽象出来的一个Session,与Tomcat自带是不一样的,用来管理应用和主体之间交互的数据;
SessionDAO:用来做(Shiro的)Session的增删改查(CRUD)。
CacheManager:缓存管理器,可以用来存储用户的角色、权限等信息。
Cryptography:密码模块,Shiro自己提供了一些加密码组件;
(好吧,我承认这一段是简述了 开涛的博客-跟我学Shiro ,嘿嘿嘿...)
4:Shiro框架的简单搭建。
好了,现在要开始主体了!
第一步:
导入相关的Shiro的Jar包,我是导了这五个包:
<!-- shiro框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.2.3</version>
</dependency>
第二步:
a:创建一个spring-shiro.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- web.xml中shiro的filter对应的bean -->
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--注入安全管理器-->
<property name="securityManager" ref="securityManager" />
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login/login.do" />
<!--shiro登录成功后自己跳转的网页-->
<property name="successUrl" value="/homePage.jsp" />
<!-- 自定义filter配置 自定义认证和授权功能 -->
<property name="filters">
<map>
<!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中
authc 所有需要权限认证的 都会走此filter
-->
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
<!--anon 为全部放过-->
<!--authc 为全部拦截,注意,这里的authc是上面配置的那个map中的authc-->
<!-- 放过静态资源 -->
/images/** = anon
/js/** = anon
/styles/** = anon
<!-- 登录页面放过 -->
/login.jsp=anon
<!-- 登录方法拦截,最好和上面的loginUrl配置为一致的,不需要写方法 -->
/login/login.do = authc
<!-- 请求 logout.action地址,shiro去清除session,想要注销的话直接访问logout.do这个路径就可以进行注销,不需要去写方法-->
/logout.do = logout
<!--对所有的信息进行拦截,一般配置在最下面-->
/** = authc
</value>
</property>
</bean>
<!-- securityManager安全管理器,引入realm -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="shiroRealm" />
</bean>
<!-- 自己的realm -->
<bean id="shiroRealm" class="com.jk.shiro.ShiroRealm">
</bean>
<!-- 自定义form认证过虑器 -->
<!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
<bean id="formAuthenticationFilter" class="com.jk.filter.formAuthenticationInfoFilter">
<!-- 表单中账号的input名称 -->
<property name="usernameParam" value="username" />
<!-- 表单中密码的input名称 -->
<property name="passwordParam" value="password" />
<!-- 记住我input的名称 -->
<property name="rememberMeParam" value="rememberMe"/>
</bean>
</beans>
b:在web.xml中添加spring-shiro.xml的声明
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring*.xml</param-value>
</context-param>
<!-- shiro的filter -->
<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean-->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
c:创建一个类继承FormAuthenticationFilter,重写里面的onLoginSuccess方法
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,ServletResponse response) throws Exception {
WebUtils.getAndClearSavedRequest(request);//清除原先的地址 shiro权限框架在认证时认证通过之后 就会跳转到上一次访问的路径
//之前有个bug,若上一次请求为注销请求 则会无限死循环 认证成功会立即发送注销请求
WebUtils.redirectToSavedRequest(request, response, "/homePage.jsp");
return false;
}
d:创建一个类继承AuthorizingRealm,实现里面的setName、doGetAuthenticationInfo和doGetAuthorizationInfo方法。
public class ShiroRealm extends AuthorizingRealm {
//使用注解注入service,所以说,要扫描到service层
@Autowired
private LoginService loginService;
//设置setName,下面要用
@Override
public void setName(String name) {
super.setName("shiroRealm");
}//注意,这个括号里写的是你在spring-shiro.xml中的realm的bean中的Id名
/**doGetAuthenticationInfo 这个方法为认证方法*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从token中获取用户名
String username = (String) token.getPrincipal();
//根据用户名去数据控中查询用户
User user = loginService.selUserByUserAccount(username);
//如果用户信息为空则返回找不到账号异常
if(user == null){
throw new UnknownAccountException();
}
//设置盐,也可以不设置。盐通常为登录用户名+时间戳,添加到数据库当中。
String salt = "FaQ";
//一个简单的认证器,如果不设置盐的话就不能添加第三个参数;
// 第一个参数可以放查询出来的对象、也可以放获取的登录用户名,第二个参数为查询出来的密码,第三个参数为盐,第四个参数为上面设置的setName
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserpwd(), ByteSource.Util.bytes(salt),this.getName());
return simpleAuthenticationInfo;
}
/**doGetAuthorizationInfo 这个方法为授权方法*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//获取已经登录的用户的信息
User user = (User) principal.getPrimaryPrincipal();
//根据已经获取的用户的信息查询当前用户所拥有的所有的权限
List<String> permissionList = loginService.getUserPermissionByUserName(user.getUseraccount());
//New一个简单的授权器
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//把查询到的用户的权限的信息放入到授权器当中
simpleAuthorizationInfo.addStringPermissions(permissionList);
return simpleAuthorizationInfo;
}
}
第三步:
emmmmmm,到这里配置文件差不多就配置完了,运行流程的话我简单说两句,并没有太深入只是简单的使用,项目启动加载web.xml,加载spring的各种配置文件,然后进入登录页面,在进入的时候会优先去访问一遍spring-shiro.xml中配置的logunUrl中的地址,在那里面会先通过request.getAttribute()方法获取Shiro的异常类全限定名判断下是否为空,如果为空则继续进入登录页面,如果不为空则去判断查看是用户名、密码或者验证码中的那个错误。
loginUrl的配置:(loginUrl的那个路径就是访问Controller中的指定方法的路径)
@RequestMapping("login")
public String login(HttpServletRequest request) throws Exception {
//如果登录失败从requst中获取认证异常信息,shiroLoginFailure为shiro 的异常类全限定名。该方法不处理认证成功,只有在认证失败的时候才会进入。
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
if(exceptionClassName!=null){
if(UnknownAccountException.class.getName().equals(exceptionClassName)){
throw new CustomException("账号不存在");
}else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){
throw new CustomException("用户名/密码不存在");
}else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("验证码错误");
}else{
throw new Exception();
}
}
return "../../login";
}
在输入账号密码(账号密码的name值分别为:username和password,在spring-shiro.xml中引入了一个自定义form认证过滤器,他继承了shiro的form认证过滤器,在里面配置了username和password,他会转成相应的格式去shiro的认证过滤器里面进行存储数据),点击登录的时候,会验证一下登录路径是否是在spring-shiro.xml中配置的过滤器链中拦截的方法路径,如果不是的话会继续进入loginUrl地址,然后继续返回登录页面。如果路径正确,则会进入realm中,在realm中通过token获取到登录的用户名,然后拿用户名去数据库进行查询,接着判断一下,如果查询返回的对象为空则证明用户名错误,throw new UnknownAccountExcecption()抛出一个用户名错误的异常,如果不为空则继续往下走,新new 一个SimpleAuthenticationInfo对象,在对象中放入查询出来的对象或者用户名,查询出来的密码,盐(加密处理),this.getName(获取上面set的Name),Shiro会比较登录的账号密码和数据库的账号密码是否一致,不一致走loginurl中的路径,一致说明认证通过,走loginUrl下面的successUrl路径,successUrl路径配置的是认证通过后要跳转的页面。
在账号密码认证成功之后会进入realm下面的授权器的方法(doGetAuthorizationInfo),使用形式参数对象的PrincipalCollection对象中的getPrimaryPrincipal方法获取到在认证器里面的SimpleAuthenticationInfo里面的第一个参数,拿该参数去数据库获取该用户所拥有的权限放入一个List<String>的集合当中,然后在new SimpleAuthorizationInfo,使用simpleAuthorizationInfo对象的addStringPermission方法把获取到的权限的集合放进去。然后返回该对象。
上述一切都是Ok的之后会进入spring-shiro.xml中的successUrl所指向的页面,在这个页面可以使用Shiro的各种Jsp标签,当然前提是需要导入Shiro的标签库。
判断该用户是否有权限访问某一个方法的时候需要在方法的上面加入@RequiresPermission("user:add")之类的注解,括号里放的是权限表中的权限字段,有权限则能访问,无权限就报一个异常。
要判断是否可以使用方法的话就需要开启对Shiro注解的支持了,我是在spring-mvc.xml中加入了
<!-- 开启aop,对类代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 开启shiro注解支持 -->
<bean class="
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
还有无权限访问的话可以在加上
<!-- 使用shiro注解 由于源码问题需要自己捕获异常 并且跳转页面
若当前用户无权限 则跳转到无权限页面
-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">exception</prop>
</props>
</property>
</bean>
就可以报异常自动跳到exception这个异常页面了。
Ok,一切大功告成!