spring整合shiro框架的实现步骤记录
shiro
Shiro是Apache下的一个开源项目,我们称之为ApacheShiro。它是一个很易用与Java项目的的安全框架,提供了认证、授权、加密、会话管理,与springSecurity一样都是做一个权限的安全框架,但是与SpringSecurity相比,在于Shiro使用了比较简单易懂易于使用的授权方式。shiro属于轻量级框架,相对于security简单的多,也没有security那么复杂。更多详细的介绍可以从它的官网上(http://shiro.apache.org/)基本可以了解到,她主要提供以下功能:
(1)Authentication(认证)
(2)Authorization(授权)
(3)SessionManagement(会话管理)
(4)Cryptography(加密)
首先,认证服务,也就是说通过她可以完成身份认证,让她去判断用户是否为真实的会员。
其次,授权服务,说白了就是“访问控制”服务,也就是让她来识别用户拥有哪些权限。再说的白一点,就是通过判断用户是什么角色,来赋予他哪些操作权限。
然后,还有会话管理服务,这时一个独立的Session管理框架,和我们熟知的HttpSession不太一样。
最后,她还提供了Cryptography(加密)服务,封装了很多密码学的算法。
今天,我就不全说了,重点说下她的会话管理功能,其实这个也是几乎所有的web应该都会涉及到的。
在说shiro的会话管理服务前,先回顾下之前的会话管理我们是怎么做的。
1、最初我们是直接用的web服务器的HttpSession的机制,也就是用户第一次进来的话,web容器会为这个请求创建一个session,然后把这个session存储起来,通过将对应的sessionId,作为cookie传给客户端,
如果客户端再次向这个服务器发送请求的话,会自动将这个sessionId带过来,然后web服务器会根据客户端带过来的sessionId,判断其对于的session是否还存在于内存中(session是有过期时间的,可以在web.xml文件里面配置),如果找不到对应的session了,说明已经过了session失效时间,这时web服务器会再次为它创建一个session,然后和之前一样,将这个新的sessionId传给客户端。
因此,我们可以通过这种机制,在程序里管理用户的登录会话,比如我们在用户第一次登录成功后,将用户的基本信息存储在session里(比如:session.setAttribute("user","userInfo")),下次用户再次访问的时候,我们根据获取当前session里的user信息
(session.getAttribute("user")),来判断用户是否过期,如果获取不到,那么提示用户重新登录。
2、第二种方式,就是我们将存储信息的地方转移到第三方介质中,比如缓存里,memecache或者Redis都可以,这种方式主要是因为分布式系统的出现而采用的。
这种情况下,就需要我们自己生成sessionId了,一般我们会用一个定义好的前缀(user:login:token)再加上userid,或者时间戳都可以。然后我们会将这个sessionId作为缓存的key,用户的信息作为value,存入缓存中,并设置失效时间:
jedisClient.set(tokenKey,JsonUtil.toJSONString(userInfo)); jedisClient.expire(tokenKey,TOKEN_LOSE_SECONDS);
我们还要将生成的这个tokenKey通过cookie传到客户端:CookieUtils.setCookie(request,response,"TT_TOKEN",tokenKey);
这样,我们在用户下次访问的时候(定义一个拦截器),就可以从cookie里取出对应的tokenKey,然后用这个tokenKey去到缓存里取相应的值,如果获取不到,说明这个key已经失效了,提示用户重新登录。
注:tokenKey很重要,她是连接缓存端和客户端的枢纽。
3、最后一种就是我们shiro方式了,思路也类似,代码挺简单的,那我就直接上代码吧:
1)、新建一个applicationContext-shiro.xml文件:
/jcaptcha*=anon /logout=anon //这个类是需要自己实现的
2)、在web.xml里配置相应的filter:
shiroFilter org.springframework.web.filter.DelegatingFilterProxy targetFilterLifecycle true shiroFilter /*
3)写一个实现类,继承AbstractSessionDAO,实现相应的方法。
packagecom.jdd.core.shiro;
importcom.smart.core.redis.RedisManager;
importorg.apache.shiro.session.Session;
importorg.apache.shiro.session.UnknownSessionException;
importorg.apache.shiro.session.mgt.eis.AbstractSessionDAO;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.util.SerializationUtils;
importjava.io.*;
importjava.util.ArrayList;
importjava.util.Collection;
publicclassMySessionDAOextendsAbstractSessionDAO{
@Autowired
privateRedisManagerredisManager;
@Override
publicvoidupdate(Sessionsession)throwsUnknownSessionException{
redisManager.set(SerializationUtils.serialize(session.getId().toString()),SerializationUtils.serialize(session));
redisManager.expire(SerializationUtils.serialize(session.getId().toString()),60);
}
@Override
publicvoiddelete(Sessionsession){
redisManager.del(SerializationUtils.serialize(session.getId().toString()));
}
@Override
publicCollectiongetActiveSessions(){
returnnewArrayList();
}
@Override
protectedSerializabledoCreate(Sessionsession){//这就是第一次访问的时候,创建sessionId
Serializablesid=this.generateSessionId(session);
assignSessionId(session,sid);
redisManager.set(SerializationUtils.serialize(session.getId().toString()),SerializationUtils.serialize(session));
redisManager.expire(SerializationUtils.serialize(session.getId().toString()),60);
returnsid;
}
@Override
protectedSessiondoReadSession(Serializableserializable){//这个方法其实就是通过sessionId读取session,每读一次,都要重新设置失效时间
byte[]aa=redisManager.get(SerializationUtils.serialize(serializable.toString()));
Sessionsession=(Session)SerializationUtils.deserialize(aa);
redisManager.set(SerializationUtils.serialize(serializable.toString()),SerializationUtils.serialize(session));
redisManager.expire(SerializationUtils.serialize(serializable.toString()),60);
returnsession;
}
}
4)下一步,我就是要在登录成功之后的逻辑里,获取到shiro的session,然后将用户信息设置进去
packagecom.smart.controller;
importcom.smart.pojo.User;
importcom.smart.service.UserService;
importorg.apache.shiro.SecurityUtils;
importorg.apache.shiro.mgt.SecurityManager;
importorg.apache.shiro.subject.Subject;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.*;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
@Controller
@RequestMapping("/user")
publicclassUserController{
@Autowired
privateUserServiceuserService;
@Autowired
privateSecurityManagersm;//注入SecurityManager
privateLoggerlogger=LoggerFactory.getLogger(UserController.class);
@RequestMapping(value="/loginPage")
publicStringloginPage(){
return"user/userLogin";
}
@RequestMapping(value="/userLogin",method=RequestMethod.POST)
publicStringuserLogin(@RequestParam(value="name")Stringname,@RequestParam(value="pwd")Stringpwd,Modelmodel){
logger.info("enteruserLogin...");
Useruser=userService.getUserByNameAndPassword(name,pwd);
if(user==null){
logger.info("userisnotexist...");
model.addAttribute("login_error","用户名或密码错误");
return"user/userLogin";
}
SecurityUtils.setSecurityManager(sm);
SubjectcurrentUser=SecurityUtils.getSubject();
currentUser.getSession().setAttribute("LOGIN_USER",user);
return"redirect:/employee/list";
}
}
获取当前用户,在shiro里是主题,然后获取对应的session,并将用户信息设置进去,是不是感觉有点像Httpsession的操作的样子,哈哈。
5)、最后,定义一个springmvc的拦截器,在拦截器里获取相应的session里的而用户信息,如果获取不到,则跳转到登录界面。
packagecom.smart.core.shiro;
importcom.smart.pojo.User;
importorg.apache.shiro.SecurityUtils;
importorg.apache.shiro.mgt.SecurityManager;
importorg.apache.shiro.subject.Subject;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.web.servlet.HandlerInterceptor;
importorg.springframework.web.servlet.ModelAndView;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
publicclassLoginInterceptorimplementsHandlerInterceptor{
privateLoggerlogger=LoggerFactory.getLogger(LoginInterceptor.class);
@Autowired
privateSecurityManagersm;
@Override
publicbooleanpreHandle(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,Objecto)throwsException{
logger.info("enterLoginInterceptor...");
HttpServletRequestrequest=httpServletRequest;
HttpServletResponseresponse=httpServletResponse;
logger.info("requesturi===>"+request.getRequestURI());//如果是登录页面的请求,则不拦截,否则会陷入死循环
if(request.getRequestURI().contains("loginPage")||request.getRequestURI().contains("userLogin")){
returntrue;
}else{
SecurityUtils.setSecurityManager(sm);
SubjectcurrentUser=SecurityUtils.getSubject();
Objectobj=currentUser.getSession().getAttribute("LOGIN_USER");
if(obj==null){
response.sendRedirect("http://localhost:8080/user/loginPage");
returnfalse;
}else{
Useruser=(User)obj;
if(user==null||user.getName()==null){
response.sendRedirect("http://localhost:8080/user/loginPage");
returnfalse;
}else{
returntrue;
}
}
}
}
@Override
publicvoidpostHandle(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,Objecto,ModelAndViewmodelAndView)throwsException{
}
@Override
publicvoidafterCompletion(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,Objecto,Exceptione)throwsException{
}
}
到这里就基本结束了,如果你现在直接访问主页信息的话,它会自动跳到登录页面。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。