文章目录
- 一、Shiro简历及组成
- 二、Shiro过滤器&标签
- 三、Shiro环境搭建
- 1.添加shiro依赖
- 2.配置web.xml
- 3.创建自定义realm类:AuthRealm,继承AuthorizingRealm
- 4.配置applicationContext-shiro.xml, Spring整合shiro
- 四、Shiro登陆认证(一)认证实现
- 1.修改LoginController,通过shiro实现登陆认证
- 2.编写AuthRealm,实现登陆认证
- 五、Shiro登陆认证(二)自定义凭证匹配器实现认证
- 自定义凭证匹配器
- 六、Shiro授权
- 1. shiro授权校验有几种方式
- 2.realm实现授权
提示:以下是本篇文章正文内容,下面案例可供参考
一、Shiro简历及组成
Apache Shiro是Java的一个安全框架。功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。
shiro的组成:
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情。
常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
subject.login(token); 认证
subject.checkPermission("指定访问资源需要的权限") 授权
**Session Manager:**会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
**Cryptography:**加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
我们从外部来看Shiro吧,即从应用程序角度的来观察如何使用Shiro完成工作:
可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
也就是说对于我们而言,最简单的一个Shiro应用:
1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
二、Shiro过滤器&标签
过滤器
过滤器简称 | 对应的java类 |
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter |
标签
标签名称 | 标签条件(满足条件,才显示标签内容) |
<shiro:authenticated > | 登录之后 |
<shiro:notAuthenticated > | 不在登录状态时 |
<shiro:guest > | 用户在没有RememberMe时 |
<shiro:user > | 用户在RememberMe时 |
<shiro:hasAnyRoles name=“abc,123” > | 在有abc或者123角色时 |
<shiro:hasRole name=“abc”> | 拥有角色abc |
<shiro:lacksRole name=“abc”> | 没有角色abc |
<shiro:hasPermission name=“abc”> | 拥有权限资源abc |
<shiro:lacksPermission name=“abc”> | 没有abc权限资源 |
<shiro:principal > | 默认显示用户名称 |
三、Shiro环境搭建
搭建shiro环境并实现认证,步骤如下:
- 添加shiro依赖
- 配置web.xml
- 创建自定义realm类:AuthRealm,继承AuthorizingRealm
- 配置applicationContext-shiro.xml, Spring整合shiro
- 修改LoginController,通过shiro实现登陆认证
- 实现自定义realm类AuthRealm中的认证方法
- 登陆认证测试
1.添加shiro依赖
<!--shiro和spring整合-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--shiro核心包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
2.配置web.xml
<!--配置shiro的代理过滤器,拦截到请求后,把认证授权的处理交给容器中指定名称的bean-->
<!--注意: 这里的filter-name 名称要匹配容器中的bean的名称-->
<!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
<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类:AuthRealm,继承AuthorizingRealm
/**
* 自定义的realm类,提供认证、授权数据访问的方法
*/
public class AuthRealm extends AuthorizingRealm{
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
return null;
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
4.配置applicationContext-shiro.xml, Spring整合shiro
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">
<!--1. 创建过滤器的工厂,注入SecurityManager。注意:这里的id匹配web.xml的过滤器名称。-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--1.1 注入SecurityManager -->
<property name="securityManager" ref="securityManager"/>
<!--1.2 注入其他参数-->
<!--1.2.1 认证失败后,默认跳转到的登陆页面-->
<property name="loginUrl" value="/login.jsp"/>
<!--1.2.2 认证成功后,默认跳转到的登陆页面(如果程序有指定,以程序指定为主)-->
<property name="successUrl" value="/index.jsp"/>
<!--1.2.3 授权校验失败,默认跳转的页面-->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!--
1.3 指定过滤器配置
anon 匿名访问过滤器,指定需要放行的资源,这些资源不需要认证可以直接访问
authc 认证过滤器,指定需要认证的资源
perms 授权校验过滤器,指定需要进行权限校验的资源
语法:
/css/* 拦截css目录下的所有资源,不包含子目录
/css/** 拦截css目录及其子目录的资源,包含子目录
/login* 拦截login开头的请求路径
-->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/img/** = anon
/make/** = anon
/plugins/** = anon
/login.jsp = anon
/login.do = anon
/index.jsp = anon
/** = authc
</value>
</property>
</bean>
<!--2. 创建SecurityManager对象,注入realm-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
<!--3. 创建自定义的realm,访问我们的认证、授权数据; 注入凭证匹配器-->
<bean id="myRealm" class="cn.test.web.shiro.AuthRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!--4. 创建凭证匹配器,自动对用户输入的密码按照指定的算法加密-->
<bean id="credentialsMatcher"
class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
</bean>
</beans>
四、Shiro登陆认证(一)认证实现
步骤
- 修改LoginController,通过shiro实现登陆认证
- 编写AuthRealm,实现登陆认证
- 测试
1.修改LoginController,通过shiro实现登陆认证
@Controller
public class LoginController extends BaseController {
@Autowired
private UserService userService;
@Autowired
private ModuleService moduleService;
/**
* shiro登陆认证
*/
@RequestMapping("/login")
public String login(String email,String password){
if (StringUtils.isEmpty(email) || StringUtils.isEmpty(password)){
return "forward:/login.jsp";
}
try {
// 创建shiro的subject对象,表示当前的用户
Subject subject = SecurityUtils.getSubject();
// 创建token对象,封装账号密码信息
AuthenticationToken token = new UsernamePasswordToken(email,password);
// 登陆认证 (自动去到ream的认证方法)
subject.login(token);
// 认证成功
// 获取认证后的身份对象(获取的是realm的认证方法返回的对象的构造函数的第一个参数)
User user = (User) subject.getPrincipal();
httpSession.setAttribute("loginUser",user);
// 登陆成功,查询用户的权限
List<Module> moduleList = moduleService.findModuleByUserId(user.getId());
httpSession.setAttribute("moduleList",moduleList);
// 登陆成功,跳转到主页
return "home/main";
} catch (AuthenticationException e) {
e.printStackTrace();
// 认证失败
request.setAttribute("error","用户名或密码错误!");
return "forward:/login.jsp";
}
}
@RequestMapping("/home")
public String home(){
return "home/home";
}
// 注销
@RequestMapping("/logout")
public String logout(){
// 释放资源
httpSession.removeAttribute("loginUser");
// 销毁服务器端的session
httpSession.invalidate();
// 注销后,跳转到登陆页面
return "forward:/login.jsp";
}
}
2.编写AuthRealm,实现登陆认证
/**
* 自定义的realm类,提供认证、授权数据访问的方法
*/
public class AuthRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 转换
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 获取登陆名
String email = upToken.getUsername();
// 根据email查询
User user = userService.findByEmail(email);
if (user == null) {
return null;
}
// 获取数据库中正确的密码
String dbPwd = user.getPassword();
// 参数1:身份对象(从数据库中查询到的user对象)
// 参数2:数据库中正确的密码
// 参数3:realm的名称; 可以随便写,但是有多个realm要唯一。getName()获取默认名称
SimpleAuthenticationInfo sai =
new SimpleAuthenticationInfo(user,dbPwd,getName());
return sai;
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
五、Shiro登陆认证(二)自定义凭证匹配器实现认证
目前对用户输入的密码,我们直接按照md5加密。这样不安全,要对用户输入的密码加盐加密,确保安全性
最终使用:md5加密、加盐的方式
自定义凭证匹配器
/**
* 自定义凭证匹配器,自己指定对用户输入的密码的加密方式
* 加密方案: md5加密,用户名作为盐。
*/
public class CusomtCredentialsMatcher extends SimpleCredentialsMatcher {
/**
* 在凭证匹配器中,指定对用户输入的密码的加密方式,以及最终的校验
* @param token 封装用户输入的密码、账号
* @param info 封装认证后的身份对象 (拿到的是user对象,也就是数据库中正确的密码)
* @return
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//1. 获取用户输入的email
String email = (String) token.getPrincipal();
//2. 获取用户输入的密码
String inputPwd = new String((char[]) token.getCredentials());
//3. 对用户输入的密码加密、加盐
String encodePwd = new Md5Hash(inputPwd,email).toString();
//4. 获取数据库中正确的密码(数据库的密码要加密、加盐。 注意:要先修改数据库再测试)
String dbPwd = (String) info.getCredentials();
//5. 对比:用户输入的密码加密加盐后,与数据库的密码进行对比
return encodePwd.equals(dbPwd);
}
}
applicationContext-shiro.xml配置自定义匹配器
<!--自定义的凭证匹配器,指定密码加密、加盐 -->
<bean id="credentialsMatcher" class="cn.test.web.shiro.CusomtCredentialsMatcher"></bean>
六、Shiro授权
1. shiro授权校验有几种方式
shiro提供了四种方式实现权限校验:
1) 硬编码方式
Subject subject = SecurityUtils.getSubject();
subject.checkPermission("部门管理");
2) 过滤器配置方式
/system/user/list.do = perms["用户管理"]
3) 注解方式
@RequiresPermissions(“”)
4) shiro提供的标签
<shiro:hasPermission name="用户管理">
<a href="#">用户管理</a>
</shiro:hasPermission>
2.realm实现授权
实现自定义realm的doGetAuthorizationInfo()方法,返回用户已经具有的权限:
/**
* 自定义的realm类,提供认证、授权数据访问的方法
*/
public class AuthRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
@Autowired
private ModuleService moduleService;
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 转换
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 获取登陆名
String email = upToken.getUsername();
// 根据email查询
User user = userService.findByEmail(email);
if (user == null) {
return null; // UnknownAccountException
}
// 获取数据库中正确的密码
String dbPwd = user.getPassword();
// 参数1:身份对象(从数据库中查询到的user对象)
// 参数2:数据库中正确的密码
// 参数3:realm的名称; 可以随便写,但是有多个realm要唯一。getName()获取默认名称
SimpleAuthenticationInfo sai =
new SimpleAuthenticationInfo(user,dbPwd,getName());
return sai;
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
/* 查询登陆用户的权限*/
//1. 获取用户的身份对象(认证方法返回对象的构造函数的第一个参数)
User user = (User) principals.getPrimaryPrincipal();
//2. 根据用户的id查询权限
List<Module> moduleList = moduleService.findModuleByUserId(user.getId());
//3. 返回,告诉shiro当前登陆用户拥有的权限。后面就可以进行权限校验(四种方式)
SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo();
if (moduleList != null && moduleList.size() > 0){
for (Module module : moduleList) {
// 返回用户的权限
sai.addStringPermission(module.getName());
}
}
return sai;
}
}