现在大部分系统都使用Shiro管理认证和权限,包括功能权限和内容权限,如何进一步灵活使用Shiro,需要我们了解Shiro的工作原理。
Shiro使用Web过滤器拦截用户访问,如果用户已经登录跳转工作页面,如果没有登录跳转到登录页面,Shiro的过滤器可以通过Web.xml设置,也可以通过注解设置,SpringBoot使用注解配置Shiro过滤器。
Shiro的Filter需要注入SecurityManager对象,而SecurityManager对象需要注入我们自定义的域对象myRealm,在我们域对象myRealm对象中实现Shiro的定制化功能,我们在对象myRealm中定义如何用户认证,如何给登录用户分配权限,权限分为内容权限和功能权限,我们可以设置角色,用户对应角色,角色对应权限列表,当然这些都需要通过数据库查询。
域对象myRealm是Shiro开发我们的开发接口,Shiro会回调这个对象的方法。
跟踪一下Shiro的源代码,你会发现对象myRealm作为Shiro功能扩展,主要处理认证和权限分配,而这些操作都是系统实现者需要考虑的业务逻辑,Shiro按照约定使用这些数据。
集成步骤:
一、修改POM依赖
POM.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 定义公共资源版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.test</groupId>
<artifactId>Proj_1604F</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>Proj_1604F Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--JSP支持的依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- 数据源依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>jconsole</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>tools</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 数据库驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-solr</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils-core</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0-RC2</version>
</dependency>
<!--
<dependency>
<groupId>com.test</groupId>
<artifactId>gfdev</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${basedir}/src/lib/gfdev-2.0.jar</systemPath>
</dependency>
-->
</dependencies>
<build>
<finalName>EC5_Proj</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、Shiro过滤器
package com.test.util;
import java.util.HashMap;
import java.util.Map;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
@Configuration
public class ShiroConfiguration {
//将自己的验证方式加入容器
@Bean
public HkShiroRealm myShiroRealm() {
HkShiroRealm myShiroRealm = new HkShiroRealm();
myShiroRealm.setCachingEnabled(false);
return myShiroRealm;
}
//权限管理,配置主要是Realm的管理认证
@Bean
public org.apache.shiro.mgt.SecurityManager securityManager()
{
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager)
{
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String,String> map = new HashMap<String, String>();
//登出
map.put("/logout","logout");
//对所有用户认证
map.put("/**","authc");
//对静态资源过滤权限检查
map.put("/easyui/**", "anon");
map.put("/images/**", "anon");
map.put("/login","anon");
map.put("/js/**", "anon");
//登录
shiroFilterFactoryBean.setLoginUrl("/init");
//首页
shiroFilterFactoryBean.setSuccessUrl("/main");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/nopriv.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//加入注解的使用,不加入这个注解不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public HandlerExceptionResolver solver(){
HandlerExceptionResolver handlerExceptionResolver=new ShiroExceptionResolver();
return handlerExceptionResolver;
}
}
三、Shrio自定义域
package com.test.util;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import com.test.model.UserInfo;
import com.test.service.IOrgModel;
//实现AuthorizingRealm接口用户用户认证
public class HkShiroRealm extends AuthorizingRealm{
//用于用户查询
@Autowired
private IOrgModel orgmodel;
//角色权限和对应权限添加
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
{
//获取登录用户名
String name= (String) principalCollection.getPrimaryPrincipal();
//查询用户名称
UserInfo user = null;
try
{
user = orgmodel.getUserByLoginId(name);
}
catch(Exception e)
{
e.printStackTrace();
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//判断是否为超级管理员
if("admin".equals(user.getLoginId()))
{
//从数据库权限表中获取权限列表,此处使用静态配置模拟
//simpleAuthorizationInfo.addStringPermission("/");
simpleAuthorizationInfo.addStringPermission("url:/main");
}
else
{
//添加角色和权限
//从数据库权限表中获取权限列表,此处使用静态配置模拟
//simpleAuthorizationInfo.addStringPermission("/");
}
return simpleAuthorizationInfo;
}
//用户认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException
{
System.out.println("authenticationToken==="+authenticationToken);
//在Post请求的时候会先进认证,然后在到请求
if (authenticationToken.getPrincipal() == null) {
return null;
}
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
UserInfo user = null;
try
{
user = orgmodel.getUserByLoginId(name);
}
catch(Exception e)
{
e.printStackTrace();
}
if (user == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPwd(),user.getName());
return simpleAuthenticationInfo;
}
}
}
四、异常处理类
package com.test.util;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
public class ShiroExceptionResolver implements HandlerExceptionResolver{
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
{
String url = request.getRequestURI();
if(ex instanceof UnauthorizedException){
ModelAndView mv = new ModelAndView("nopriv");
mv.addObject("url", url);
return mv;
}
ex.printStackTrace();
ModelAndView mv = new ModelAndView("nopriv");
mv.addObject("url", url);
mv.addObject("exception", ex.toString().replaceAll("\n", "<br/>"));
return mv;
}
}
五、Controller方法上定义注解
@RequiresPermissions("url:/main")
@RequestMapping("/main")
public String main()
{
return "main";
}
六、登录方法
@RequestMapping("/login")
public String login(HttpServletRequest req,HttpServletResponse resp,String loginId,String pwd)
{
try
{
System.out.println("loginId==="+loginId+",pwd==="+pwd);
//初始化组织机构
orgmodel.initOrgModel();
try
{
String pwd2 = Util.getMD5(pwd);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken upToken = new UsernamePasswordToken(loginId,pwd2);
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(upToken);
}
catch(UnknownAccountException uae)
{
req.setAttribute("showMsg", "账号错误");
return "login";
}
catch(IncorrectCredentialsException ice)
{
req.setAttribute("showMsg", "密码错误");
return "login";
}
//记录Cookie
Cookie c = new Cookie("loginId",loginId);
c.setPath(req.getContextPath());
resp.addCookie(c);
Cookie c2 = new Cookie("pwd",pwd);
c2.setPath(req.getContextPath());
resp.addCookie(c2);
String pwd2 = Util.getMD5(pwd);
Boolean rtn = orgmodel.login(loginId, pwd2);
System.out.println("rtn="+rtn);
return "redirect:/main";
}
catch(Exception e)
{
e.printStackTrace();
String msg = e.getMessage();
req.setAttribute("msg", msg);
}
return "redirect:/main";
}
七、未授权页面
<%@ page language="java" contentType="text/html; charset=UTF8"
pageEncoding="UTF8"%>
<%
String ctx = request.getContextPath();
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF8">
<title>未授权访问</title>
<link rel="stylesheet" type="text/css" href="<%=ctx%>/easyui/themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="<%=ctx%>/easyui/themes/icon.css">
<script type="text/javascript" src="<%=ctx%>/easyui/jquery.min.js"></script>
<script type="text/javascript" src="<%=ctx%>/easyui/jquery.easyui.min.js"></script>
</head>
<body>
<div id="p" class="easyui-panel" title="错误信息"
style="width:500px;height:150px;padding:10px;background:#fafafa;"
data-options="iconCls:'icon-tip',closable:false,
collapsible:false,minimizable:false,maximizable:false">
<p>未授权访问网络资源:${url}</p>
<p>请联系管理员,分配授权</p>
</div>
</body>
</html>