概念
Shiro是apache基金会旗下的一款安全框架,其中包括登陆认证/权限控制/session管理/加密处理.这四大模块。
-
Authentication登录认证
当用户访问时必须登录。这时,shiro会进行验证通过。
-
Authorization权限认证
不同用户登录系统时,由于权限不同,所看到的功能不同。
-
SessionMangement
由shiro框架对javax.servlet中的session进行了再次包装。
-
Cryptography加密处理
包含了加密算法(内部匹配规则)与工具类。
Shiro内部流程调用关系
- ApplicationCode
任何形式的请求(线程/机器/用户请求) - Subject
是shiro安全中心对外暴露的唯一入口。当一个请求到达时都会经过Subject才能到达shiro内部进行校验,所以每个请求都相当于一个”user”来进行处理, - ShiroSecurityManager
核心部分。Shiro内部有自己的校验规则,不需要程序员干预。 - Realm
通过Realm查询用户的真实信息,然后返回给安全中心内部,进行自动校验。但是程序员需要准备Realm原材料.
搭建步骤
- 导入依赖的jar包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.8.3</version>
</dependency>
- 编辑配置文件–spring整合shiro框架
- applicationContext-shiro.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 将shiro的四大组件的生命周期 交给 spring框架管理 -->
<bean id="lifeCycleBeanProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
<!-- 创建shiro的代理对象
代理:在完成本职工作的同时,添加额外的操作
额外的操作:原材料realm的准备
加密模块的匹配规则(MD5算法)
权限的查询:根据当前登录的用户去查询数据库的权限模块
动态代理的两种模式:jdk的动态代理/ cglib的动态代理
-->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifeCycleBeanProcessor">
<property name="proxyTargetClass" value="true"></property>
</bean>
<!-- 强制制定使用 cglib的代理模式
proxy-target-class="true"
-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--引用 程序员编写的realm的原材料 -->
<property name="realm" ref="AuthRealm"></property>
</bean>
<!-- 自己编写的处理原材料的程序 -->
<bean id="AuthRealm" class="cn.tarena.ht.shiro.AuthRealm">
<!-- 自定义的加密算法 :引用bean标签
name="credentialsMatch" 与
authCredential类的方法名对应
-->
<property name="credentialsMatcher" ref="authCredential"></property>
</bean>
<!-- 自定义的加密算法 -->
<bean id="authCredential"
class="cn.tarena.ht.shiro.AuthCredential"></bean>
<!-- 权限认证交给 shiro的安全中心管理 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager"
ref="securityManager"></property>
</bean>
<!-- shiro的过滤器
所有的请求 都需要经过过滤器
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 引入shiro的安全管理中心 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 指定登录的地址 当用户没有登录时,默认跳转的页面 -->
<property name="loginUrl" value="/index.jsp"></property>
<!-- 过滤链:values标签中配置的所有请求 -->
<property name="filterChainDefinitions">
<value>
<!--
请求:
静态的配置文件 放行 不拦截
动态的客户端的请求 拦截大部分请求,但是登录请求要拦截吗?
-->
<!--
添加过滤信息
1.anon 表示放行
2.authc 表示拦截
-->
/tologin.action = anon
/logout.action = anon
/login.action = anon
/staticfile/** = anon
<!-- /**拦截所有的请求和静态资源文件 -->
/** = authc
</value>
</property>
</bean>
</beans>
- web容器管理shiro过滤链–web.xml
<!-- shiro安全框架的过滤器交给web容器
filter-name必须要和配置文件中bean的id一致
-->
<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.自定义Realm
- 自定义过滤器(在pom.xml中已配置)
- 将过滤器交给web容器进行管理(在web.xml中已添加过滤器)
Shiro安全认证步骤
- Shiro安全管理进行登录认证(Subject:在LoginController类中)
@RequestMapping(value="/login")
public String toLogin(String username,String password,Model model){
//验证用户名密码是否为空
if(StringUtils.isEmpty(username)||StringUtils.isEmpty(password)){
model.addAttribute("errorInfo", "用户名或密码不能为空");
return "/sysadmin/login/login";
}
//验证用户名密码是否正确--使用shiro提供的subject类
//这个subject时所有请求对应的统一对象
Subject subject = SecurityUtils.getSubject();//有安全管理中心创建对象
//使用令牌进行登录--AuthenticationToken
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//如果令牌错误不能正常登录
try {
subject.login(token);
//获取真实的对象
User user = (User) subject.getPrincipal();
//放入shiro提供的session中
subject.getSession().setAttribute("sessionUser", user);
return "redirect:/home";
} catch (Exception e) {//令牌出现错误,打印异常信息,并提示登录错误,返回登录页面
e.printStackTrace();
model.addAttribute("errorInfo", "用户名或密码不正确");
return "/sysadmin/login/login";
}
}
- 登录认证—自定义的Realm
public class AuthRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
//权限认证方法:查询当前用户对应的模块权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
// TODO Auto-generated method stub
return null;
}
//用户认证:1.拿到用户名密码 2.用户名密码进行加密
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户名和密码的令牌
UsernamePasswordToken loginToken = (UsernamePasswordToken) token;
// 通过令牌对象获取 前端页面输入的 用户名信息
String username = loginToken.getUsername();
// 根据username,去数据库中查询当前用户是否存在
// 程序员手动准备的原材料信息
/* sql语句:查询用户的信息的同时获取 部门信息
* mapper层
* service层
*/
User user = userService.findUserByUsername(username);
//返回值类型为:AuthenticationInfo
// 结果:只是根据用户名去查询了 数据库
/* principal : 表示真实的用户 user
* credentials : 表示 user对象的 密码
* realmName : 原材料的类名 string字符串 this.getName()
*/
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
return info;
}
}
- shiro内部密码规则校验
//shiro安全中心根据密码,进行内部规则校验
public class AuthCredential extends SimpleCredentialsMatcher{
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//获取用户名密码的令牌
UsernamePasswordToken loginToken = (UsernamePasswordToken) token;
//获取用户名密码
String username = loginToken.getUsername();
String password = String.valueOf(loginToken.getPassword());
//进行MD5加密
String md5passsword = MD5Password.getMd5HashPassword(username, password);
loginToken.setPassword(password.toCharArray());
//调用父类的内部校验规则:令牌和登录认证的原材料info信息
return super.doCredentialsMatch(token, info);
}
}
- 权限控制
- 在jsp页面中引入shiro标签库
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
- 管理相应的模块标题
<shiro:hasPermission name="部门管理">
<li><a href="${ctx}/sysadmin/dept/list" onclick="linkHighlighted(this)" target="main" id="aa_1">部门管理</a></li>
</shiro:hasPermission>
- 自定义的Realm中进行权限认证
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
String userId = user.getUserId();
//根据userId查询模块信息
List<String> pList = userService.findModuleByUserId(userId);
//创建权限控制对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(pList);
return info;
}
- 根据userId查询模块信息–在userMapper.xml中编写sql语句