写在最最最~~~前面的:由于Shiro框架在学习过程中假如没有一个实例Demo的参考,理解起来可能较为生涩难懂,所以笔者建议大家参考这个开源的项目:点我下载项目,该项目是我在学习Apache Shiro过程中参考的项目,我在原项目的基础上增加了一些便于理解的注释等,项目采用前后分离的方式开发,原作者:点我查看
Apache Shiro是Java项目中常用的两大安全框架之一,可以完成认证、授权、加密、会话管理等功能。Apache Shiro较Spring家族的Spring Security更为简洁、更易上手的特点。
首先需要在pom文件中引入该框架,配置 web.xml ,使用 shiroFilter 拦截 / * 根据需求完成对页面权限控制
<?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-3.0.xsd">
<!-- Shiro核心组件 -->
<!-- 1 配置 securityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 配置缓存配置 -->
<property name="cacheManager" ref="cacheManager"/>
<property name="realm" ref="jdbcRealm"/>
</bean>
<!-- 2配置 缓存框架 ehcache-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 3 自定义Realm -->
<bean id="jdbcRealm" class="com.qfjy.web.shiro.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--hashAlgorithm 加密的名称 -->
<property name="hashAlgorithmName" value="MD5"></property>
<!-- hashIterations 加密的次数-->
<property name="hashIterations" value="1902"></property>
</bean>
</property>
</bean>
<!--
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA-256"/>
</bean>
</property>
-->
<!-- =========================================================
Shiro Spring特有的整合
========================================================= -->
<!-- 后处理器自动为Spring配置的Shiro对象调用init()和destroy()方法,
因此您不必1)为每个bean定义指定init-method和destroy-method属性,
2)甚至知道哪些Shiro对象要求调用这些方法。
Spring 自动管理Shiro的对象(生命周期管理)
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--
为Spring配置的bean启用Shiro Annotations。仅在lifecycleBeanProcessor运行后运行
-->
<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>
<!-- 前提条件: bean id 必须要和 web.xml shiroFilter一致 -->
<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="/main.jsp"/>
<!-- 无权限页面-->
<property name="unauthorizedUrl" value="/unauth.jsp"/>
<!-- <property name="filters">
<util:map>
<entry key="aName" value-ref="someFilterPojo"/>
</util:map>
</property> -->
<property name="filterChainDefinitions">
<value>
/login.jsp = anon
/users/login=anon
/css/**=anon
/images/**=anon
/js/**=anon
/login/exit = logout
/student.jsp=roles[stu]
/teacher.jsp=roles[tea]
/list.jsp=roles[stu,tea]
/** = authc
</value>
</property>
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
</bean>
<!-- 工厂方法注入 -->
<!-- 1 构建Bean管理交由Spring ioc容器-->
<bean id="filterChainDefinitionMapBuilder" class="com.qfjy.web.shiro.FilterChainDefinitionMapBuilder"></bean>
<!-- 2 工厂方法注入-->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="builder" />
</beans>
关于配置:
页面权限的优先顺序: 先声明的优先:URL权限采取第一次匹配优先的方式(为准),即从头开始
xxx.html* = anon (未登录可以访问) xxx.html* =authc (必须登录才能访问 ) xxx.html* = perms[权限] (需要特定权限才能访问) xxx.html* = roles[角色] (需要特定角色才能访问 )
方法级别粒度权限控制需要完成对用户的认证功能
//Realm:SecurityManager要验证用户身份,那么它需要从Realm中获取相应分身进行比较,以确定用户身份是否合法。
//合法:从Realm中获得用户相应的角色/权限。
//ShiroRealm.java
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UsersService usersService;
/**
* 作用:认证
* 1、该方法什么情况下会被调用
* currentUser.login(token)
* 2、该方法的入参是什么数据
* UsernamePasswordToken
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从token中获取用户从前端提交的数据
UsernamePasswordToken upToken= (UsernamePasswordToken) token;
System.out.println(token);
//1 得到用户名输入的用户名
String username=upToken.getUsername();
//2 判断当前用户在数据库中是否存在
Users users=usersService.selectByUsername(username);
//3如果用户名不存在 UnknownAccountException
if(users==null){
throw new UnknownAccountException("用户名不存在");
}
//4如果用户的状态 锁定 LockedAccountException status=0 1正常
if(users.getStatus()==0){
throw new LockedAccountException("该帐户已被锁定");
}
//5 密码的比较(前台的密码=数据库中查询的密码) Shiro内部来完成的。
// 方法中的参数:Object principal, Object credentials, String realmName
/*
身份验证:一般需要提供如身份 ID 等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。
在 shiro 中,用户需要提供 principals (身份)和 credentials(证 明)给 shiro,从而应用能验证用户身份:
• principals:身份,即主体的标识属性,可以是任何属性,如用户名、 邮箱等,唯一即可。
一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名/邮箱/手机号。
•credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证 书等。
• 最常见的 principals 和 credentials 组合就是用户名/密码了
*/
Object principal=username;
Object credentials=users.getPassword();//数据库查询出的密码,不能给从前端中获取的token中的密码,否则全是对的
ByteSource credentialsSalt=ByteSource.Util.bytes(username); //加盐主体
//AuthenticationInfo info=new SimpleAuthenticationInfo(principal,credentials,super.getName());
AuthenticationInfo info =new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,super.getName());
return info;
}
/**
* 作用:授权
* 1、该方法什么情况下会被调用?
* 1.1当访问需要角色权限时访问
* 1.2如果找到该角色,就不会再第二次调用。
* 2、该方法的入参是什么数据?
* 主体信息(用户名)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1 得到用户名信息
String username= (String) principals.getPrimaryPrincipal();
//2 查询数据库,根据用户名称,查询该用户拥有哪些角色
Set<String> roles= usersService.selectRnamesByUserName(username);
//3 管理员拥有所有的角色(特殊权限)
if(roles.contains("admin")){
roles=usersService.selectRolesAllRnames();
}
AuthorizationInfo info=new SimpleAuthorizationInfo(roles);
return info;
}
/**
* 注册功能时需要使用的代码
* @param args
*/
public static void main(String[] args) {
//注册时存入的数据
//对数据库的密码按照相应规则加密
/**
* hashAlgorithmName 加密码名称
* credentials 要加密的密码
* hashIterations 加密的次数
*/
Object credentials="123456";
String hashAlgorithmName ="MD5";
String username="maoshuai"; //用户名
Object salt=ByteSource.Util.bytes(username); ;
int hashIterations=1902; //加密次数
Object result=new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
System.out.println(result);
}
}
登录:
//UserController.java
@Controller
@RequestMapping("users")
public class UsersController {
/**
* 登录功能
* 如果登录失败,跳到登录页面login.jsp
* 如果登录成功,跳到主页面 main.jsp
*
*@RequestParam注解:
*将请求的参数赋值到控制器的方法参数上
*eg:@RequestParam(value="userId",defaultValue="1"
*/
@RequestMapping("login") // users/login
public ModelAndView login(@RequestParam("username")String username,
@RequestParam("password")String password){
ModelAndView model=new ModelAndView();
//获取用户信息
Subject currentUser = SecurityUtils.getSubject();
if (!currentUser.isAuthenticated()) { //当前Subject是否进行认证(登录)
//前台用户传入的用户名和密码 (将用户名和密码封装到UsernamePasswordToken对象中)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true);//记住我
try {
//进行认证(登录)功能
currentUser.login(token);
} catch (UnknownAccountException uae) {//未知帐户异常
System.out.println(uae.getMessage());
model.setViewName("/login.jsp");
model.addObject("msg","用户名不存在");
return model;
} catch (IncorrectCredentialsException ice) { //凭证匹配器异常 不正确的凭据异常
System.out.println(ice.getMessage());
model.setViewName("/login.jsp");
model.addObject("msg","密码输入错误");
return model;
} catch (LockedAccountException lae) { //帐户锁定异常 锁定帐户例外 (将来要在业务逻辑中进行判断)
System.out.println(lae.getMessage());
model.setViewName("/login.jsp");
model.addObject("msg","该用户因违规,被锁定");
return model;
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) { // 认证异常 身份验证异常
return null;
}
}
/*
* 在ShiroRealm中认证成功后
* */
model.setViewName("redirect:/main.jsp");
return model;
}
/**
* 登出功能 (退出登录)
*/
@RequestMapping("logout") // users/logout
public String logout(){
Subject currentUser = SecurityUtils.getSubject();
//all done - log out! 登出
currentUser.logout();
return "/login.jsp";
}
}
粒度权限控制常用注解:
@RequiresPermissions(权限) 需要特定权限才能访问
@RequiresRoles(角色) 需要特定角色才能访问
@RequiresAuthentication 需要认证才能访问
以下是一个粒度例子:
//UserController.java
//若需要实现这么一个需求:只有特定角色才能实现删除功能
/**
* 删除角色
* 传入一个roleId,根据roleId判断用户是否具有删除功能的权限
*/
@RequiresPermissions("role:delete")
@PostMapping("/deleteRole")
public JSONObject deleteRole(@RequestBody JSONObject requestJson) {
CommonUtil.hasAllRequired(requestJson, "roleId");
return userService.deleteRole(requestJson);
}
sys_role:用户组/角色(定义系统需要的权限分级,如CEO、HR、员工,表中使用不同的id以区分的角色)
sys_user:用户表(表中放置系统中所有用户的信息,表中的id用于设置主键,由于角色与用户是一对多的关系,role_id用于与角色表中的id进行关联,以实现用户获取其对应的角色)
sys_permission:权限表,用户登录后,获取角色,角色表对应permission中id来获取角色可以使用的操作权限,
sys_role_permission:角色权限表(表中permission_id对应sys_permission表中id,表中有role_id和permission_id,来配置角色中的操作权限)