Shiro与Spring、Springmvc的整合

1、在web.xml中配置Shiro的filter

在web系统中,shiro也通过filter进行拦截。filter拦截后将操作权交给spring中配置的filterChain(过虑链儿)
shiro提供很多filter。




shiroFilter
org.springframework.web.filter.DelegatingFilterProxy


targetFilterLifecycle
true



targetBeanName
shiroFilter



shiroFilter
/*

2、在applicationContext-shiro.xml 中配置web.xml中fitler对应spring容器中的bean。##





























3、配置静态资源的匿名访问





/images/** = anon
/js/** = anon
/styles/** = anon

4、登录认证的实现

一、原理

使用FormAuthenticationFilter过虑器实现 ,原理如下:


 将用户没有认证时,请求loginurl进行认证,用户身份和用户密码提交数据到loginurl

 FormAuthenticationFilter拦截住取出request中的username和password(两个参数名称是可以配置的)

 FormAuthenticationFilter调用realm传入一个token(username和password)

 realm认证时根据username查询用户信息(在Activeuser中存储,包括 userid、usercode、username、menus)。

 如果查询不到,realm返回null,FormAuthenticationFilter向request域中填充一个参数(记录了异常信息)

二、代码实现

//登陆提交地址,和applicationContext-shiro.xml中配置的loginurl一致
@RequestMapping("login")
public String login(HttpServletRequest request)throws Exception{

//如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
//根据shiro返回的异常类路径判断,抛出指定异常信息
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
//最终会抛给异常处理器
throw new CustomException("账号不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CustomException("用户名/密码错误");
} else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("验证码错误 ");
}else {
throw new Exception();//最终在异常处理器生成未知错误
}
}
//此方法不处理登陆成功(认证成功),shiro认证成功会自动跳转到上一个请求路径
//登陆失败还到login页面
return "login";
}

三、配置登录的拦截器





/images/** = anon
/js/** = anon
/styles/** = anon

/** = authc

四、退出

不用我们去实现退出,只要去访问一个退出的url(该 url是可以不存在),由LogoutFilter拦截住,清除session。

在applicationContext-shiro.xml配置LogoutFilter




/images/** = anon
/js/** = anon
/styles/** = anon

/** = authc


/logout.action = logout

五、编写认证的realm

//realm的认证方法,从数据库查询用户信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {

// token是用户输入的用户名和密码
// 第一步从token中取出用户名
String userCode = (String) token.getPrincipal();

// 第二步:根据用户输入的userCode从数据库查询
SysUser sysUser = null;
try {
sysUser = sysService.findSysUserByUserCode(userCode);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

// 如果查询不到返回null
if(sysUser==null){//
return null;
}
// 从数据库查询到密码
String password = sysUser.getPassword();

//盐
String salt = sysUser.getSalt();

// 如果查询到返回认证信息AuthenticationInfo

//activeUser就是用户身份信息
ActiveUser activeUser = new ActiveUser();

activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
//..

//根据用户id取出菜单
List menus = null;
try {
//通过service取出菜单
menus = sysService.findMenuListByUserId(sysUser.getId());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//将用户菜单 设置到activeUser
activeUser.setMenus(menus);

//将activeUser设置simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password,ByteSource.Util.bytes(salt), this.getName());

return simpleAuthenticationInfo;
}

六、设置凭证匹配器###

数据库中存储到的md5的散列值,在realm中需要设置数据库中的散列值它使用散列算法 及散列次数,让shiro进行散列对比时和原始数据库中的散列值使用的算法 一致。





七、认证通过的Controller

//系统首页
@RequestMapping("/first")
public String first(Model model)throws Exception{

//从shiro的session中取activeUser
Subject subject = SecurityUtils.getSubject();
//取身份信息
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
//通过model传到页面
model.addAttribute("activeUser", activeUser);

return "/first";
}

5、授权的实现

一、授权流程

在applicationContext-shiro.xml中配置url所对应的权限。

授权流程:
1、在applicationContext-shiro.xml中配置filter规则

/items/queryItems.action = perms[item:query]
2、用户在认证通过后,请求/items/queryItems.action
3、被PermissionsAuthorizationFilter拦截,发现需要“item:query”权限
4、PermissionsAuthorizationFilter调用realm中的doGetAuthorizationInfo获取数据库中正确的权限
5、PermissionsAuthorizationFilter对item:query 和从realm中获取权限进行对比,如果“item:query”在realm返回的权限列表中,授权通过。

二、配置授权不通过的拒绝页面








三、此种授权方式存在的问题

1、在applicationContext-shiro.xml中配置过虑器链接,需要将全部的url和权限对应起来进行配置,比较发麻不方便使用。

2、每次授权都需要调用realm查询数据库,对于系统性能有很大影响,可以通过shiro缓存来解决

四、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

anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数
perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms ["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
user:例如/admins/user/**=user没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查

五、授权realm的实现

// 用于授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {

//从 principals获取主身份信息
//将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中身份类型),
ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();

//根据身份信息获取权限信息
//从数据库获取到权限数据
List permissionList = null;
try {
permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//单独定一个集合对象
List permissions = new ArrayList();
if(permissionList!=null){
for(SysPermission sysPermission:permissionList){
//将数据库中的权限标签 符放入集合
permissions.add(sysPermission.getPercode());
}
}

/* List permissions = new ArrayList();
permissions.add("user:create");//用户的创建
permissions.add("item:query");//商品查询权限
permissions.add("item:add");//商品添加权限
permissions.add("item:edit");//商品修改权限**/
//查到权限数据,返回授权信息(要包括 上边的permissions)
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将上边查询到授权信息填充到simpleAuthorizationInfo对象中
simpleAuthorizationInfo.addStringPermissions(permissions);

