本文主要讲解Shiro安全框架与SSM(SpringMVC/Spring/Mybatis)框架整合,实现系统的权限控制。
本文建立在SSM框架的基础上,默认已经搭建了SSM框架。具体的SSM框架搭建,请参考文章“SSM框架搭建”,这里不做赘述。
Shiro安全框架与SSM整合的示例代码,github地址:https://github.com/1287642889/Shiro_SSM。
前期准备工作:
1、新建Maven工程,并搭建SSM框架,项目结构如下所示:
2、pom.xml具体如下:
<!--Spring依赖-->
<dependencies>
<!--Spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--sqlserver数据库依赖-->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>sqljdbc4</artifactId>
<version>4.0</version>
</dependency>
<!--c3p0数据库连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--jsp依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<!--jstl标签库-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--fileupload文件下载-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.2</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</build>
3、数据库中新建用户表、角色表、资源表、用户角色表、角色资源表这5张表,在Users表中添加wzf、a、b三个用户;在Role表中添加admin、role1、role2三个角色,在Permission表中添加/student/add、/student/delete、/student/update、/student/select四个资源权限;并在相应的关系表中添加对应的数据。
本系统的权限分配情况是:wzf用户是admin角色,拥有所有的资源权限;a用户是role1角色,拥有除perms[student:delete]外的所有资源权限;b用户是role2角色,只拥有perms[student:select]资源权限。
数据库表结构和主外键关系具体如下:
在SSM框架的基础上整合Shiro功能。Shiro主要有Filter、Realm构成,主要代码文件如下:
1、配置ShiroConfiguration过滤器链,代码如下:
@Configuration
public class ShiroConfiguration {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shiroFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置SecuritManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/","anon");
filterChainDefinitionMap.put("/login","anon");
filterChainDefinitionMap.put("/student/add", "perms[/student/add]");
filterChainDefinitionMap.put("/student/delete", "perms[/student/delete]");
filterChainDefinitionMap.put("/student/update", "perms[/student/update]");
filterChainDefinitionMap.put("/student/select", "perms[/student/select]");
// 配置退出过滤器,其中的具体代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/");
// 登录成功后要跳转的链接
//shiroFilterFactoryBean.setSuccessUrl("");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
@Bean
public MyRealm myShiroRealm(){
MyRealm userRealm = new MyRealm();
return userRealm;
}
//开启shiro aop注解支持.
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
2、自定义MyRealm代码如下:
public class MyRealm extends AuthorizingRealm {
@Autowired
private UsersService usersService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
// Users user = userService.findByUserName(username);
// user.setLocked(true); //登录成功后锁定用户
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//根据用户名查找对应的角色集合
authorizationInfo.setRoles(usersService.findRoles(username));
//根据用户名查找对应的资源集合
authorizationInfo.setStringPermissions(usersService.findPermissions(username));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal();
//根据用户名查找用户
Users user = usersService.findByUserName(username);
if(user == null) {
throw new UnknownAccountException();//没找到帐号
}
// if(Boolean.TRUE.equals(user.getLocked())) {
// throw new LockedAccountException(); //帐号锁定
// }
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUserName(), //用户名
user.getPassword(), //密码
getName() //realm name
);
return authenticationInfo;
}
public void setUsersService(UsersService usersService) {
this.usersService = usersService;
}
}
3、对于MyRealm中需要使用UsersService的方法:usersService.findRoles(username)、usersService.findPermissions(username)。参考代码如下:
@Service
public class UsersServiceImpl implements UsersService {
@Autowired
private UsersDao usersDao;
@Autowired
private RoleDao roleDao;
@Autowired
private PermissionDao permissionDao;
@Autowired
private UsersRoleDao usersRoleDao;
@Autowired
private RolePermissionDao rolePermissionDao;
//根据用户名查找用户
public Users findByUserName(String username){
List<Users> list = usersDao.findByUserName(username);
if(list.size()<1){
return null;
}else{
return list.get(0);
}
}
//根据用户名查找用户角色
public Set<String> findRoles(String username){
Set<String> roleNameSet = new HashSet<>();
Integer userId = findByUserName(username).getId();
List<UserRole> usersRoleList = usersRoleDao.findByUserId(userId);
for(int i = 0; i < usersRoleList.size(); i++){
Role role = roleDao.findById(usersRoleList.get(i).getRoleId());
roleNameSet.add(role.getRoleName());
}
return roleNameSet;
}
//根据用户名查找用户权限
public Set<String> findPermissions(String username){
Set<String> permissionNameSet = new HashSet<>();
Integer userId = findByUserName(username).getId();
List<Integer> roleIdList = new ArrayList<>();
List<UserRole> usersRoleList = usersRoleDao.findByUserId(userId);
for(int i = 0; i < usersRoleList.size(); i++){
Integer roleId = roleDao.findById(usersRoleList.get(i).getRoleId()).getId();
roleIdList.add(roleId);
}
List<RolePermission> rolePermissionList = rolePermissionDao.findByRoleIdIn(roleIdList);
for(int i = 0;i<rolePermissionList.size();i++){
Permission permission = permissionDao.findById(rolePermissionList.get(i).getPermissionId());
permissionNameSet.add(permission.getPermissionName());
}
return permissionNameSet;
}
//省略set方法
}
RolePermissionMapper.xml参考代码如下:
<mapper namespace="com.wzf.dao.RolePermissionDao">
<resultMap id="RolePermission" type="com.wzf.pojo.RolePermission">
</resultMap>
<select id="findByRoleIdIn" resultMap="RolePermission">
select * from RolePermission where roleId IN
<foreach collection="list" item="roleId" open="(" close=")" separator=",">
#{roleId}
</foreach>
</select>
</mapper>
4、web.xml中添加shiro监听和过滤器,具体代码如下:
<!-- Spring和mybatis的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring-mybatis.xml</param-value>
</context-param>
<!-- 编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 防止Spring内存溢出监听器 -->
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!-- Spring MVC servlet -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--Shiro监听和过滤器-->
<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>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<welcome-file-list>
<welcome-file>/WEB-INF/index.jsp</welcome-file>
</welcome-file-list>
shiro具体使用:
1、登录方法代码如下:
//登录
@PostMapping(value = "/login")
public ModelAndView login(String userName, String password){
ModelAndView mav = new ModelAndView();
//密码加密
String newPassword = PasswordUtil.encodePwd(password);
UsernamePasswordToken token = new UsernamePasswordToken(userName,newPassword);
Subject subject = SecurityUtils.getSubject();
try{
subject.login(token);
//mav.addObject("currentUser",userName);
mav.setViewName("main");
return mav;
}catch (Exception e){
e.printStackTrace();
mav.setViewName("index");
mav.addObject("error","用户名或密码错误!");
return mav;
}
}
2、main.jsp代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName() +":"+request.getServerPort()+path+"/";
%>
<%@page isELIgnored="false" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>main.jsp</title>
</head>
<body>
<h1>欢迎您,<shiro:principal/></h1></br>
<shiro:hasRole name="admin">
<h1>只有admin角色能够看到的内容</h1><br>
</shiro:hasRole>
<shiro:hasRole name="role1">
<h1>只有role1角色能够看到的内容</h1><br>
</shiro:hasRole>
<shiro:hasRole name="role2">
<h1>只有role2角色能够看到的内容</h1><br>
</shiro:hasRole>
<shiro:hasPermission name="/student/add">
<h1>增加用户</h1></br>
</shiro:hasPermission>
<shiro:hasPermission name="/student/delete">
<h1>删除用户</h1></br>
</shiro:hasPermission>
<shiro:hasPermission name="/student/update">
<h1>修改用户</h1></br>
</shiro:hasPermission>
<shiro:hasPermission name="/student/select">
<h1>查询用户</h1></br>
</shiro:hasPermission>
<a href="<%=basePath%>student/add">增加学生</a><br>
<a href="<%=basePath%>student/delete">删除学生</a><br>
<a href="<%=basePath%>student/update">修改学生</a><br>
<a href="<%=basePath%>student/select">查询学生</a><br>
</body>
</html>
3、结果展示