return simpleAuthorizationInfo;
}

六、开启controller的aop支持


<aop:config proxy-target-class="true"></aop:config>



七、在controller中添加注解完成授权

//商品信息方法
@RequestMapping("/queryItems")
@RequiresPermissions("item:query")//执行queryItems需要"item:query"权限
public ModelAndView queryItems(HttpServletRequest request) throws Exception {

System.out.println(request.getParameter("id"));

//调用service查询商品列表
List itemsList = itemsService.findItemsList(null);

ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("itemsList", itemsList);
// 指定逻辑视图名
modelAndView.setViewName("itemsList");

return modelAndView;
}

八、jsp页面通过标签授权

Jsp页面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>

标签名称     标签条件(均是显示标签内容)
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:principal property="username"/> 显示用户身份中的属性值
jsp页面实例:



<shiro:hasPermission name="item:update">
修改
</shiro:hasPermission>

6、配置session管理器

和shiro整合后,使用shiro的session管理,shiro提供sessionDao操作 会话数据。

配置sessionManager













7、shiro缓存

一、缓存流程

shiro中提供了对认证信息和授权信息的缓存。shiro默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的。主要研究授权信息缓存,因为授权的数据量大。

用户认证通过。
该 用户第一次授权:调用realm查询数据库
该 用户第二次授权:不调用realm查询数据库,直接从缓存中取出授权信息(权限标识符)。

二、配置缓存管理器

使用ehcache进行缓存管理。





!-- securityManager安全管理器 -->






三、编写ehcache的配置文件






四、缓存清空

如果用户正常退出,缓存自动清空。

如果用户非正常退出,缓存自动清空。

如果修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。
需要手动进行编程实现:
在权限修改后调用realm的clearCache方法清除缓存。
下边的代码正常开发时要放在service中调用。
在service中,权限修改后调用realm的方法。

//清除缓存
public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
编写controller

//注入realm
@Autowired
private CustomRealm customRealm;

@RequestMapping("/clearShiroCache")
public String clearShiroCache(){

//清除缓存,将来正常开发要在service调用customRealm.clearCached()
customRealm.clearCached();

return "success";
}

8、验证码的实现

一、验证码的生成

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.Random"%>
<%@ page import="java.io.OutputStream"%>
<%@ page import="java.awt.Color"%>
<%@ page import="java.awt.Font"%>
<%@ page import="java.awt.Graphics"%>
<%@ page import="java.awt.image.BufferedImage"%>
<%@ page import="javax.imageio.ImageIO"%>
<%
int width = 60;
int height = 32;
//create the image
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
// set the background color
g.setColor(new Color(0xDCDCDC));
g.fillRect(0, 0, width, height);
// draw the border
g.setColor(Color.black);
g.drawRect(0, 0, width - 1, height - 1);
// create a random instance to generate the codes
Random rdm = new Random();
String hash1 = Integer.toHexString(rdm.nextInt());
System.out.print(hash1);
// make some confusion
for (int i = 0; i < 50; i++) {
int x = rdm.nextInt(width);
int y = rdm.nextInt(height);
g.drawOval(x, y, 0, 0);
}
// generate a random code
String capstr = hash1.substring(0, 4);
//将生成的验证码存入session
session.setAttribute("validateCode", capstr);
g.setColor(new Color(0, 100, 0));
g.setFont(new Font("Candara", Font.BOLD, 24));
g.drawString(capstr, 8, 24);
g.dispose();
//输出图片
response.setContentType("image/jpeg");
out.clear();
out = pageContext.pushBody();
OutputStream strm = response.getOutputStream();
ImageIO.write(image, "jpeg", strm);
strm.close();
%>

二、在登录页面加入验证码


验证码:
刷新

三、自定义FormAuthenticationFilter过滤器

public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {

//原FormAuthenticationFilter的认证方法
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception {
//在这里进行验证码的校验

//从session获取正确验证码
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpSession session =httpServletRequest.getSession();
//取出session的验证码(正确的验证码)
String validateCode = (String) session.getAttribute("validateCode");

//取出页面的验证码
//输入的验证和session中的验证进行对比
String randomcode = httpServletRequest.getParameter("randomcode");
if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
//如果校验失败,将验证码错误失败信息,通过shiroLoginFailure设置到request中
httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
//拒绝访问,不再校验账号和密码
return true;
}
return super.onAccessDenied(request, response);
}

}

四、配置自定义FormAuthenticationFilter过滤器























五、在login.action对验证错误 进行解析

public String login(HttpServletRequest request)throws Exception{

//如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
//根据shiro返回的异常类路径判断,抛出指定异常信息
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
//最终会抛给异常处理器
throw new CustomException("账号不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CustomException("用户名/密码错误");
} else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("验证码错误 ");
}else {
throw new Exception();//最终在异常处理器生成未知错误
}
}
//此方法不处理登陆成功(认证成功),shiro认证成功会自动跳转到上一个请求路径
//登陆失败还到login页面
return "login";
}

六、在filter配置匿名访问验证码jsp





/images/** = anon
/js/** = anon
/styles/** = anon

/validatecode.jsp = anon

9、记住我实现

一、 用户身份实现java.io.Serializable接口

二、配置rememeberMeManager



















三、在登录页面加入记住我选项



自动登陆

四、配置rememberMe的input名称










五、使用UserFilter

如果设置记住我,下次访问某些url时可以不用登陆。将记住我即可访问的地址配置让UserFilter拦截。





/images/** = anon
/js/** = anon
/styles/** = anon

/validatecode.jsp = anon


/logout.action = logout



/index.jsp = user
/first.action = user
/welcome.jsp = user

/** = authc


posted @ 2017-08-29 21:33  一条路上的咸鱼  阅读(272)  评论(0)    收藏  举报